Switch to new login/create/restore status-go endpoints (#14139)

* fix: new login endpoints - iteration 1

* feat: use CreateAccountAndLogin endpoint

* fix keycard import

* better comment

* wip: screens reordering

* cleanup

* force minimum of 30 seconds fetching backup

* keycard flows fixes

* typo fix

* remove ganache argument

* fix: wallet issues

* update status-go
This commit is contained in:
Igor Sirotin 2024-04-24 18:31:22 +02:00 committed by GitHub
parent ec9bf9fc7b
commit 59cb770ddb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
30 changed files with 813 additions and 542 deletions

View File

@ -1546,10 +1546,11 @@ proc runStartUsingKeycardForProfilePopup[T](self: Module[T]) =
################################################################################
method checkAndPerformProfileMigrationIfNeeded*[T](self: Module[T]) =
let keyUid = singletonInstance.userProfile.getKeyUid()
let migrationNeeded = self.settingsService.getProfileMigrationNeeded()
let profileKeypair = self.walletAccountService.getKeypairByKeyUid(singletonInstance.userProfile.getKeyUid())
let profileKeypair = self.walletAccountService.getKeypairByKeyUid(keyUid)
if profileKeypair.isNil:
info "quit the app because of unresolved profile keypair"
info "quit the app because of unresolved profile keypair", keyUid
quit() # quit the app
if not migrationNeeded:
if not self.keycardSharedModule.isNil:

View File

@ -370,7 +370,7 @@ proc getSeedPhraseLength*(self: Controller): int =
proc validSeedPhrase*(self: Controller, seedPhrase: string): bool =
if not serviceApplicable(self.accountsService):
return
let err = self.accountsService.validateMnemonic(seedPhrase)
let (_, err) = self.accountsService.validateMnemonic(seedPhrase)
return err.len == 0
proc getKeyUidForSeedPhrase*(self: Controller, seedPhrase: string): string =

View File

@ -13,19 +13,17 @@ import app_service/service/profile/service as profile_service
import app_service/service/keycard/service as keycard_service
import app_service/service/devices/service as devices_service
import app_service/common/[account_constants, utils]
import app/modules/shared_modules/keycard_popup/io_interface as keycard_shared_module
import app_service/service/accounts/dto/create_account_request
logScope:
topics = "startup-controller"
type ProfileImageDetails = object
url*: string
croppedImage*: string
x1*: int
y1*: int
x2*: int
y2*: int
croppedImage*: string # TODO: Remove after https://github.com/status-im/status-go/issues/4977
cropRectangle*: ImageCropRectangle
type
Controller* = ref object of RootObj
@ -57,6 +55,7 @@ type
tmpRecoverKeycardUsingSeedPhraseWhileLoggingIn: bool
tmpConnectionString: string
localPairingStatus: LocalPairingStatus
loggedInPofilePublicKey: string
proc newController*(delegate: io_interface.AccessInterface,
events: EventEmitter,
@ -116,12 +115,6 @@ proc connectToFetchingFromWakuEvents*(self: Controller) =
self.delegate.onFetchingFromWakuMessageReceived(receivedData.clock, k, v.totalNumber, v.dataNumber)
self.connectionIds.add(handlerId)
proc connectToTimeoutEventAndStratTimer*(self: Controller, timeoutInMilliseconds: int) =
var handlerId = self.events.onWithUUID(SIGNAL_GENERAL_TIMEOUT) do(e: Args):
self.delegate.startAppAfterDelay()
self.connectionIds.add(handlerId)
self.generalService.runTimer(timeoutInMilliseconds)
proc disconnect*(self: Controller) =
self.disconnectKeychain()
for id in self.connectionIds:
@ -134,7 +127,7 @@ proc delete*(self: Controller) =
proc init*(self: Controller) =
var handlerId = self.events.onWithUUID(SignalType.NodeLogin.event) do(e:Args):
let signal = NodeSignal(e)
self.delegate.onNodeLogin(signal.error)
self.delegate.onNodeLogin(signal.error, signal.account, signal.settings)
self.connectionIds.add(handlerId)
handlerId = self.events.onWithUUID(SignalType.NodeStopped.event) do(e:Args):
@ -196,12 +189,14 @@ proc init*(self: Controller) =
proc shouldStartWithOnboardingScreen*(self: Controller): bool =
return self.accountsService.openedAccounts().len == 0
# This is used when fetching backup failed and we create a new displayName and profileImage.
# At this point the account is already created in the database. All that's left is to set the displayName and profileImage.
proc storeProfileDataAndProceedWithAppLoading*(self: Controller) =
self.delegate.removeAllKeycardUidPairsForCheckingForAChangeAfterLogin() # reason for this is in the table in AppController.nim file
discard self.profileService.setDisplayName(self.tmpDisplayName)
let images = self.storeIdentityImage()
self.accountsService.updateLoggedInAccount(self.tmpDisplayName, images)
self.delegate.finishAppLoading()
self.delegate.notifyLoggedInAccountChanged()
proc checkFetchingStatusAndProceed*(self: Controller) =
self.delegate.checkFetchingStatusAndProceed()
@ -221,11 +216,15 @@ proc clearImage*(self: Controller) =
proc generateImage*(self: Controller, imageUrl: string, aX: int, aY: int, bX: int, bY: int): string =
let formatedImg = singletonInstance.utils.formatImagePath(imageUrl)
let images = self.generalService.generateImages(formatedImg, aX, aY, bX, bY)
if(images.len == 0):
if images.len == 0:
return
for img in images:
if(img.imgType == "large"):
self.tmpProfileImageDetails = ProfileImageDetails(url: imageUrl, croppedImage: img.uri, x1: aX, y1: aY, x2: bX, y2: bY)
if img.imgType == "large":
self.tmpProfileImageDetails = ProfileImageDetails(
url: formatedImg,
croppedImage: img.uri,
cropRectangle: ImageCropRectangle(ax: aX, ay: aY, bx: bX, by: bY)
)
return img.uri
proc fetchWakuMessages*(self: Controller) =
@ -349,22 +348,46 @@ proc tryToObtainDataFromKeychain*(self: Controller) =
let selectedAccount = self.getSelectedLoginAccount()
self.keychainService.tryToObtainData(selectedAccount.keyUid)
# TODO: Remove when implemented https://github.com/status-im/status-go/issues/4977
proc storeIdentityImage*(self: Controller): seq[Image] =
if self.tmpProfileImageDetails.url.len == 0:
return
let account = self.accountsService.getLoggedInAccount()
let image = singletonInstance.utils.formatImagePath(self.tmpProfileImageDetails.url)
result = self.profileService.storeIdentityImage(account.keyUid, image, self.tmpProfileImageDetails.x1,
self.tmpProfileImageDetails.y1, self.tmpProfileImageDetails.x2, self.tmpProfileImageDetails.y2)
result = self.profileService.storeIdentityImage(
account.keyUid,
image,
self.tmpProfileImageDetails.cropRectangle.aX,
self.tmpProfileImageDetails.cropRectangle.aY,
self.tmpProfileImageDetails.cropRectangle.bX,
self.tmpProfileImageDetails.cropRectangle.bY,
)
self.tmpProfileImageDetails = ProfileImageDetails()
# validMnemonic only checks if mnemonic is valid
# This is used from UI.
proc validMnemonic*(self: Controller, mnemonic: string): bool =
let err = self.accountsService.validateMnemonic(mnemonic)
let (keyUID, err) = self.accountsService.validateMnemonic(mnemonic)
if err.len == 0:
self.setSeedPhrase(mnemonic)
return true
return false
# validateMnemonicForImport checks if mnemonic is valid and not yet saved in local database
proc validateMnemonicForImport*(self: Controller, mnemonic: string): bool =
let (keyUID, err) = self.accountsService.validateMnemonic(mnemonic)
if err.len != 0:
self.delegate.emitStartupError(err, StartupErrorType.ImportAccError)
return false
if self.accountsService.openedAccountsContainsKeyUid(keyUID):
self.delegate.emitStartupError(ACCOUNT_ALREADY_EXISTS_ERROR, StartupErrorType.ImportAccError)
return false
self.setSeedPhrase(mnemonic)
return true
# TODO: Remove after https://github.com/status-im/status-go/issues/4977
proc importMnemonic*(self: Controller): bool =
let error = self.accountsService.importMnemonic(self.tmpSeedPhrase)
if(error.len == 0):
@ -380,25 +403,37 @@ proc setupKeychain(self: Controller, store: bool) =
else:
singletonInstance.localAccountSettings.setStoreToKeychainValue(LS_VALUE_NEVER)
proc setupAccount(self: Controller, accountId: string, removeMnemonic: bool, storeToKeychain: bool, recoverAccount: bool = false) =
self.delegate.moveToLoadingAppState()
let error = self.accountsService.setupAccount(accountId, self.tmpPassword, self.tmpDisplayName, removeMnemonic, recoverAccount)
proc processCreateAccountResult*(self: Controller, error: string, storeToKeychain: bool) =
if error != "":
self.delegate.emitStartupError(error, StartupErrorType.SetupAccError)
else:
self.setupKeychain(storeToKeychain)
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, removeMnemonic=false, storeToKeychain)
proc createAccountAndLogin*(self: Controller, storeToKeychain: bool) =
self.delegate.moveToLoadingAppState()
let error = self.accountsService.createAccountAndLogin(
self.tmpPassword,
self.tmpDisplayName,
self.tmpProfileImageDetails.url,
self.tmpProfileImageDetails.cropRectangle
)
self.processCreateAccountResult(error, storeToKeychain)
proc storeImportedAccountAndLogin*(self: Controller, storeToKeychain: bool, recoverAccount: bool = false) =
let accountId = self.getImportedAccount().id
self.setupAccount(accountId, removeMnemonic=true, storeToKeychain, recoverAccount)
proc importAccountAndLogin*(self: Controller, storeToKeychain: bool, recoverAccount: bool = false) =
if recoverAccount:
self.delegate.prepareAndInitFetchingData()
self.connectToFetchingFromWakuEvents()
else:
self.delegate.moveToLoadingAppState()
let error = self.accountsService.importAccountAndLogin(
self.tmpSeedPhrase,
self.tmpPassword,
recoverAccount,
self.tmpDisplayName,
self.tmpProfileImageDetails.url,
self.tmpProfileImageDetails.cropRectangle,
)
self.processCreateAccountResult(error, storeToKeychain)
proc storeKeycardAccountAndLogin*(self: Controller, storeToKeychain: bool, newKeycard: bool) =
if self.importMnemonic():
@ -419,6 +454,9 @@ proc setupKeycardAccount*(self: Controller, storeToKeychain: bool, recoverAccoun
self.accountsService.openedAccountsContainsKeyUid(self.tmpKeycardEvent.keyUid):
self.delegate.emitStartupError(ACCOUNT_ALREADY_EXISTS_ERROR, StartupErrorType.ImportAccError)
return
if recoverAccount:
self.delegate.prepareAndInitFetchingData()
self.connectToFetchingFromWakuEvents()
if self.tmpSeedPhrase.len > 0:
# if `tmpSeedPhrase` is not empty means user has recovered keycard via seed phrase
let accFromSeedPhrase = self.accountsService.createAccountFromMnemonic(self.tmpSeedPhrase, includeEncryption = true,
@ -441,7 +479,7 @@ proc setupKeycardAccount*(self: Controller, storeToKeychain: bool, recoverAccoun
self.tmpKeycardEvent.encryptionKey.privateKey = accFromSeedPhrase.derivedAccounts.encryption.privateKey
self.tmpKeycardEvent.encryptionKey.publicKey = accFromSeedPhrase.derivedAccounts.encryption.publicKey
self.tmpKeycardEvent.encryptionKey.address = accFromSeedPhrase.derivedAccounts.encryption.address
self.delegate.moveToLoadingAppState()
self.syncKeycardBasedOnAppWalletStateAfterLogin()
self.accountsService.setupAccountKeycard(self.tmpKeycardEvent, self.tmpDisplayName, useImportedAcc = false, recoverAccount)
self.setupKeychain(storeToKeychain)
@ -611,3 +649,30 @@ proc validateLocalPairingConnectionString*(self: Controller, connectionString: s
proc inputConnectionStringForBootstrapping*(self: Controller, connectionString: string): string =
return self.devicesService.inputConnectionStringForBootstrapping(connectionString)
proc setLoggedInAccount*(self: Controller, account: AccountDto) =
self.accountsService.setLoggedInAccount(account)
proc setLoggedInProfile*(self: Controller, publicKey: string) =
self.loggedInPofilePublicKey = publicKey
proc getLoggedInAccountPublicKey*(self: Controller): string =
return self.loggedInPofilePublicKey
proc getLoggedInAccountDisplayName*(self: Controller): string =
return self.accountsService.getLoggedInAccount().name
proc getLoggedInAccountImage*(self: Controller): string =
let images = self.accountsService.getLoggedInAccount().images
for img in images:
if img.imgType == "large":
return img.uri
return ""
# NOTE: This could be a constant now, but in future we should check if the user
# has already enabled notifications and return corresponding result from this function.
proc notificationsNeedsEnable*(self: Controller): bool =
return main_constants.IS_MACOS
proc proceedToApp*(self: Controller) =
self.delegate.finishAppLoading()

View File

@ -10,16 +10,24 @@ proc newBiometricsState*(flowType: FlowType, backState: State): BiometricsState
proc delete*(self: BiometricsState) =
self.State.delete
method executePrimaryCommand*(self: BiometricsState, controller: Controller) =
let storeToKeychain = true # true, cause we have support for keychain for mac os
method getNextPrimaryState*(self: BiometricsState, controller: Controller): State =
if self.flowType == FlowType.FirstRunOldUserImportSeedPhrase or
self.flowType == FlowType.FirstRunOldUserKeycardImport:
return createState(StateType.ProfileFetching, self.flowType, self)
return nil
method getNextSecondaryState*(self: BiometricsState, controller: Controller): State =
return self.getNextPrimaryState(controller)
proc command(self: BiometricsState, controller: Controller, storeToKeychain: bool) =
if self.flowType == FlowType.FirstRunNewUserNewKeys:
controller.storeGeneratedAccountAndLogin(storeToKeychain)
controller.createAccountAndLogin(storeToKeychain)
elif self.flowType == FlowType.FirstRunNewUserImportSeedPhrase:
controller.storeImportedAccountAndLogin(storeToKeychain)
controller.importAccountAndLogin(storeToKeychain)
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, recoverAccount = true)
controller.importAccountAndLogin(storeToKeychain, recoverAccount = true)
elif self.flowType == FlowType.FirstRunNewUserNewKeycardKeys:
controller.storeKeycardAccountAndLogin(storeToKeychain, newKeycard = true)
elif self.flowType == FlowType.FirstRunNewUserImportSeedPhraseIntoKeycard:
@ -32,27 +40,11 @@ method executePrimaryCommand*(self: BiometricsState, controller: Controller) =
elif self.flowType == FlowType.LostKeycardConvertToRegularAccount:
controller.loginAccountKeycardUsingSeedPhrase(storeToKeychain)
method executePrimaryCommand*(self: BiometricsState, controller: Controller) =
self.command(controller, true)
method executeSecondaryCommand*(self: BiometricsState, controller: Controller) =
let storeToKeychain = false # false, cause we don't have keychain support for other than mac os
if self.flowType == FlowType.FirstRunNewUserNewKeys:
controller.storeGeneratedAccountAndLogin(storeToKeychain)
elif self.flowType == FlowType.FirstRunNewUserImportSeedPhrase:
controller.storeImportedAccountAndLogin(storeToKeychain)
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, recoverAccount = true)
elif self.flowType == FlowType.FirstRunNewUserNewKeycardKeys:
controller.storeKeycardAccountAndLogin(storeToKeychain, newKeycard = true)
elif self.flowType == FlowType.FirstRunNewUserImportSeedPhraseIntoKeycard:
controller.storeKeycardAccountAndLogin(storeToKeychain, newKeycard = true)
elif self.flowType == FlowType.FirstRunOldUserKeycardImport:
controller.setupKeycardAccount(storeToKeychain, recoverAccount = true)
elif self.flowType == FlowType.LostKeycardReplacement:
self.storeToKeychain = storeToKeychain
controller.startLoginFlowAutomatically(controller.getPin())
elif self.flowType == FlowType.LostKeycardConvertToRegularAccount:
controller.loginAccountKeycardUsingSeedPhrase(storeToKeychain)
self.command(controller, false)
method resolveKeycardNextState*(self: BiometricsState, keycardFlowType: string, keycardEvent: KeycardEvent,
controller: Controller): State =

View File

@ -9,4 +9,4 @@ proc delete*(self: NotificationState) =
self.State.delete
method getNextPrimaryState*(self: NotificationState, controller: Controller): State =
return createState(StateType.Welcome, FlowType.General, nil)
controller.proceedToApp()

View File

@ -9,23 +9,10 @@ proc delete*(self: UserProfileChatKeyState) =
self.State.delete
method executePrimaryCommand*(self: UserProfileChatKeyState, controller: Controller) =
if main_constants.IS_MACOS:
return
let storeToKeychain = false # false, cause we don't have keychain support for other than mac os
if self.flowType == FlowType.FirstRunNewUserNewKeycardKeys:
controller.storeKeycardAccountAndLogin(storeToKeychain, newKeycard = true)
elif self.flowType == FlowType.FirstRunNewUserImportSeedPhraseIntoKeycard:
controller.storeKeycardAccountAndLogin(storeToKeychain, newKeycard = true)
elif self.flowType == FlowType.FirstRunOldUserKeycardImport:
controller.setupKeycardAccount(storeToKeychain)
if not controller.notificationsNeedsEnable():
controller.proceedToApp()
method getNextPrimaryState*(self: UserProfileChatKeyState, controller: Controller): State =
if self.flowType == FlowType.FirstRunNewUserNewKeys or
self.flowType == FlowType.FirstRunNewUserImportSeedPhrase:
return createState(StateType.UserProfileCreatePassword, self.flowType, self)
if self.flowType == FlowType.FirstRunNewUserNewKeycardKeys or
self.flowType == FlowType.FirstRunNewUserImportSeedPhraseIntoKeycard or
self.flowType == FlowType.FirstRunOldUserKeycardImport:
if not main_constants.IS_MACOS:
if controller.notificationsNeedsEnable():
return createState(StateType.AllowNotifications, self.flowType, nil)
return nil
return createState(StateType.Biometrics, self.flowType, self)

View File

@ -9,23 +9,21 @@ proc delete*(self: UserProfileConfirmPasswordState) =
self.State.delete
method getNextPrimaryState*(self: UserProfileConfirmPasswordState, controller: Controller): State =
if not main_constants.IS_MACOS:
if not main_constants.SUPPORTS_FINGERPRINT:
return nil
return createState(StateType.Biometrics, self.flowType, self)
method executePrimaryCommand*(self: UserProfileConfirmPasswordState, controller: Controller) =
if main_constants.IS_MACOS:
if main_constants.SUPPORTS_FINGERPRINT:
return
let storeToKeychain = false # false, cause we don't have keychain support for other than mac os
if self.flowType == FlowType.FirstRunNewUserNewKeys:
controller.storeGeneratedAccountAndLogin(storeToKeychain)
controller.createAccountAndLogin(storeToKeychain)
elif self.flowType == FlowType.FirstRunNewUserImportSeedPhrase:
controller.storeImportedAccountAndLogin(storeToKeychain)
controller.importAccountAndLogin(storeToKeychain)
elif self.flowType == FlowType.FirstRunNewUserNewKeycardKeys:
controller.storeKeycardAccountAndLogin(storeToKeychain, newKeycard = true)
elif self.flowType == FlowType.FirstRunOldUserImportSeedPhrase:
controller.storeImportedAccountAndLogin(storeToKeychain, recoverAccount = true)
controller.importAccountAndLogin(storeToKeychain, recoverAccount = true)
elif self.flowType == FlowType.LostKeycardConvertToRegularAccount:
controller.loginAccountKeycardUsingSeedPhrase(storeToKeychain)

View File

@ -9,6 +9,7 @@ proc delete*(self: UserProfileCreateState) =
self.State.delete
method executePrimaryCommand*(self: UserProfileCreateState, controller: Controller) =
# We're here in case of a backup fetch failure
if self.flowType == FlowType.FirstRunOldUserImportSeedPhrase or
self.flowType == FlowType.FirstRunOldUserKeycardImport:
controller.storeProfileDataAndProceedWithAppLoading()
@ -16,9 +17,20 @@ method executePrimaryCommand*(self: UserProfileCreateState, controller: Controll
method getNextPrimaryState*(self: UserProfileCreateState, controller: Controller): State =
if self.flowType == FlowType.FirstRunOldUserImportSeedPhrase or
self.flowType == FlowType.FirstRunOldUserKeycardImport:
return
return createState(StateType.UserProfileChatKey, self.flowType, self)
if self.flowType == FlowType.FirstRunNewUserNewKeys or
self.flowType == FlowType.FirstRunNewUserImportSeedPhrase:
return createState(StateType.UserProfileCreatePassword, self.flowType, self)
if self.flowType == FlowType.FirstRunNewUserNewKeycardKeys or
self.flowType == FlowType.FirstRunNewUserImportSeedPhraseIntoKeycard:
if main_constants.SUPPORTS_FINGERPRINT:
return createState(StateType.Biometrics, self.flowType, self)
return createState(StateType.UserProfileChatKey, self.flowType, self)
return nil
method executeBackCommand*(self: UserProfileCreateState, controller: Controller) =
controller.setDisplayName("")
controller.clearImage()

View File

@ -44,10 +44,13 @@ method getNextPrimaryState*(self: UserProfileEnterSeedPhraseState, controller: C
method executePrimaryCommand*(self: UserProfileEnterSeedPhraseState, controller: Controller) =
if self.flowType == FlowType.FirstRunNewUserImportSeedPhrase or
self.flowType == FlowType.FirstRunOldUserImportSeedPhrase:
self.successfulImport = controller.importMnemonic()
else:
self.successfulImport = controller.validateMnemonicForImport(controller.getSeedPhrase())
return
self.successfulImport = controller.validMnemonic(controller.getSeedPhrase())
if self.successfulImport:
if not self.successfulImport:
return
controller.setKeycardData(updatePredefinedKeycardData(controller.getKeycardData(), PredefinedKeycardData.WrongSeedPhrase, add = false))
let keyUid = controller.getKeyUidForSeedPhrase(controller.getSeedPhrase())

View File

@ -3,6 +3,7 @@ import ../../../app_service/service/accounts/service as accounts_service
import models/login_account_item as login_acc_item
from ../../../app_service/service/keycard/service import KeycardEvent, KeyDetails
from ../../../app_service/service/devices/dto/local_pairing_status import LocalPairingStatus
from ../../../app_service/service/settings/dto/settings import SettingsDto
const UNIQUE_STARTUP_MODULE_IDENTIFIER* = "SartupModule"
@ -115,7 +116,7 @@ method importAccountSuccess*(self: AccessInterface) {.base.} =
method setSelectedLoginAccount*(self: AccessInterface, item: login_acc_item.Item) {.base.} =
raise newException(ValueError, "No implementation available")
method onNodeLogin*(self: AccessInterface, error: string) {.base.} =
method onNodeLogin*(self: AccessInterface, error: string, account: AccountDto, settings: SettingsDto) {.base.} =
raise newException(ValueError, "No implementation available")
method onProfileConverted*(self: AccessInterface, success: bool) {.base.} =
@ -171,15 +172,15 @@ method onFetchingFromWakuMessageReceived*(self: AccessInterface, backedUpMsgCloc
totalMessages: int, loadedMessages: int) {.base.} =
raise newException(ValueError, "No implementation available")
method prepareAndInitFetchingData*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method finishAppLoading*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method checkFetchingStatusAndProceed*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method startAppAfterDelay*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method getConnectionString*(self: AccessInterface): string {.base} =
raise newException(ValueError, "No implementation available")
@ -215,6 +216,21 @@ method insertMockedKeycardAction*(self: AccessInterface, cardIndex: int) {.base.
method removeMockedKeycardAction*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method notificationsNeedsEnable*(self: AccessInterface): bool {.base.} =
raise newException(ValueError, "No implementation available")
method getLoggedInAccountPublicKey*(self: AccessInterface): string {.base.} =
raise newException(ValueError, "No implementation available")
method getLoggedInAccountDisplayName*(self: AccessInterface): string {.base.} =
raise newException(ValueError, "No implementation available")
method getLoggedInAccountImage*(self: AccessInterface): string {.base.} =
raise newException(ValueError, "No implementation available")
method notifyLoggedInAccountChanged*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
# This way (using concepts) is used only for the modules managed by AppController
type
DelegateInterface* = concept c

View File

@ -21,6 +21,7 @@ import app_service/service/general/service as general_service
import app_service/service/profile/service as profile_service
import app_service/service/keycard/service as keycard_service
import app_service/service/devices/service as devices_service
from app_service/service/settings/dto/settings import SettingsDto
import app/modules/shared_modules/keycard_popup/module as keycard_shared_module
@ -124,10 +125,7 @@ method load*[T](self: Module[T]) =
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 main_constants.IS_MACOS:
self.view.setCurrentStartupState(newNotificationState(state.FlowType.General, nil))
else:
if self.controller.shouldStartWithOnboardingScreen():
self.view.setCurrentStartupState(newWelcomeState(state.FlowType.General, nil))
else:
let openedAccounts = self.controller.getOpenedAccounts()
@ -349,7 +347,8 @@ method checkFetchingStatusAndProceed*[T](self: Module[T]) =
if currStateObj.isNil:
error "cannot resolve current state in order to resolve next state"
return
self.view.setCurrentStartupState(newProfileFetchingAnnouncementState(currStateObj.flowType(), nil))
let nextState = newProfileFetchingAnnouncementState(currStateObj.flowType(), nil)
self.view.setCurrentStartupState(nextState)
method onFetchingFromWakuMessageReceived*[T](self: Module[T], backedUpMsgClock: uint64, section: string,
totalMessages: int, receivedMessageAtPosition: int) =
@ -374,26 +373,10 @@ method onFetchingFromWakuMessageReceived*[T](self: Module[T], backedUpMsgClock:
if self.view.fetchingDataModel().allMessagesLoaded():
self.view.setCurrentStartupState(newProfileFetchingSuccessState(currStateObj.flowType(), nil))
proc prepareAndInitFetchingData[T](self: Module[T]) =
method prepareAndInitFetchingData*[T](self: Module[T]) =
# fetching data from waku starts when messenger starts
self.view.createAndInitFetchingDataModel(listOfEntitiesWeExpectToBeSynced)
proc delayStartingApp[T](self: Module[T]) =
## In the following 2 cases:
## - FlowType.FirstRunOldUserImportSeedPhrase
## - FlowType.FirstRunOldUserKeycardImport
## we want to delay app start just to be sure that messages from waku will be received
self.controller.connectToTimeoutEventAndStratTimer(timeoutInMilliseconds = 30000) # delay for 30 seconds
method startAppAfterDelay*[T](self: Module[T]) =
if not self.view.fetchingDataModel().allMessagesLoaded():
let currStateObj = self.view.currentStartupStateObj()
if currStateObj.isNil:
error "cannot determine current startup state", procName="startAppAfterDelay"
quit() # quit the app
self.view.setCurrentStartupState(newProfileFetchingState(currStateObj.flowType(), nil))
self.moveToStartupState()
proc logoutAndDisplayError[T](self: Module[T], error: string, errType: StartupErrorType) =
self.delegate.logout()
if self.controller.isSelectedLoginAccountKeycardAccount() and
@ -418,35 +401,13 @@ method onProfileConverted*[T](self: Module[T], success: bool) =
self.moveToStartupState()
self.view.setCurrentStartupState(newLoginKeycardConvertedToRegularAccountState(currStateObj.flowType(), nil))
method onNodeLogin*[T](self: Module[T], error: string) =
method onNodeLogin*[T](self: Module[T], error: string, account: AccountDto, settings: SettingsDto) =
let currStateObj = self.view.currentStartupStateObj()
if currStateObj.isNil:
error "cannot determine current startup state", procName="onNodeLogin"
quit() # quit the app
if error.len == 0:
if currStateObj.flowType() == state.FlowType.FirstRunOldUserImportSeedPhrase or
currStateObj.flowType() == state.FlowType.FirstRunOldUserKeycardImport:
self.prepareAndInitFetchingData()
self.controller.connectToFetchingFromWakuEvents()
self.delayStartingApp()
let err = self.delegate.userLoggedIn()
if err.len > 0:
self.logoutAndDisplayError(err, StartupErrorType.UnknownType)
return
elif currStateObj.flowType() == state.FlowType.LostKeycardConvertToRegularAccount:
self.controller.convertKeycardProfileKeypairToRegular()
return
else:
let err = self.delegate.userLoggedIn()
if err.len > 0:
self.logoutAndDisplayError(err, StartupErrorType.UnknownType)
return
if currStateObj.flowType() != FlowType.AppLogin:
let images = self.controller.storeIdentityImage()
self.accountsService.updateLoggedInAccount(self.getDisplayName, images)
self.finishAppLoading()
else:
if error.len != 0:
self.moveToStartupState()
if currStateObj.flowType() == state.FlowType.AppLogin:
if self.controller.isSelectedLoginAccountKeycardAccount():
@ -456,6 +417,51 @@ method onNodeLogin*[T](self: Module[T], error: string) =
else:
self.emitStartupError(error, StartupErrorType.SetupAccError)
error "login error", methodName="onNodeLogin", errDesription =error
return
self.controller.setLoggedInAccount(account)
self.accountsService.updateLoggedInAccount(account.name, account.images)
self.controller.setLoggedInProfile(settings.publicKey)
self.view.notifyLoggedInAccountChanged()
if currStateObj.flowType() == state.FlowType.FirstRunOldUserImportSeedPhrase or
currStateObj.flowType() == state.FlowType.FirstRunOldUserKeycardImport:
let err = self.delegate.userLoggedIn()
if err.len > 0:
self.logoutAndDisplayError(err, StartupErrorType.UnknownType)
return
return
if currStateObj.flowType() == state.FlowType.LostKeycardConvertToRegularAccount:
self.controller.convertKeycardProfileKeypairToRegular()
return
let err = self.delegate.userLoggedIn()
if err.len > 0:
self.logoutAndDisplayError(err, StartupErrorType.UnknownType)
return
if currStateObj.flowType() == FlowType.AppLogin:
self.finishAppLoading()
return
# TODO: Remove this block when implemented https://github.com/status-im/status-go/issues/4977
if currStateObj.flowType() == FlowType.FirstRunNewUserNewKeycardKeys or
currStateObj.flowType() == FlowType.FirstRunNewUserImportSeedPhraseIntoKeycard:
let images = self.controller.storeIdentityImage()
self.accountsService.updateLoggedInAccount(self.getDisplayName, images)
self.view.notifyLoggedInAccountChanged()
var nextState: state.State
if currStateObj.flowType() == FlowType.LostKeycardReplacement:
if not self.controller.notificationsNeedsEnable():
self.finishAppLoading()
return
nextState = newNotificationState(currStateObj.flowType(), nil)
else:
nextState = newUserProfileChatKeyState(currStateObj.flowType(), nil)
self.view.setCurrentStartupState(nextState)
self.moveToStartupState()
method onKeycardResponse*[T](self: Module[T], keycardFlowType: string, keycardEvent: KeycardEvent) =
if self.isSharedKeycardModuleFlowRunning():
@ -585,4 +591,19 @@ method insertMockedKeycardAction*[T](self: Module[T], cardIndex: int) =
method removeMockedKeycardAction*[T](self: Module[T]) =
self.keycardService.removeMockedKeycardAction()
method notificationsNeedsEnable*[T](self: Module[T]): bool =
return self.controller.notificationsNeedsEnable()
method getLoggedInAccountPublicKey*[T](self: Module[T]): string =
return self.controller.getLoggedInAccountPublicKey()
method getLoggedInAccountDisplayName*[T](self: Module[T]): string =
return self.controller.getLoggedInAccountDisplayName()
method getLoggedInAccountImage*[T](self: Module[T]): string =
return self.controller.getLoggedInAccountImage()
method notifyLoggedInAccountChanged*[T](self: Module[T]) =
self.view.notifyLoggedInAccountChanged()
{.pop.}

View File

@ -1,4 +1,4 @@
import NimQml
import NimQml, chronicles
import io_interface
import selected_login_account
import internal/[state, state_wrapper]
@ -127,6 +127,7 @@ QtObject:
proc setAppState*(self: View, state: AppState) =
if(self.appState == state):
return
debug "app state changed ", state
self.appState = state
self.appStateChanged(self.appState.int)
QtProperty[int] appState:
@ -206,10 +207,8 @@ QtObject:
proc validMnemonic*(self: View, mnemonic: string): bool {.slot.} =
return self.delegate.validMnemonic(mnemonic)
proc accountImportSuccess*(self: View) {.signal.}
proc importAccountSuccess*(self: View) =
self.importedAccountChanged()
self.accountImportSuccess()
proc selectedLoginAccountChanged*(self: View) {.signal.}
proc getSelectedLoginAccount(self: View): QVariant {.slot.} =
@ -390,3 +389,30 @@ QtObject:
proc removeMockedKeycardAction*(self: View) {.slot.} =
self.delegate.removeMockedKeycardAction()
proc getNotificationsNeedsEnable*(self: View): bool {.slot.} =
return self.delegate.notificationsNeedsEnable()
QtProperty[bool] notificationsNeedsEnable:
read = getNotificationsNeedsEnable
proc getLoggedInAccountPublicKey*(self: View): string {.slot.} =
return self.delegate.getLoggedInAccountPublicKey()
proc loggedInAccountChanged*(self: View) {.signal.}
QtProperty[string] loggedInAccountPublicKey:
read = getLoggedInAccountPublicKey
notify = loggedInAccountChanged
proc getLoggedInAccountDisplayName*(self: View): string {.slot.} =
return self.delegate.getLoggedInAccountDisplayName()
QtProperty[string] loggedInAccountDisplayName:
read = getLoggedInAccountDisplayName
notify = loggedInAccountChanged
proc getLoggedInAccountImage*(self: View): string {.slot.} =
return self.delegate.getLoggedInAccountImage()
QtProperty[string] loggedInAccountImage:
read = getLoggedInAccountImage
notify = loggedInAccountChanged
proc notifyLoggedInAccountChanged*(self: View) =
self.loggedInAccountChanged()

View File

@ -0,0 +1,88 @@
import json, std/options
import ./wallet_secretes_config
import ./image_crop_rectangle
export wallet_secretes_config
export image_crop_rectangle
type
CreateAccountRequest* = object
backupDisabledDataDir*: string
kdfIterations*: int
deviceName*: string
displayName*: string
password*: string
imagePath*: string
imageCropRectangle*: ImageCropRectangle
customizationColor*: string
emoji*: string
wakuV2Nameserver*: Option[string]
wakuV2LightClient*: bool
logLevel*: Option[string]
logFilePath*: string
logEnabled*: bool
previewPrivacy*: bool
verifyTransactionURL*: Option[string]
verifyENSURL*: Option[string]
verifyENSContractAddress*: Option[string]
verifyTransactionChainID*: Option[int64]
upstreamConfig*: string
networkID*: Option[uint64]
walletSecretsConfig*: WalletSecretsConfig
torrentConfigEnabled*: Option[bool]
torrentConfigPort*: Option[int]
proc toJson*(self: CreateAccountRequest): JsonNode =
result = %*{
"backupDisabledDataDir": self.backupDisabledDataDir,
"kdfIterations": self.kdfIterations,
"deviceName": self.deviceName,
"displayName": self.displayName,
"password": self.password,
"imagePath": self.imagePath,
"imageCropRectangle": self.imageCropRectangle,
"customizationColor": self.customizationColor,
"emoji": self.emoji,
"wakuV2LightClient": self.wakuV2LightClient,
"logFilePath": self.logFilePath,
"logEnabled": self.logEnabled,
"previewPrivacy": self.previewPrivacy,
"upstreamConfig": self.upstreamConfig,
}
if self.logLevel.isSome():
result["logLevel"] = %self.logLevel.get("")
if self.wakuV2Nameserver.isSome():
result["wakuV2Nameserver"] = %self.wakuV2Nameserver.get()
if self.verifyTransactionURL.isSome():
result["verifyTransactionURL"] = %self.verifyTransactionURL.get()
if self.verifyENSURL.isSome():
result["verifyENSURL"] = %self.verifyENSURL.get()
if self.verifyENSContractAddress.isSome():
result["verifyENSContractAddress"] = %self.verifyENSContractAddress.get()
if self.verifyTransactionChainID.isSome():
result["verifyTransactionChainID"] = %self.verifyTransactionChainID.get()
if self.networkID.isSome():
result["networkID"] = %self.networkID.get()
if self.torrentConfigEnabled.isSome():
result["torrentConfigEnabled"] = %self.torrentConfigEnabled.get()
if self.torrentConfigPort.isSome():
result["torrentConfigPort"] = %self.torrentConfigPort.get()
for key, value in self.walletSecretsConfig.toJson().pairs():
result[key] = value

View File

@ -0,0 +1,8 @@
type
ImageCropRectangle* = object
ax*: int
ay*: int
bx*: int
by*: int
# Default `toJson` works fine.

View File

@ -0,0 +1,22 @@
import json
import ./wallet_secretes_config
type
LoginAccountRequest* = object
passwordHash*: string
keyUID*: string
kdfIterations*: int
runtimeLogLevel*: string
wakuV2Nameserver*: string
walletSecretsConfig*: WalletSecretsConfig
proc toJson*(self: LoginAccountRequest): JsonNode =
result = %* {
"password": self.passwordHash,
"keyUid": self.keyUID,
"kdfIterations": self.kdfIterations,
"runtimeLogLevel": self.runtimeLogLevel,
"wakuV2Nameserver": self.wakuV2Nameserver
}
for key, value in self.walletSecretsConfig.toJson().pairs():
result[key] = value

View File

@ -0,0 +1,18 @@
import json
import create_account_request
type
RestoreAccountRequest* = object
mnemonic*: string
fetchBackup*: bool
createAccountRequest*: CreateAccountRequest
proc toJson*(self: RestoreAccountRequest): JsonNode =
result = %*{
"mnemonic": self.mnemonic,
"fetchBackup": self.fetchBackup
}
for key, value in self.createAccountRequest.toJson().pairs():
result[key] = value

View File

@ -0,0 +1,38 @@
import json
type
WalletSecretsConfig* = object
poktToken*: string
infuraToken*: string
infuraSecret*: string
openseaApiKey*: string
raribleMainnetApiKey*: string
raribleTestnetApiKey*: string
alchemyEthereumMainnetToken*: string
alchemyEthereumGoerliToken*: string
alchemyEthereumSepoliaToken*: string
alchemyArbitrumMainnetToken*: string
alchemyArbitrumGoerliToken*: string
alchemyArbitrumSepoliaToken*: string
alchemyOptimismMainnetToken*: string
alchemyOptimismGoerliToken*: string
alchemyOptimismSepoliaToken*: string
proc toJson*(self: WalletSecretsConfig): JsonNode =
return %* {
"poktToken": self.poktToken,
"infuraToken": self.infuraToken,
"infuraSecret": self.infuraSecret,
"openseaApiKey": self.openseaApiKey,
"raribleMainnetApiKey": self.raribleMainnetApiKey,
"raribleTestnetApiKey": self.raribleTestnetApiKey,
"alchemyEthereumMainnetToken": self.alchemyEthereumMainnetToken,
"alchemyEthereumGoerliToken": self.alchemyEthereumGoerliToken,
"alchemyEthereumSepoliaToken": self.alchemyEthereumSepoliaToken,
"alchemyArbitrumMainnetToken": self.alchemyArbitrumMainnetToken,
"alchemyArbitrumGoerliToken": self.alchemyArbitrumGoerliToken,
"alchemyArbitrumSepoliaToken": self.alchemyArbitrumSepoliaToken,
"alchemyOptimismMainnetToken": self.alchemyOptimismMainnetToken,
"alchemyOptimismGoerliToken": self.alchemyOptimismGoerliToken,
"alchemyOptimismSepoliaToken": self.alchemyOptimismSepoliaToken
}

View File

@ -1,9 +1,13 @@
import NimQml, Tables, os, json, stew/shims/strformat, sequtils, strutils, uuids, times
import NimQml, Tables, os, json, stew/shims/strformat, sequtils, strutils, uuids, times, std/options
import json_serialization, chronicles
import ../../../app/global/global_singleton
import ./dto/accounts as dto_accounts
import ./dto/generated_accounts as dto_generated_accounts
import ./dto/login_request
import ./dto/create_account_request
import ./dto/restore_account_request
from ../keycard/service import KeycardEvent, KeyDetails
import ../../../backend/general as status_general
import ../../../backend/core as status_core
@ -29,7 +33,7 @@ const DEFAULT_WALLET_ACCOUNT_NAME = "Account 1"
const PATHS = @[PATH_WALLET_ROOT, PATH_EIP_1581, PATH_WHISPER, PATH_DEFAULT_WALLET, PATH_ENCRYPTION]
const ACCOUNT_ALREADY_EXISTS_ERROR* = "account already exists"
const KDF_ITERATIONS* {.intdefine.} = 256_000
const DEFAULT_COLORID_FOR_DEFAULT_WALLET_ACCOUNT = "primary" # to match `CustomizationColor` on the go side
const DEFAULT_CUSTOMIZATION_COLOR = "primary" # to match `CustomizationColor` on the go side
# allow runtime override via environment variable. core contributors can set a
# specific peer to set for testing messaging and mailserver functionality with squish.
@ -66,8 +70,6 @@ QtObject:
defaultWalletEmoji: string
tmpAccount: AccountDto
tmpHashedPassword: string
tmpThumbnailImage: string
tmpLargeImage: string
proc delete*(self: Service) =
self.QObject.delete
@ -81,9 +83,17 @@ QtObject:
result.keyStoreDir = main_constants.ROOTKEYSTOREDIR
result.defaultWalletEmoji = ""
proc setLocalAccountSettingsFile(self: Service) =
if self.loggedInAccount.isValid():
singletonInstance.localAccountSettings.setFileName(self.loggedInAccount.name)
proc getLoggedInAccount*(self: Service): AccountDto =
return self.loggedInAccount
proc setLoggedInAccount*(self: Service, account: AccountDto) =
self.loggedInAccount = account
self.setLocalAccountSettingsFile()
proc updateLoggedInAccount*(self: Service, displayName: string, images: seq[Image]) =
self.loggedInAccount.name = displayName
self.loggedInAccount.images = images
@ -125,12 +135,12 @@ QtObject:
self.loggedInAccount = AccountDto()
self.importedAccount = GeneratedAccountDto()
proc validateMnemonic*(self: Service, mnemonic: string): string =
proc validateMnemonic*(self: Service, mnemonic: string): (string, string) =
try:
let response = status_general.validateMnemonic(mnemonic)
if response.result.contains("error"):
return response.result["error"].getStr
return ""
return ("", response.result["error"].getStr)
return (response.result["keyUID"].getStr, "")
except Exception as e:
error "error: ", procName="validateMnemonic", errName = e.name, errDesription = e.msg
@ -153,47 +163,7 @@ QtObject:
error "error: ", procName="openedAccounts", errName = e.name, errDesription = e.msg
proc openedAccountsContainsKeyUid*(self: Service, keyUid: string): bool =
let openedAccounts = self.openedAccounts()
for acc in openedAccounts:
if acc.keyUid == keyUid:
return true
return false
proc storeDerivedAccounts(self: Service, accountId, hashedPassword: string,
paths: seq[string]): DerivedAccounts =
let response = status_account.storeDerivedAccounts(accountId, hashedPassword, paths)
if response.result.contains("error"):
raise newException(Exception, response.result["error"].getStr)
result = toDerivedAccounts(response.result)
proc storeAccount(self: Service, accountId, hashedPassword: string): GeneratedAccountDto =
let response = status_account.storeAccounts(accountId, hashedPassword)
if response.result.contains("error"):
raise newException(Exception, response.result["error"].getStr)
result = toGeneratedAccountDto(response.result)
proc saveAccountAndLogin(self: Service, hashedPassword: string, account,
subaccounts, settings, config: JsonNode): AccountDto =
try:
let response = status_account.saveAccountAndLogin(hashedPassword, account, subaccounts, settings, config)
var error = "response doesn't contain \"error\""
if(response.result.contains("error")):
error = response.result["error"].getStr
if error == "":
debug "Account saved succesfully"
result = toAccountDto(account)
return
let err = "Error saving account and logging in: " & error
error "error: ", procName="saveAccountAndLogin", errDesription = err
except Exception as e:
error "error: ", procName="saveAccountAndLogin", errName = e.name, errDesription = e.msg
return (keyUID in self.openedAccounts().mapIt(it.keyUid))
proc saveKeycardAccountAndLogin(self: Service, chatKey, password: string, account, subaccounts, settings,
config: JsonNode): AccountDto =
@ -214,31 +184,13 @@ QtObject:
except Exception as e:
error "error: ", procName="saveKeycardAccountAndLogin", errName = e.name, errDesription = e.msg
proc prepareAccountJsonObject(self: Service, account: GeneratedAccountDto, displayName: string): JsonNode =
result = %* {
"name": if displayName == "": account.alias else: displayName,
"address": account.address,
"key-uid": account.keyUid,
"keycard-pairing": nil,
"kdfIterations": KDF_ITERATIONS,
}
proc getAccountDataForAccountId(self: Service, accountId: string, displayName: string): JsonNode =
for acc in self.generatedAccounts:
if(acc.id == accountId):
return self.prepareAccountJsonObject(acc, displayName)
if(self.importedAccount.isValid()):
if(self.importedAccount.id == accountId):
return self.prepareAccountJsonObject(self.importedAccount, displayName)
proc prepareSubaccountJsonObject(self: Service, account: GeneratedAccountDto, displayName: string):
JsonNode =
result = %* [
{
"public-key": account.derivedAccounts.defaultWallet.publicKey,
"address": account.derivedAccounts.defaultWallet.address,
"colorId": DEFAULT_COLORID_FOR_DEFAULT_WALLET_ACCOUNT,
"colorId": DEFAULT_CUSTOMIZATION_COLOR,
"wallet": true,
"path": PATH_DEFAULT_WALLET,
"name": DEFAULT_WALLET_ACCOUNT_NAME,
@ -312,6 +264,7 @@ QtObject:
if(self.importedAccount.id == accountId):
return self.prepareAccountSettingsJsonObject(self.importedAccount, installationId, displayName, withoutMnemonic)
# TODO: Remove after https://github.com/status-im/status-go/issues/4977
proc getDefaultNodeConfig*(self: Service, installationId: string, recoverAccount: bool): JsonNode =
let fleet = Fleet.ShardsTest
let dnsDiscoveryURL = "enrtree://AMOJVZX4V6EXP7NTJPMAYJYST2QP6AJXYW76IU6VGJS7UVSNDYZG4@boot.test.shards.nodes.status.im"
@ -352,6 +305,7 @@ QtObject:
result["KeycardPairingDataFile"] = newJString(main_constants.KEYCARDPAIRINGDATAFILE)
result["ProcessBackedupMessages"] = newJBool(recoverAccount)
# TODO: Remove after https://github.com/status-im/status-go/issues/4977
proc getLoginNodeConfig(self: Service): JsonNode =
# To create appropriate NodeConfig for Login we set only params that maybe be set via env variables or cli flags
result = %*{}
@ -394,10 +348,6 @@ QtObject:
if STATUS_PORT != 0:
result["ListenAddr"] = newJString("0.0.0.0:" & $main_constants.STATUS_PORT)
proc setLocalAccountSettingsFile(self: Service) =
if self.getLoggedInAccount.isValid():
singletonInstance.localAccountSettings.setFileName(self.getLoggedInAccount.name)
proc addKeycardDetails(self: Service, kcInstance: string, settingsJson: var JsonNode, accountData: var JsonNode) =
let keycardPairingJsonString = readFile(main_constants.KEYCARDPAIRINGDATAFILE)
let keycardPairingJsonObj = keycardPairingJsonString.parseJSON
@ -412,38 +362,87 @@ QtObject:
if not accountData.isNil:
accountData["keycard-pairing"] = kcDataObj{"key"}
proc setupAccount*(self: Service, accountId, password, displayName: string, removeMnemonic: bool, recoverAccount: bool = false): string =
proc buildWalletSecrets(self: Service): WalletSecretsConfig =
return WalletSecretsConfig(
poktToken: POKT_TOKEN_RESOLVED,
infuraToken: INFURA_TOKEN_RESOLVED,
infuraSecret: INFURA_TOKEN_SECRET_RESOLVED,
openseaApiKey: OPENSEA_API_KEY_RESOLVED,
raribleMainnetApiKey: RARIBLE_MAINNET_API_KEY_RESOLVED,
raribleTestnetApiKey: RARIBLE_TESTNET_API_KEY_RESOLVED,
alchemyEthereumMainnetToken: ALCHEMY_ETHEREUM_MAINNET_TOKEN_RESOLVED,
alchemyEthereumGoerliToken: ALCHEMY_ETHEREUM_GOERLI_TOKEN_RESOLVED,
alchemyEthereumSepoliaToken: ALCHEMY_ETHEREUM_SEPOLIA_TOKEN_RESOLVED,
alchemyArbitrumMainnetToken: ALCHEMY_ARBITRUM_MAINNET_TOKEN_RESOLVED,
alchemyArbitrumGoerliToken: ALCHEMY_ARBITRUM_GOERLI_TOKEN_RESOLVED,
alchemyArbitrumSepoliaToken: ALCHEMY_ARBITRUM_SEPOLIA_TOKEN_RESOLVED,
alchemyOptimismMainnetToken: ALCHEMY_OPTIMISM_MAINNET_TOKEN_RESOLVED,
alchemyOptimismGoerliToken: ALCHEMY_OPTIMISM_GOERLI_TOKEN_RESOLVED,
alchemyOptimismSepoliaToken: ALCHEMY_OPTIMISM_SEPOLIA_TOKEN_RESOLVED,
)
proc buildCreateAccountRequest(self: Service, password: string, displayName: string, imagePath: string, imageCropRectangle: ImageCropRectangle): CreateAccountRequest =
return CreateAccountRequest(
backupDisabledDataDir: main_constants.STATUSGODIR,
kdfIterations: KDF_ITERATIONS,
password: hashPassword(password),
displayName: displayName,
imagePath: imagePath,
imageCropRectangle: imageCropRectangle,
customizationColor: DEFAULT_CUSTOMIZATION_COLOR,
emoji: self.defaultWalletEmoji,
logLevel: some(toStatusGoSupportedLogLevel(main_constants.LOG_LEVEL)),
wakuV2LightClient: false,
previewPrivacy: true,
torrentConfigEnabled: some(true),
torrentConfigPort: some(TORRENT_CONFIG_PORT),
walletSecretsConfig: self.buildWalletSecrets(),
)
proc createAccountAndLogin*(self: Service, password: string, displayName: string, imagePath: string, imageCropRectangle: ImageCropRectangle): string =
try:
let installationId = $genUUID()
var accountDataJson = self.getAccountDataForAccountId(accountId, displayName)
self.setKeyStoreDir(accountDataJson{"key-uid"}.getStr) # must be called before `getDefaultNodeConfig`
let subaccountDataJson = self.getSubaccountDataForAccountId(accountId, displayName)
var settingsJson = self.getAccountSettings(accountId, installationId, displayName, removeMnemonic)
let nodeConfigJson = self.getDefaultNodeConfig(installationId, recoverAccount)
let request = self.buildCreateAccountRequest(password, displayName, imagePath, imageCropRectangle)
let response = status_account.createAccountAndLogin(request)
if(accountDataJson.isNil or subaccountDataJson.isNil or settingsJson.isNil or
nodeConfigJson.isNil):
let description = "at least one json object is not prepared well"
error "error: ", procName="setupAccount", errDesription = description
return description
if not response.result.contains("error"):
error "invalid status-go response", response
return "invalid response: no error field found"
let hashedPassword = hashPassword(password)
discard self.storeAccount(accountId, hashedPassword)
discard self.storeDerivedAccounts(accountId, hashedPassword, PATHS)
self.loggedInAccount = self.saveAccountAndLogin(hashedPassword,
accountDataJson,
subaccountDataJson,
settingsJson,
nodeConfigJson)
self.setLocalAccountSettingsFile()
if self.getLoggedInAccount.isValid():
let error = response.result["error"].getStr
if error == "":
debug "Account saved succesfully"
return ""
else:
return "logged in account is not valid"
error "createAccountAndLogin status-go error: ", error
return "createAccountAndLogin failed: " & error
except Exception as e:
error "error: ", procName="setupAccount", errName = e.name, errDesription = e.msg
error "failed to create account or login", procName="createAccountAndLogin", errName = e.name, errDesription = e.msg
return e.msg
proc importAccountAndLogin*(self: Service, mnemonic: string, password: string, recoverAccount: bool, displayName: string, imagePath: string, imageCropRectangle: ImageCropRectangle): string =
try:
let request = RestoreAccountRequest(
mnemonic: mnemonic,
fetchBackup: recoverAccount,
createAccountRequest: self.buildCreateAccountRequest(password, displayName, imagePath, imageCropRectangle),
)
let response = status_account.restoreAccountAndLogin(request)
if not response.result.contains("error"):
error "invalid status-go response", response
return "invalid response: no error field found"
let error = response.result["error"].getStr
if error == "":
debug "Account saved succesfully"
return ""
error "importAccountAndLogin status-go error: ", error
return "importAccountAndLogin failed: " & error
except Exception as e:
error "failed to import account or login", procName="importAccountAndLogin", errName = e.name, errDesription = e.msg
return e.msg
proc setupAccountKeycard*(self: Service, keycardData: KeycardEvent, displayName: string, useImportedAcc: bool,
@ -491,7 +490,7 @@ QtObject:
{
"public-key": walletPublicKey,
"address": walletAddress,
"colorId": DEFAULT_COLORID_FOR_DEFAULT_WALLET_ACCOUNT,
"colorId": DEFAULT_CUSTOMIZATION_COLOR,
"wallet": true,
"path": PATH_DEFAULT_WALLET,
"name": DEFAULT_WALLET_ACCOUNT_NAME,
@ -658,35 +657,28 @@ QtObject:
except Exception as e:
error "error: ", procName="verifyDatabasePassword", errName = e.name, errDesription = e.msg
proc doLogin(self: Service, account: AccountDto, hashedPassword, thumbnailImage, largeImage: string) =
let nodeConfigJson = self.getLoginNodeConfig()
let response = status_account.login(
account.name,
account.keyUid,
account.kdfIterations,
hashedPassword,
thumbnailImage,
largeImage,
$nodeConfigJson
proc doLogin(self: Service, account: AccountDto, passwordHash: string) =
var request = LoginAccountRequest(
keyUid: account.keyUid,
kdfIterations: account.kdfIterations,
passwordHash: passwordHash,
walletSecretsConfig: self.buildWalletSecrets(),
)
if main_constants.runtimeLogLevelSet():
request.runtimeLogLevel = toStatusGoSupportedLogLevel(main_constants.LOG_LEVEL)
let response = status_account.loginAccount(request)
if response.result{"error"}.getStr != "":
self.events.emit(SIGNAL_LOGIN_ERROR, LoginErrorArgs(error: response.result{"error"}.getStr))
return
debug "Account logged in"
self.loggedInAccount = account
debug "account logged in"
self.setLocalAccountSettingsFile()
proc login*(self: Service, account: AccountDto, hashedPassword: string) =
try:
var thumbnailImage: string
var largeImage: string
for img in account.images:
if(img.imgType == "thumbnail"):
thumbnailImage = img.uri
elif(img.imgType == "large"):
largeImage = img.uri
let keyStoreDir = joinPath(main_constants.ROOTKEYSTOREDIR, account.keyUid) & main_constants.sep
if not dirExists(keyStoreDir):
os.createDir(keyStoreDir)
@ -698,11 +690,11 @@ QtObject:
let isOldHashPassword = self.verifyDatabasePassword(account.keyUid, hashedPasswordToUpperCase(hashedPassword))
if isOldHashPassword:
debug "database reencryption scheduled"
# Save tmp properties so that we can login after the timer
self.tmpAccount = account
self.tmpHashedPassword = hashedPassword
self.tmpThumbnailImage = thumbnailImage
self.tmpLargeImage = largeImage
# Start a 1 second timer for the loading screen to appear
let arg = TimerTaskArg(
@ -714,24 +706,25 @@ QtObject:
self.threadpool.start(arg)
return
self.doLogin(account, hashedPassword, thumbnailImage, largeImage)
self.doLogin(account, hashedPassword)
except Exception as e:
error "error: ", procName="login", errName = e.name, errDesription = e.msg
error "login failed", errName = e.name, errDesription = e.msg
self.events.emit(SIGNAL_LOGIN_ERROR, LoginErrorArgs(error: e.msg))
proc onWaitForReencryptionTimeout(self: Service, response: string) {.slot.} =
debug "starting database reencryption"
# Reencryption (can freeze and take up to 30 minutes)
let oldHashedPassword = hashedPasswordToUpperCase(self.tmpHashedPassword)
discard status_privacy.changeDatabasePassword(self.tmpAccount.keyUid, oldHashedPassword, self.tmpHashedPassword)
# Normal login after reencryption
self.doLogin(self.tmpAccount, self.tmpHashedPassword, self.tmpThumbnailImage, self.tmpLargeImage)
self.doLogin(self.tmpAccount, self.tmpHashedPassword)
# Clear out the temp properties
self.tmpAccount = AccountDto()
self.tmpHashedPassword = ""
self.tmpThumbnailImage = ""
self.tmpLargeImage = ""
proc loginAccountKeycard*(self: Service, accToBeLoggedIn: AccountDto, keycardData: KeycardEvent): string =
try:
@ -758,7 +751,7 @@ QtObject:
self.setLocalAccountSettingsFile()
return
except Exception as e:
error "error: ", procName="loginAccountKeycard", errName = e.name, errDesription = e.msg
error "keycard login failed", procName="loginAccountKeycard", errName = e.name, errDesription = e.msg
return e.msg
proc convertRegularProfileKeypairToKeycard*(self: Service, keycardUid, currentPassword: string, newPassword: string) =

View File

@ -1,6 +1,9 @@
import json, json_serialization, chronicles, strutils
import ./core, ../app_service/common/utils
import ../app_service/service/wallet_account/dto/account_dto
import ../app_service/service/accounts/dto/login_request
import ../app_service/service/accounts/dto/create_account_request
import ../app_service/service/accounts/dto/restore_account_request
import ./response_type
import status_go
@ -310,36 +313,6 @@ proc openedAccounts*(path: string): RpcResponse[JsonNode] =
error "error doing rpc request", methodName = "openedAccounts", exception=e.msg
raise newException(RpcException, e.msg)
proc storeDerivedAccounts*(id, hashedPassword: string, paths: seq[string]):
RpcResponse[JsonNode] =
let payload = %* {
"accountID": id,
"paths": paths,
"password": hashedPassword
}
try:
let response = status_go.multiAccountStoreDerivedAccounts($payload)
result.result = Json.decode(response, JsonNode)
except RpcException as e:
error "error doing rpc request", methodName = "storeDerivedAccounts", exception=e.msg
raise newException(RpcException, e.msg)
proc storeAccounts*(id, hashedPassword: string): RpcResponse[JsonNode] =
let payload = %* {
"accountID": id,
"password": hashedPassword
}
try:
let response = status_go.multiAccountStoreAccount($payload)
result.result = Json.decode(response, JsonNode)
except RpcException as e:
error "error doing rpc request", methodName = "storeAccounts", exception=e.msg
raise newException(RpcException, e.msg)
proc addPeer*(peer: string): RpcResponse[JsonNode] =
try:
let response = status_go.addPeer(peer)
@ -349,15 +322,24 @@ proc addPeer*(peer: string): RpcResponse[JsonNode] =
error "error doing rpc request", methodName = "addPeer", exception=e.msg
raise newException(RpcException, e.msg)
proc saveAccountAndLogin*(hashedPassword: string, account, subaccounts, settings,
config: JsonNode): RpcResponse[JsonNode] =
proc createAccountAndLogin*(request: CreateAccountRequest): RpcResponse[JsonNode] =
try:
let response = status_go.saveAccountAndLogin($account, hashedPassword,
$settings, $config, $subaccounts)
let payload = request.toJson()
let response = status_go.createAccountAndLogin($payload)
result.result = Json.decode(response, JsonNode)
except RpcException as e:
error "error doing rpc request", methodName = "saveAccountAndLogin", exception=e.msg
error "error doing rpc request", methodName = "createAccountAndLogin", exception=e.msg
raise newException(RpcException, e.msg)
proc restoreAccountAndLogin*(request: RestoreAccountRequest): RpcResponse[JsonNode] =
try:
let payload = request.toJson()
let response = status_go.restoreAccountAndLogin($payload)
result.result = Json.decode(response, JsonNode)
except RpcException as e:
error "error doing rpc request", methodName = "restoreAccountAndLogin", exception=e.msg
raise newException(RpcException, e.msg)
proc saveAccountAndLoginWithKeycard*(chatKey, password: string, account, subaccounts, settings, config: JsonNode):
@ -388,25 +370,14 @@ proc convertKeycardProfileKeypairToRegular*(mnemonic: string, currPassword: stri
error "error doing rpc request", methodName = "convertKeycardProfileKeypairToRegular", exception=e.msg
raise newException(RpcException, e.msg)
proc login*(name, keyUid: string, kdfIterations: int, hashedPassword, thumbnail, large: string, nodeCfgObj: string):
RpcResponse[JsonNode]
=
proc loginAccount*(request: LoginAccountRequest): RpcResponse[JsonNode] =
try:
var payload = %* {
"name": name,
"key-uid": keyUid,
"identityImage": newJNull(),
"kdfIterations": kdfIterations,
}
if(thumbnail.len>0 and large.len > 0):
payload["identityImage"] = %* {"thumbnail": thumbnail, "large": large}
let response = status_go.loginWithConfig($payload, hashedPassword, nodeCfgObj)
let payload = request.toJson()
let response = status_go.loginAccount($payload)
result.result = Json.decode(response, JsonNode)
except RpcException as e:
error "error doing rpc request", methodName = "login", exception=e.msg
error "loginAccount failed", exception=e.msg
raise newException(RpcException, e.msg)
proc loginWithKeycard*(chatKey, password: string, account, confNode: JsonNode): RpcResponse[JsonNode] =

View File

@ -51,9 +51,11 @@ OnboardingBasePage {
return keysMainViewComponent
case Constants.startupState.userProfileCreate:
case Constants.startupState.userProfileChatKey:
return insertDetailsViewComponent
case Constants.startupState.userProfileChatKey:
return profileChatKeyViewComponent
case Constants.startupState.userProfileCreatePassword:
return createPasswordViewComponent
@ -241,6 +243,13 @@ following the \"Add existing Status user\" flow, using your seed phrase.")
}
}
Component {
id: profileChatKeyViewComponent
ProfileChatKeyView {
startupStore: root.startupStore
}
}
Component {
id: createPasswordViewComponent
CreatePasswordView {

View File

@ -20,6 +20,7 @@ QtObject {
readonly property string localPairingInstallationId: startupModuleInst ? startupModuleInst.localPairingInstallationId : ""
readonly property string localPairingInstallationName: startupModuleInst ? startupModuleInst.localPairingInstallationName : ""
readonly property string localPairingInstallationDeviceType: startupModuleInst ? startupModuleInst.localPairingInstallationDeviceType : ""
readonly property bool notificationsNeedsEnable: startupModuleInst ? startupModuleInst.notificationsNeedsEnable : false
function backAction() {
root.currentStartupState.backAction()

View File

@ -68,7 +68,7 @@ Item {
leftPadding: Style.current.padding
rightPadding: Style.current.padding
font.weight: Font.Medium
text: qsTr("Ok, got it")
text: qsTr("Start using Status")
onClicked: {
root.startupStore.doPrimaryAction()
}

View File

@ -22,56 +22,23 @@ import "../shared"
Item {
id: root
objectName: "onboardingInsertDetailsView"
property StartupStore startupStore
property string pubKey
property string address
property string displayName
signal createPassword()
Component.onCompleted: {
if (!!root.startupStore.startupModuleInst.importedAccountPubKey) {
root.address = root.startupStore.startupModuleInst.importedAccountAddress ;
root.pubKey = root.startupStore.startupModuleInst.importedAccountPubKey;
}
nameInput.text = root.startupStore.getDisplayName();
userImage.asset.name = root.startupStore.getCroppedProfileImage();
}
onStateChanged: {
if (state === Constants.startupState.userProfileCreate) {
nameInput.input.edit.forceActiveFocus()
return
}
nextBtn.forceActiveFocus()
}
Loader {
active: !root.startupStore.startupModuleInst.importedAccountPubKey
sourceComponent: StatusListView {
model: root.startupStore.startupModuleInst.generatedAccountsModel
delegate: Item {
Component.onCompleted: {
if (index === 0) {
root.address = model.address;
root.pubKey = model.pubKey;
}
}
}
}
}
QtObject {
id: d
function doAction() {
if(!nextBtn.enabled) {
if (!nextBtn.enabled) {
return
}
if (root.state === Constants.startupState.userProfileCreate) {
root.startupStore.setDisplayName(nameInput.text)
root.displayName = nameInput.text;
}
root.startupStore.doPrimaryAction()
}
}
@ -109,19 +76,16 @@ Item {
Layout.alignment: Qt.AlignTop | Qt.AlignHCenter
Layout.topMargin: Style.current.bigPadding
StatusSmartIdenticon {
anchors.left: parent.left
id: userImage
objectName: "welcomeScreenUserProfileImage"
anchors.left: parent.left
asset.width: 86
asset.height: 86
asset.letterSize: 32
asset.color: Utils.colorForPubkey(root.pubKey)
asset.color: Utils.colorForColorId(0) // We haven't generated the keys yet, show default color
asset.charactersLen: 2
asset.isImage: !!asset.name
asset.imgIsIdenticon: false
ringSettings {
ringSpecModel: Utils.getColorHashAsJson(root.pubKey)
}
}
StatusRoundButton {
id: updatePicButton
@ -165,54 +129,6 @@ Item {
}
}
StyledText {
id: chatKeyTxt
objectName: "insertDetailsViewChatKeyTxt"
Layout.preferredHeight: 22
color: Style.current.secondaryText
text: qsTr("Chatkey:") + " " + Utils.getCompressedPk(root.pubKey)
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.WordWrap
Layout.alignment: Qt.AlignHCenter | Qt.AlignTop
Layout.topMargin: 13
font.pixelSize: 15
}
Item {
id: chainsChatKeyImg
Layout.alignment: Qt.AlignHCenter | Qt.AlignTop
Layout.topMargin: Style.current.padding
Layout.preferredWidth: 215
Layout.preferredHeight: 77
Image {
id: imgChains
anchors.horizontalCenter: parent.horizontalCenter
source: Style.svg("onboarding/chains")
cache: false
}
EmojiHash {
anchors {
bottom: parent.bottom
left: parent.left
}
publicKey: root.pubKey
objectName: "publicKeyEmojiHash"
}
StatusSmartIdenticon {
id: userImageCopy
anchors {
bottom: parent.bottom
right: parent.right
rightMargin: 25
}
asset.width: 44
asset.height: 44
asset.color: "transparent"
ringSettings { ringSpecModel: Utils.getColorHashAsJson(root.pubKey) }
}
}
Item {
Layout.fillWidth: true
Layout.fillHeight: true
@ -252,92 +168,4 @@ Item {
}
}
}
states: [
State {
name: Constants.startupState.userProfileCreate
when: root.startupStore.currentStartupState.stateType === Constants.startupState.userProfileCreate
PropertyChanges {
target: usernameText
text: qsTr("Your profile")
}
PropertyChanges {
target: txtDesc
text: qsTr("Longer and unusual names are better as they are less likely to be used by someone else.")
}
PropertyChanges {
target: chatKeyTxt
visible: false
}
PropertyChanges {
target: chainsChatKeyImg
opacity: 0.0
}
PropertyChanges {
target: userImageCopy
opacity: 0.0
}
PropertyChanges {
target: updatePicButton
opacity: 1.0
}
PropertyChanges {
target: nameInputItem
enabled: true
visible: true
}
},
State {
name: Constants.startupState.userProfileChatKey
when: root.startupStore.currentStartupState.stateType === Constants.startupState.userProfileChatKey
PropertyChanges {
target: usernameText
text: qsTr("Your emojihash and identicon ring")
}
PropertyChanges {
target: txtDesc
text: qsTr("This set of emojis and coloured ring around your avatar are unique and represent your chat key, so your friends can easily distinguish you from potential impersonators.")
}
PropertyChanges {
target: chatKeyTxt
visible: true
}
PropertyChanges {
target: chainsChatKeyImg
opacity: 1.0
}
PropertyChanges {
target: userImageCopy
opacity: 1.0
}
PropertyChanges {
target: updatePicButton
opacity: 0.0
}
PropertyChanges {
target: nameInputItem
enabled: false
visible: false
}
}
]
transitions: [
Transition {
from: "*"
to: "*"
SequentialAnimation {
PropertyAction {
target: root
property: "opacity"
value: 0.0
}
PropertyAction {
target: root
property: "opacity"
value: 1.0
}
}
}
]
}

View File

@ -0,0 +1,169 @@
import QtQuick 2.13
import QtQuick.Layouts 1.12
import QtQuick.Controls 2.14
import QtQuick.Dialogs 1.3
import StatusQ.Components 0.1
import StatusQ.Controls 0.1
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Popups 0.1
import shared.panels 1.0
import shared 1.0
import shared.popups 1.0
import shared.controls 1.0
import utils 1.0
import "../popups"
import "../stores"
import "../shared"
Item {
id: root
objectName: "onboardingProfileChatKeyView"
property StartupStore startupStore
Component.onCompleted: {
nextBtn.forceActiveFocus()
}
QtObject {
id: d
readonly property string publicKey: root.startupStore.startupModuleInst.loggedInAccountPublicKey
readonly property string displayName: root.startupStore.startupModuleInst.loggedInAccountDisplayName
readonly property string image: root.startupStore.startupModuleInst.loggedInAccountImage
function doAction() {
if (!nextBtn.enabled) {
return
}
root.startupStore.doPrimaryAction()
}
}
ColumnLayout {
height: 461
anchors.centerIn: parent
StyledText {
id: usernameText
objectName: "onboardingHeaderText"
text: qsTr("Your emojihash and identicon ring")
font.weight: Font.Bold
font.pixelSize: 22
Layout.alignment: Qt.AlignHCenter
}
StyledText {
id: txtDesc
Layout.preferredWidth: root.state === Constants.startupState.userProfileCreate? 338 : 643
Layout.alignment: Qt.AlignTop | Qt.AlignHCenter
Layout.topMargin: Style.current.smallPadding
color: Style.current.secondaryText
text: qsTr("This set of emojis and coloured ring around your avatar are unique and represent your chat key, so your friends can easily distinguish you from potential impersonators.")
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.WordWrap
font.pixelSize: 15
lineHeight: 1.2
font.letterSpacing: -0.2
}
Item {
Layout.preferredWidth: 86
Layout.preferredHeight: 86
Layout.alignment: Qt.AlignTop | Qt.AlignHCenter
Layout.topMargin: Style.current.bigPadding
StatusSmartIdenticon {
id: userImage
objectName: "welcomeScreenUserProfileImage"
anchors.left: parent.left
asset.width: 86
asset.height: 86
asset.letterSize: 32
asset.color: Utils.colorForPubkey(d.publicKey)
asset.charactersLen: 2
asset.isImage: !!asset.name
asset.imgIsIdenticon: false
asset.name: d.image
name: d.displayName
ringSettings {
ringSpecModel: Utils.getColorHashAsJson(d.publicKey)
}
}
}
StyledText {
id: chatKeyTxt
objectName: "profileChatKeyViewChatKeyTxt"
Layout.preferredHeight: 22
color: Style.current.secondaryText
text: qsTr("Chatkey:") + " " + Utils.getCompressedPk(d.publicKey)
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.WordWrap
Layout.alignment: Qt.AlignHCenter | Qt.AlignTop
Layout.topMargin: 13
font.pixelSize: 15
visible: true
}
Item {
id: chainsChatKeyImg
Layout.alignment: Qt.AlignHCenter | Qt.AlignTop
Layout.topMargin: Style.current.padding
Layout.preferredWidth: 215
Layout.preferredHeight: 77
Image {
id: imgChains
anchors.horizontalCenter: parent.horizontalCenter
source: Style.svg("onboarding/chains")
cache: false
}
EmojiHash {
anchors {
bottom: parent.bottom
left: parent.left
}
publicKey: d.publicKey
objectName: "publicKeyEmojiHash"
}
StatusSmartIdenticon {
id: userImageCopy
anchors {
bottom: parent.bottom
right: parent.right
rightMargin: 25
}
asset.width: 44
asset.height: 44
asset.color: "transparent"
ringSettings { ringSpecModel: Utils.getColorHashAsJson(d.publicKey) }
}
}
Item {
Layout.fillWidth: true
Layout.fillHeight: true
}
StatusButton {
id: nextBtn
objectName: "onboardingDetailsViewNextButton"
Layout.alignment: Qt.AlignHCenter
font.weight: Font.Medium
text: root.startupStore.notificationsNeedsEnable ? qsTr("Next") : qsTr("Start using Status")
onClicked: {
d.doAction()
}
Keys.onPressed: {
if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
event.accepted = true
d.doAction()
}
}
}
}
}

View File

@ -22,6 +22,11 @@ Item {
property int counter: Constants.onboarding.profileFetching.timeout
// NOTE: This block can be removed when fetching backup is optimized
// https://github.com/status-im/status-go/issues/5022
readonly property bool minimumWaitingTimePassed: (Constants.onboarding.profileFetching.timeout - counter) > 30000
readonly property bool fetching: root.startupStore.currentStartupState.stateType === Constants.startupState.profileFetching || !d.minimumWaitingTimePassed
readonly property string fetchingDataText: qsTr("Fetching data...")
readonly property string continueText: qsTr("Continue")
}
@ -147,12 +152,12 @@ Item {
id: button
Layout.alignment: Qt.AlignHCenter
focus: true
enabled: root.state !== Constants.startupState.profileFetching
enabled: !d.fetching
Timer {
id: timer
interval: 1000
running: root.state === Constants.startupState.profileFetching
running: d.fetching
repeat: true
onTriggered: {
d.counter = d.counter - 1000 // decrease 1000 ms
@ -176,7 +181,7 @@ Item {
states: [
State {
name: Constants.startupState.profileFetching
when: root.startupStore.currentStartupState.stateType === Constants.startupState.profileFetching
when: d.fetching
PropertyChanges {
target: title
text: d.fetchingDataText

View File

@ -174,7 +174,7 @@ Item {
StatusButton {
Layout.alignment: Qt.AlignHCenter
enabled: d.allEntriesValid
text: qsTr("Finish")
text: qsTr("Next")
onClicked: {
root.startupStore.doPrimaryAction()
}

@ -1 +1 @@
Subproject commit 294ee4bf49df10af5c877cfdc4d36a6688c0f0ca
Subproject commit d38b1147f8b4d0c4abdb1dbf353aff36454e02ed

2
vendor/status-go vendored

@ -1 +1 @@
Subproject commit 78db9054fc4fac565f5fb6cdd711d22e5870d0a1
Subproject commit 68bc9d39fd9e42e75448d1536e2825a0fbc3d19a