feat(@desktop/onboarding): `Lost Keycard` - `Create replacement Keycard with seed phrase` flow

This commit introduces:
- `Create replacement Keycard with seed phrase` flow
- `Order new keycard` option

Closes: #7641
This commit is contained in:
Sale Djenic 2023-01-18 12:53:38 +01:00 committed by saledjenic
parent d3fffac4be
commit 3ca03bdd49
47 changed files with 451 additions and 92 deletions

View File

@ -47,6 +47,7 @@ logScope:
type
AppController* = ref object of RootObj
storeKeyPair: bool
syncWalletForReplacedKeycard: bool
statusFoundation: StatusFoundation
notificationsManager*: NotificationsManager
@ -108,6 +109,7 @@ proc userLoggedIn*(self: AppController): string
proc logout*(self: AppController)
proc finishAppLoading*(self: AppController)
proc storeKeyPairForNewKeycardUser*(self: AppController)
proc syncWalletAccountsOnLoginForReplacedKeycard*(self: AppController)
# Main Module Delegate Interface
proc mainDidLoad*(self: AppController)
@ -130,6 +132,7 @@ proc connect(self: AppController) =
proc newAppController*(statusFoundation: StatusFoundation): AppController =
result = AppController()
result.storeKeyPair = false
result.syncWalletForReplacedKeycard = false
result.statusFoundation = statusFoundation
# Preparing settings service to be exposed later as global QObject
@ -434,7 +437,8 @@ proc buildAndRegisterUserProfile(self: AppController) =
singletonInstance.engine.setRootContextProperty("userProfile", self.userProfileVariant)
if self.storeKeyPair and singletonInstance.userProfile.getIsKeycardUser():
if singletonInstance.userProfile.getIsKeycardUser():
if self.storeKeyPair:
let allAccounts = self.walletAccountService.fetchAccounts()
let defaultWalletAccounts = allAccounts.filter(a =>
a.walletType == WalletTypeDefaultStatusAccount and
@ -453,6 +457,34 @@ proc buildAndRegisterUserProfile(self: AppController) =
keyUid: loggedInAccount.keyUid)
let keystoreDir = self.accountsService.getKeyStoreDir()
discard self.walletAccountService.addMigratedKeyPair(keyPair, keystoreDir)
if self.syncWalletForReplacedKeycard:
let allAccounts = self.walletAccountService.fetchAccounts()
let accountsForLoggedInUser = allAccounts.filter(a => a.keyUid == loggedInAccount.keyUid)
var kpDto = KeyPairDto(keycardUid: "",
keycardName: displayName,
keycardLocked: false,
accountsAddresses: @[],
keyUid: loggedInAccount.keyUid)
var activeValidPathsToStoreToAKeycard: seq[string]
for acc in accountsForLoggedInUser:
activeValidPathsToStoreToAKeycard.add(acc.path)
kpDto.accountsAddresses.add(acc.address)
self.keycardService.startStoreMetadataFlow(displayName, self.startupModule.getPin(), activeValidPathsToStoreToAKeycard)
## sleep for 3 seconds, since that is more than enough to store metadata to a keycard, if the reader is still plugged in
## and the card is still inserted, otherwise we just skip that.
## At the moment we're not able to sync later keycard without metadata, cause such card doesn't return instance uid for
## loaded seed phrase, that's in the notes I am taking for discussion with keycard team. If they are able to provide
## instance uid for GetMetadata flow we will be able to use SIGNAL_SHARED_KEYCARD_MODULE_TRY_KEYCARD_SYNC signal for syncing
## otherwise we need to handle that way separatelly in `handleKeycardSyncing` of shared module
sleep(3000)
let (_, kcEvent) = self.keycardService.getLastReceivedKeycardData()
if kcEvent.instanceUID.len > 0:
kpDto.keycardUid = kcEvent.instanceUID
let keystoreDir = self.accountsService.getKeyStoreDir()
discard self.walletAccountService.addMigratedKeyPair(kpDto, keystoreDir)
proc storeKeyPairForNewKeycardUser*(self: AppController) =
self.storeKeyPair = true
proc syncWalletAccountsOnLoginForReplacedKeycard*(self: AppController) =
self.syncWalletForReplacedKeycard = true

View File

@ -1,2 +1,5 @@
## All app's labels/terms/warnings which need to be used/created on the backed side and displayed on the UI later
## should be stated here and translated via translations map in `Constants.qml` to be displayed well in user's language
const LOGIN_ACCOUNTS_LIST_ADD_NEW_USER* = "LOGIN-ACCOUNTS-LIST-ADD-NEW-USER"
const LOGIN_ACCOUNTS_LIST_ADD_EXISTING_USER* = "LOGIN-ACCOUNTS-LIST-ADD-EXISTING-USER"
const LOGIN_ACCOUNTS_LIST_LOST_KEYCARD* = "LOGIN-ACCOUNTS-LIST-LOST-KEYCARD"

View File

@ -22,7 +22,8 @@ method executePrePrimaryStateCommand*(self: FactoryResetSuccessState, controller
controller.runLoadAccountFlow(seedPhraseLength = 0, seedPhrase = "", pin = controller.getPinForKeycardCopy())
method executeCancelCommand*(self: FactoryResetSuccessState, controller: Controller) =
if self.flowType == FlowType.SetupNewKeycard or
if self.flowType == FlowType.FactoryReset or
self.flowType == FlowType.SetupNewKeycard or
self.flowType == FlowType.SetupNewKeycardNewSeedPhrase or
self.flowType == FlowType.SetupNewKeycardOldSeedPhrase or
self.flowType == FlowType.CreateCopyOfAKeycard:

View File

@ -379,7 +379,7 @@ proc getSelectedLoginAccount*(self: Controller): AccountDto =
if(acc.keyUid == self.tmpSelectedLoginAccountKeyUid):
return acc
proc keyUidMatch*(self: Controller, keyUid: string): bool =
proc keyUidMatchSelectedLoginAccount*(self: Controller, keyUid: string): bool =
return self.tmpSelectedLoginAccountKeyUid == keyUid
proc isSelectedLoginAccountKeycardAccount*(self: Controller): bool =
@ -391,7 +391,7 @@ proc setSelectedLoginAccount*(self: Controller, keyUid: string, isKeycardAccount
let selectedAccount = self.getSelectedLoginAccount()
singletonInstance.localAccountSettings.setFileName(selectedAccount.name)
proc isKeycardCreatedAccountSelectedOne*(self: Controller): bool =
proc isSelectedAccountAKeycardAccount*(self: Controller): bool =
let selectedAccount = self.getSelectedLoginAccount()
return selectedAccount.keycardPairing.len > 0
@ -413,7 +413,14 @@ proc login*(self: Controller) =
if(error.len > 0):
self.delegate.emitAccountLoginError(error)
proc loginAccountKeycard*(self: Controller) =
proc loginAccountKeycard*(self: Controller, storeToKeychain = false, syncWalletAfterLogin = false) =
if syncWalletAfterLogin:
self.delegate.syncWalletAccountsOnLoginForReplacedKeycard()
if storeToKeychain:
## storing not now, user will be asked to store the pin once he is logged in
singletonInstance.localAccountSettings.setStoreToKeychainValue(LS_VALUE_NOT_NOW)
else:
singletonInstance.localAccountSettings.setStoreToKeychainValue(LS_VALUE_NEVER)
self.delegate.moveToLoadingAppState()
let error = self.accountsService.loginAccountKeycard(self.tmpKeycardEvent)
if(error.len > 0):

View File

@ -1,9 +1,11 @@
type
BiometricsState* = ref object of State
storeToKeychain: bool
proc newBiometricsState*(flowType: FlowType, backState: State): BiometricsState =
result = BiometricsState()
result.setup(flowType, StateType.Biometrics, backState)
result.storeToKeychain = false
proc delete*(self: BiometricsState) =
self.State.delete
@ -28,6 +30,9 @@ method executePrimaryCommand*(self: BiometricsState, controller: Controller) =
controller.storeKeycardAccountAndLogin(storeToKeychain)
elif self.flowType == FlowType.FirstRunOldUserKeycardImport:
controller.setupKeycardAccount(storeToKeychain)
elif self.flowType == FlowType.LostKeycardReplacement:
self.storeToKeychain = storeToKeychain
controller.startLoginFlowAutomatically(controller.getPin())
method executeSecondaryCommand*(self: BiometricsState, controller: Controller) =
let storeToKeychain = false # false, cause we don't have keychain support for other than mac os
@ -45,3 +50,14 @@ method executeSecondaryCommand*(self: BiometricsState, controller: Controller) =
controller.storeKeycardAccountAndLogin(storeToKeychain)
elif self.flowType == FlowType.FirstRunOldUserKeycardImport:
controller.setupKeycardAccount(storeToKeychain)
elif self.flowType == FlowType.LostKeycardReplacement:
self.storeToKeychain = storeToKeychain
controller.startLoginFlowAutomatically(controller.getPin())
method resolveKeycardNextState*(self: BiometricsState, keycardFlowType: string, keycardEvent: KeycardEvent,
controller: Controller): State =
if self.flowType == FlowType.LostKeycardReplacement:
if keycardFlowType == ResponseTypeValueKeycardFlowResult and
keycardEvent.error.len == 0:
controller.setKeycardEvent(keycardEvent)
controller.loginAccountKeycard(self.storeToKeychain, syncWalletAfterLogin = true)

View File

@ -11,7 +11,8 @@ proc delete*(self: KeycardInsertKeycardState) =
method executeBackCommand*(self: KeycardInsertKeycardState, controller: Controller) =
if self.flowType == FlowType.FirstRunNewUserNewKeycardKeys or
self.flowType == FlowType.FirstRunNewUserImportSeedPhraseIntoKeycard or
self.flowType == FlowType.FirstRunOldUserKeycardImport:
self.flowType == FlowType.FirstRunOldUserKeycardImport or
self.flowType == FlowType.LostKeycardReplacement:
controller.cancelCurrentFlow()
method resolveKeycardNextState*(self: KeycardInsertKeycardState, keycardFlowType: string, keycardEvent: KeycardEvent,

View File

@ -11,7 +11,8 @@ proc delete*(self: KeycardInsertedKeycardState) =
method executeBackCommand*(self: KeycardInsertedKeycardState, controller: Controller) =
if self.flowType == FlowType.FirstRunNewUserNewKeycardKeys or
self.flowType == FlowType.FirstRunNewUserImportSeedPhraseIntoKeycard or
self.flowType == FlowType.FirstRunOldUserKeycardImport:
self.flowType == FlowType.FirstRunOldUserKeycardImport or
self.flowType == FlowType.LostKeycardReplacement:
controller.cancelCurrentFlow()
method getNextPrimaryState*(self: KeycardInsertedKeycardState, controller: Controller): State =

View File

@ -8,6 +8,10 @@ proc newKeycardLockedState*(flowType: FlowType, backState: State): KeycardLocked
proc delete*(self: KeycardLockedState) =
self.State.delete
method executeBackCommand*(self: KeycardLockedState, controller: Controller) =
if self.flowType == FlowType.LostKeycardReplacement:
controller.cancelCurrentFlow()
method executePrimaryCommand*(self: KeycardLockedState, controller: Controller) =
if self.flowType == FlowType.FirstRunNewUserNewKeycardKeys:
controller.runFactoryResetPopup()

View File

@ -8,9 +8,14 @@ proc newKeycardNotEmptyState*(flowType: FlowType, backState: State): KeycardNotE
proc delete*(self: KeycardNotEmptyState) =
self.State.delete
method executeBackCommand*(self: KeycardNotEmptyState, controller: Controller) =
if self.flowType == FlowType.LostKeycardReplacement:
controller.cancelCurrentFlow()
method executePrimaryCommand*(self: KeycardNotEmptyState, controller: Controller) =
if self.flowType == FlowType.FirstRunNewUserNewKeycardKeys or
self.flowType == FlowType.FirstRunNewUserImportSeedPhraseIntoKeycard:
self.flowType == FlowType.FirstRunNewUserImportSeedPhraseIntoKeycard or
self.flowType == FlowType.LostKeycardReplacement:
controller.runFactoryResetPopup()
method executeSecondaryCommand*(self: KeycardNotEmptyState, controller: Controller) =

View File

@ -7,3 +7,7 @@ proc newKeycardNotKeycardState*(flowType: FlowType, backState: State): KeycardNo
proc delete*(self: KeycardNotKeycardState) =
self.State.delete
method executeBackCommand*(self: KeycardNotKeycardState, controller: Controller) =
if self.flowType == FlowType.LostKeycardReplacement:
controller.cancelCurrentFlow()

View File

@ -30,11 +30,27 @@ method getNextPrimaryState*(self: KeycardPinSetState, controller: Controller): S
controller.loginAccountKeycard()
return nil
return createState(StateType.KeycardWrongPuk, self.flowType, self.getBackState)
if self.flowType == FlowType.LostKeycardReplacement:
if not defined(macosx):
return nil
return createState(StateType.Biometrics, self.flowType, self.getBackState)
method executePrimaryCommand*(self: KeycardPinSetState, controller: Controller) =
if controller.getValidPuk() and not defined(macosx):
controller.setupKeycardAccount(false)
if defined(macosx):
return
# if controller.getValidPuk():
# controller.setupKeycardAccount(false)
# return
if self.flowType == FlowType.AppLogin:
if controller.getRecoverUsingSeedPhraseWhileLogin():
controller.startLoginFlowAutomatically(controller.getPin())
if self.flowType == FlowType.LostKeycardReplacement:
controller.startLoginFlowAutomatically(controller.getPin())
method resolveKeycardNextState*(self: KeycardPinSetState, keycardFlowType: string, keycardEvent: KeycardEvent,
controller: Controller): State =
if self.flowType == FlowType.LostKeycardReplacement:
if keycardFlowType == ResponseTypeValueKeycardFlowResult and
keycardEvent.error.len == 0:
controller.setKeycardEvent(keycardEvent)
controller.loginAccountKeycard()

View File

@ -11,7 +11,8 @@ proc delete*(self: KeycardPluginReaderState) =
method executeBackCommand*(self: KeycardPluginReaderState, controller: Controller) =
if self.flowType == FlowType.FirstRunNewUserNewKeycardKeys or
self.flowType == FlowType.FirstRunNewUserImportSeedPhraseIntoKeycard or
self.flowType == FlowType.FirstRunOldUserKeycardImport:
self.flowType == FlowType.FirstRunOldUserKeycardImport or
self.flowType == FlowType.LostKeycardReplacement:
controller.cancelCurrentFlow()
method resolveKeycardNextState*(self: KeycardPluginReaderState, keycardFlowType: string, keycardEvent: KeycardEvent,

View File

@ -11,7 +11,8 @@ proc delete*(self: KeycardReadingKeycardState) =
method executeBackCommand*(self: KeycardReadingKeycardState, controller: Controller) =
if self.flowType == FlowType.FirstRunNewUserNewKeycardKeys or
self.flowType == FlowType.FirstRunNewUserImportSeedPhraseIntoKeycard or
self.flowType == FlowType.FirstRunOldUserKeycardImport:
self.flowType == FlowType.FirstRunOldUserKeycardImport or
self.flowType == FlowType.LostKeycardReplacement:
controller.cancelCurrentFlow()
method getNextPrimaryState*(self: KeycardReadingKeycardState, controller: Controller): State =

View File

@ -11,7 +11,8 @@ proc delete*(self: KeycardRecognizedKeycardState) =
method executeBackCommand*(self: KeycardRecognizedKeycardState, controller: Controller) =
if self.flowType == FlowType.FirstRunNewUserNewKeycardKeys or
self.flowType == FlowType.FirstRunNewUserImportSeedPhraseIntoKeycard or
self.flowType == FlowType.FirstRunOldUserKeycardImport:
self.flowType == FlowType.FirstRunOldUserKeycardImport or
self.flowType == FlowType.LostKeycardReplacement:
controller.cancelCurrentFlow()
method getNextPrimaryState*(self: KeycardRecognizedKeycardState, controller: Controller): State =
@ -21,3 +22,5 @@ method getNextPrimaryState*(self: KeycardRecognizedKeycardState, controller: Con
return createState(StateType.UserProfileEnterSeedPhrase, self.flowType, self.getBackState)
if self.flowType == FlowType.FirstRunOldUserKeycardImport:
return createState(StateType.KeycardEnterPin, self.flowType, self.getBackState)
if self.flowType == FlowType.LostKeycardReplacement:
return createState(StateType.UserProfileEnterSeedPhrase, self.flowType, self.getBackState)

View File

@ -15,14 +15,15 @@ method executeBackCommand*(self: KeycardRepeatPinState, controller: Controller)
method executePrimaryCommand*(self: KeycardRepeatPinState, controller: Controller) =
if not controller.getPinMatch():
return
if self.flowType == FlowType.FirstRunNewUserNewKeycardKeys:
if self.flowType == FlowType.FirstRunNewUserNewKeycardKeys or
self.flowType == FlowType.FirstRunNewUserImportSeedPhraseIntoKeycard:
controller.storePinToKeycard(controller.getPin(), controller.generateRandomPUK())
elif self.flowType == FlowType.FirstRunNewUserImportSeedPhraseIntoKeycard:
controller.storePinToKeycard(controller.getPin(), controller.generateRandomPUK())
elif self.flowType == FlowType.FirstRunOldUserKeycardImport:
controller.storePinToKeycard(controller.getPin(), puk = "")
elif self.flowType == FlowType.AppLogin:
return
if self.flowType == FlowType.FirstRunOldUserKeycardImport or
self.flowType == FlowType.AppLogin or
self.flowType == FlowType.LostKeycardReplacement:
controller.storePinToKeycard(controller.getPin(), puk = "")
return
method resolveKeycardNextState*(self: KeycardRepeatPinState, keycardFlowType: string, keycardEvent: KeycardEvent,
controller: Controller): State =
@ -66,3 +67,9 @@ method resolveKeycardNextState*(self: KeycardRepeatPinState, keycardFlowType: st
controller.setKeycardEvent(keycardEvent)
controller.setPukValid(true)
return createState(StateType.KeycardPinSet, self.flowType, self.getBackState)
if self.flowType == FlowType.LostKeycardReplacement:
if keycardFlowType == ResponseTypeValueKeycardFlowResult:
controller.setKeycardEvent(keycardEvent)
controller.setPukValid(true)
let backState = findBackStateWithTargetedStateType(self, StateType.LostKeycardOptions)
return createState(StateType.KeycardPinSet, self.flowType, backState)

View File

@ -25,6 +25,11 @@ method getNextQuaternaryState*(self: LoginKeycardEmptyState, controller: Control
controller.cancelCurrentFlow()
return createState(StateType.WelcomeOldStatusUser, self.flowType, self)
method getNextQuinaryState*(self: LoginKeycardEmptyState, controller: Controller): State =
if self.flowType == FlowType.AppLogin:
controller.cancelCurrentFlow()
return createState(StateType.LostKeycardOptions, self.flowType, self)
method resolveKeycardNextState*(self: LoginKeycardEmptyState, keycardFlowType: string, keycardEvent: KeycardEvent,
controller: Controller): State =
return ensureReaderAndCardPresenceAndResolveNextLoginState(self, keycardFlowType, keycardEvent, controller)

View File

@ -22,3 +22,8 @@ method getNextQuaternaryState*(self: LoginKeycardEnterPasswordState, controller:
if self.flowType == FlowType.AppLogin:
controller.cancelCurrentFlow()
return createState(StateType.WelcomeOldStatusUser, self.flowType, self)
method getNextQuinaryState*(self: LoginKeycardEnterPasswordState, controller: Controller): State =
if self.flowType == FlowType.AppLogin:
controller.cancelCurrentFlow()
return createState(StateType.LostKeycardOptions, self.flowType, self)

View File

@ -24,6 +24,11 @@ method getNextQuaternaryState*(self: LoginKeycardEnterPinState, controller: Cont
controller.cancelCurrentFlow()
return createState(StateType.WelcomeOldStatusUser, self.flowType, self)
method getNextQuinaryState*(self: LoginKeycardEnterPinState, controller: Controller): State =
if self.flowType == FlowType.AppLogin:
controller.cancelCurrentFlow()
return createState(StateType.LostKeycardOptions, self.flowType, self)
method resolveKeycardNextState*(self: LoginKeycardEnterPinState, keycardFlowType: string, keycardEvent: KeycardEvent,
controller: Controller): State =
let state = ensureReaderAndCardPresenceLogin(self, keycardFlowType, keycardEvent, controller)

View File

@ -18,6 +18,11 @@ method getNextQuaternaryState*(self: LoginKeycardInsertKeycardState, controller:
controller.cancelCurrentFlow()
return createState(StateType.WelcomeOldStatusUser, self.flowType, self)
method getNextQuinaryState*(self: LoginKeycardInsertKeycardState, controller: Controller): State =
if self.flowType == FlowType.AppLogin:
controller.cancelCurrentFlow()
return createState(StateType.LostKeycardOptions, self.flowType, self)
method resolveKeycardNextState*(self: LoginKeycardInsertKeycardState, keycardFlowType: string, keycardEvent: KeycardEvent,
controller: Controller): State =
let state = ensureReaderAndCardPresenceLogin(self, keycardFlowType, keycardEvent, controller)

View File

@ -9,7 +9,7 @@ proc delete*(self: LoginKeycardMaxPairingSlotsReachedState) =
self.State.delete
method executeBackCommand*(self: LoginKeycardMaxPairingSlotsReachedState, controller: Controller) =
if self.flowType == FlowType.AppLogin and controller.isKeycardCreatedAccountSelectedOne():
if self.flowType == FlowType.AppLogin and controller.isSelectedAccountAKeycardAccount():
controller.runLoginFlow()
method getNextPrimaryState*(self: LoginKeycardMaxPairingSlotsReachedState, controller: Controller): State =
@ -27,6 +27,11 @@ method getNextQuaternaryState*(self: LoginKeycardMaxPairingSlotsReachedState, co
controller.cancelCurrentFlow()
return createState(StateType.WelcomeOldStatusUser, self.flowType, self)
method getNextQuinaryState*(self: LoginKeycardMaxPairingSlotsReachedState, controller: Controller): State =
if self.flowType == FlowType.AppLogin:
controller.cancelCurrentFlow()
return createState(StateType.LostKeycardOptions, self.flowType, self)
method resolveKeycardNextState*(self: LoginKeycardMaxPairingSlotsReachedState, keycardFlowType: string, keycardEvent: KeycardEvent,
controller: Controller): State =
return ensureReaderAndCardPresenceAndResolveNextLoginState(self, keycardFlowType, keycardEvent, controller)

View File

@ -9,7 +9,7 @@ proc delete*(self: LoginKeycardMaxPinRetriesReachedState) =
self.State.delete
method executeBackCommand*(self: LoginKeycardMaxPinRetriesReachedState, controller: Controller) =
if self.flowType == FlowType.AppLogin and controller.isKeycardCreatedAccountSelectedOne():
if self.flowType == FlowType.AppLogin and controller.isSelectedAccountAKeycardAccount():
controller.runLoginFlow()
method getNextPrimaryState*(self: LoginKeycardMaxPinRetriesReachedState, controller: Controller): State =
@ -26,6 +26,11 @@ method getNextQuaternaryState*(self: LoginKeycardMaxPinRetriesReachedState, cont
controller.cancelCurrentFlow()
return createState(StateType.WelcomeOldStatusUser, self.flowType, self)
method getNextQuinaryState*(self: LoginKeycardMaxPinRetriesReachedState, controller: Controller): State =
if self.flowType == FlowType.AppLogin:
controller.cancelCurrentFlow()
return createState(StateType.LostKeycardOptions, self.flowType, self)
method resolveKeycardNextState*(self: LoginKeycardMaxPinRetriesReachedState, keycardFlowType: string, keycardEvent: KeycardEvent,
controller: Controller): State =
return ensureReaderAndCardPresenceAndResolveNextLoginState(self, keycardFlowType, keycardEvent, controller)

View File

@ -9,7 +9,7 @@ proc delete*(self: LoginKeycardMaxPukRetriesReachedState) =
self.State.delete
method executeBackCommand*(self: LoginKeycardMaxPukRetriesReachedState, controller: Controller) =
if self.flowType == FlowType.AppLogin and controller.isKeycardCreatedAccountSelectedOne():
if self.flowType == FlowType.AppLogin and controller.isSelectedAccountAKeycardAccount():
controller.runLoginFlow()
method getNextPrimaryState*(self: LoginKeycardMaxPukRetriesReachedState, controller: Controller): State =
@ -27,6 +27,11 @@ method getNextQuaternaryState*(self: LoginKeycardMaxPukRetriesReachedState, cont
controller.cancelCurrentFlow()
return createState(StateType.WelcomeOldStatusUser, self.flowType, self)
method getNextQuinaryState*(self: LoginKeycardMaxPukRetriesReachedState, controller: Controller): State =
if self.flowType == FlowType.AppLogin:
controller.cancelCurrentFlow()
return createState(StateType.LostKeycardOptions, self.flowType, self)
method resolveKeycardNextState*(self: LoginKeycardMaxPukRetriesReachedState, keycardFlowType: string, keycardEvent: KeycardEvent,
controller: Controller): State =
return ensureReaderAndCardPresenceAndResolveNextLoginState(self, keycardFlowType, keycardEvent, controller)

View File

@ -24,6 +24,11 @@ method getNextQuaternaryState*(self: LoginKeycardWrongKeycardState, controller:
controller.cancelCurrentFlow()
return createState(StateType.WelcomeOldStatusUser, self.flowType, self)
method getNextQuinaryState*(self: LoginKeycardWrongKeycardState, controller: Controller): State =
if self.flowType == FlowType.AppLogin:
controller.cancelCurrentFlow()
return createState(StateType.LostKeycardOptions, self.flowType, self)
method resolveKeycardNextState*(self: LoginKeycardWrongKeycardState, keycardFlowType: string, keycardEvent: KeycardEvent,
controller: Controller): State =
return ensureReaderAndCardPresenceAndResolveNextLoginState(self, keycardFlowType, keycardEvent, controller)

View File

@ -26,6 +26,11 @@ method getNextQuaternaryState*(self: LoginKeycardWrongPinState, controller: Cont
controller.cancelCurrentFlow()
return createState(StateType.WelcomeOldStatusUser, self.flowType, self)
method getNextQuinaryState*(self: LoginKeycardWrongPinState, controller: Controller): State =
if self.flowType == FlowType.AppLogin:
controller.cancelCurrentFlow()
return createState(StateType.LostKeycardOptions, self.flowType, self)
method resolveKeycardNextState*(self: LoginKeycardWrongPinState, keycardFlowType: string, keycardEvent: KeycardEvent,
controller: Controller): State =
let state = ensureReaderAndCardPresenceLogin(self, keycardFlowType, keycardEvent, controller)

View File

@ -22,6 +22,11 @@ method getNextQuaternaryState*(self: LoginNotKeycardState, controller: Controlle
controller.cancelCurrentFlow()
return createState(StateType.WelcomeOldStatusUser, self.flowType, self)
method getNextQuinaryState*(self: LoginNotKeycardState, controller: Controller): State =
if self.flowType == FlowType.AppLogin:
controller.cancelCurrentFlow()
return createState(StateType.LostKeycardOptions, self.flowType, self)
method resolveKeycardNextState*(self: LoginNotKeycardState, keycardFlowType: string, keycardEvent: KeycardEvent,
controller: Controller): State =
return ensureReaderAndCardPresenceAndResolveNextLoginState(self, keycardFlowType, keycardEvent, controller)

View File

@ -18,6 +18,11 @@ method getNextQuaternaryState*(self: LoginPluginState, controller: Controller):
controller.cancelCurrentFlow()
return createState(StateType.WelcomeOldStatusUser, self.flowType, self)
method getNextQuinaryState*(self: LoginPluginState, controller: Controller): State =
if self.flowType == FlowType.AppLogin:
controller.cancelCurrentFlow()
return createState(StateType.LostKeycardOptions, self.flowType, self)
method resolveKeycardNextState*(self: LoginPluginState, keycardFlowType: string, keycardEvent: KeycardEvent,
controller: Controller): State =
return ensureReaderAndCardPresenceAndResolveNextLoginState(self, keycardFlowType, keycardEvent, controller)

View File

@ -34,6 +34,11 @@ method getNextQuaternaryState*(self: LoginState, controller: Controller): State
controller.cancelCurrentFlow()
return createState(StateType.WelcomeOldStatusUser, self.flowType, self)
method getNextQuinaryState*(self: LoginState, controller: Controller): State =
if self.flowType == FlowType.AppLogin:
controller.cancelCurrentFlow()
return createState(StateType.LostKeycardOptions, self.flowType, self)
method resolveKeycardNextState*(self: LoginState, keycardFlowType: string, keycardEvent: KeycardEvent,
controller: Controller): State =
return ensureReaderAndCardPresenceAndResolveNextLoginState(self, keycardFlowType, keycardEvent, controller)

View File

@ -0,0 +1,27 @@
type
LostKeycardOptionsState* = ref object of State
proc newLostKeycardOptionsState*(flowType: FlowType, backState: State): LostKeycardOptionsState =
result = LostKeycardOptionsState()
result.setup(flowType, StateType.LostKeycardOptions, backState)
proc delete*(self: LostKeycardOptionsState) =
self.State.delete
method executeBackCommand*(self: LostKeycardOptionsState, controller: Controller) =
if controller.isSelectedAccountAKeycardAccount() and
(self.flowType == FlowType.LostKeycardReplacement or self.flowType == FlowType.AppLogin):
controller.cancelCurrentFlow()
controller.runLoginFlow()
method executePrimaryCommand*(self: LostKeycardOptionsState, controller: Controller) =
if controller.isSelectedAccountAKeycardAccount():
self.setFlowType(FlowType.LostKeycardReplacement)
controller.runLoadAccountFlow()
method executeSecondaryCommand*(self: LostKeycardOptionsState, controller: Controller) =
echo "TODO: start using account without keycard..."
method resolveKeycardNextState*(self: LostKeycardOptionsState, keycardFlowType: string, keycardEvent: KeycardEvent,
controller: Controller): State =
return ensureReaderAndCardPresenceAndResolveNextOnboardingState(self, keycardFlowType, keycardEvent, controller)

View File

@ -13,6 +13,7 @@ type FlowType* {.pure.} = enum
FirstRunOldUserKeycardImport = "FirstRunOldUserKeycardImport"
FirstRunOldUserImportSeedPhrase = "FirstRunOldUserImportSeedPhrase"
AppLogin = "AppLogin"
LostKeycardReplacement = "LostKeycardReplacement"
type StateType* {.pure.} = enum
NoState = "NoState"
@ -72,6 +73,7 @@ type StateType* {.pure.} = enum
ProfileFetchingSuccess = "ProfileFetchingSuccess"
ProfileFetchingTimeout = "ProfileFetchingTimeout"
ProfileFetchingAnnouncement = "ProfileFetchingAnnouncement"
LostKeycardOptions = "LostKeycardOptions"
## This is the base class for all state we may have in onboarding/login flow.

View File

@ -15,6 +15,7 @@ logScope:
# Forward declaration
# General section
proc createState*(stateToBeCreated: StateType, flowType: FlowType, backState: State): State
proc findBackStateWithTargetedStateType*(currentState: State, targetedStateType: StateType): State
# Resolve state section
proc ensureReaderAndCardPresenceOnboarding*(state: State, keycardFlowType: string, keycardEvent: KeycardEvent, controller: Controller): State
proc ensureReaderAndCardPresenceLogin*(state: State, keycardFlowType: string, keycardEvent: KeycardEvent, controller: Controller): State
@ -77,6 +78,7 @@ include profile_fetching_success_state
include profile_fetching_timeout_state
include profile_fetching_announcement_state
include recover_old_user_state
include lost_keycard_options_state
include state_factory_general_implementation
include state_factory_state_onboarding_implementation

View File

@ -111,5 +111,17 @@ proc createState*(stateToBeCreated: StateType, flowType: FlowType, backState: St
return newProfileFetchingAnnouncementState(flowType, backState)
if stateToBeCreated == StateType.RecoverOldUser:
return newRecoverOldUserState(flowType, backState)
if stateToBeCreated == StateType.LostKeycardOptions:
return newLostKeycardOptionsState(flowType, backState)
error "No implementation available for state ", state=stateToBeCreated
proc findBackStateWithTargetedStateType*(currentState: State, targetedStateType: StateType): State =
if currentState.isNil:
return nil
var state = currentState
while not state.isNil:
if state.stateType == targetedStateType:
return state
state = state.getBackState
return nil

View File

@ -27,7 +27,7 @@ proc ensureReaderAndCardPresenceAndResolveNextLoginState*(state: State, keycardF
return createState(StateType.LoginKeycardPinVerified, state.flowType, nil)
if keycardFlowType == ResponseTypeValueEnterPIN:
if keycardEvent.error.len == 0:
if not controller.keyUidMatch(keycardEvent.keyUid):
if not controller.keyUidMatchSelectedLoginAccount(keycardEvent.keyUid):
return createState(StateType.LoginKeycardWrongKeycard, state.flowType, nil)
return createState(StateType.LoginKeycardRecognizedKeycard, state.flowType, nil)
if keycardEvent.error.len > 0:

View File

@ -11,10 +11,14 @@ proc ensureReaderAndCardPresenceOnboarding*(state: State, keycardFlowType: strin
keycardEvent.error == ErrorConnection:
if state.stateType == StateType.KeycardInsertKeycard:
return nil
if state.stateType == StateType.KeycardPluginReader:
return createState(StateType.KeycardInsertKeycard, state.flowType, state.getBackState)
return createState(StateType.KeycardInsertKeycard, state.flowType, state)
if keycardFlowType == ResponseTypeValueCardInserted:
controller.setKeycardData(updatePredefinedKeycardData(controller.getKeycardData(), PredefinedKeycardData.WronglyInsertedCard, add = false))
if state.stateType == StateType.KeycardInsertKeycard:
return createState(StateType.KeycardInsertedKeycard, state.flowType, state.getBackState)
return createState(StateType.KeycardInsertedKeycard, state.flowType, state)
proc ensureReaderAndCardPresenceAndResolveNextOnboardingState*(state: State, keycardFlowType: string, keycardEvent: KeycardEvent, controller: Controller): State =
let ensureState = ensureReaderAndCardPresenceOnboarding(state, keycardFlowType, keycardEvent, controller)
@ -115,3 +119,28 @@ proc ensureReaderAndCardPresenceAndResolveNextOnboardingState*(state: State, key
keycardEvent.error.len > 0 and
keycardEvent.error == ErrorRequireInit:
return createState(StateType.KeycardCreatePin, state.flowType, state.getBackState)
if state.flowType == FlowType.LostKeycardReplacement:
var backState = state.getBackState
if state.stateType == StateType.LostKeycardOptions:
backState = state
if keycardFlowType == ResponseTypeValueEnterNewPIN and
keycardEvent.error.len > 0 and
keycardEvent.error == ErrorRequireInit:
if state.stateType == StateType.UserProfileEnterSeedPhrase:
return createState(StateType.KeycardCreatePin, state.flowType, state)
return createState(StateType.KeycardRecognizedKeycard, state.flowType, backState)
if keycardFlowType == ResponseTypeValueEnterPIN and
keycardEvent.error.len == 0:
return createState(StateType.KeycardNotEmpty, state.flowType, backState)
if keycardFlowType == ResponseTypeValueSwapCard and
keycardEvent.error.len > 0:
if keycardEvent.error == ErrorNotAKeycard:
return createState(StateType.KeycardNotKeycard, state.flowType, backState)
if keycardEvent.error == RequestParamFreeSlots:
return createState(StateType.KeycardLocked, state.flowType, backState)
if keycardEvent.error == RequestParamPUKRetries:
return createState(StateType.KeycardLocked, state.flowType, backState)
if keycardEvent.error == ErrorHasKeys:
return createState(StateType.KeycardNotEmpty, state.flowType, backState)

View File

@ -1,13 +1,13 @@
type
UserProfileEnterSeedPhraseState* = ref object of State
successfulImport: bool
correctKeycard: bool
enteredMnemonicMatchTargetedKeyUid: bool
proc newUserProfileEnterSeedPhraseState*(flowType: FlowType, backState: State): UserProfileEnterSeedPhraseState =
result = UserProfileEnterSeedPhraseState()
result.setup(flowType, StateType.UserProfileEnterSeedPhrase, backState)
result.successfulImport = false
result.correctKeycard = false
result.enteredMnemonicMatchTargetedKeyUid = false
proc delete*(self: UserProfileEnterSeedPhraseState) =
self.State.delete
@ -22,7 +22,7 @@ method getNextPrimaryState*(self: UserProfileEnterSeedPhraseState, controller: C
if self.flowType == FlowType.FirstRunNewUserImportSeedPhrase:
return createState(StateType.UserProfileCreate, self.flowType, self)
if self.flowType == FlowType.FirstRunOldUserKeycardImport:
if not self.correctKeycard:
if not self.enteredMnemonicMatchTargetedKeyUid:
return createState(StateType.KeycardWrongKeycard, self.flowType, nil)
if self.flowType == FlowType.FirstRunOldUserImportSeedPhrase:
return createState(StateType.UserProfileCreatePassword, self.flowType, self)
@ -44,10 +44,18 @@ method executePrimaryCommand*(self: UserProfileEnterSeedPhraseState, controller:
if self.flowType == FlowType.FirstRunNewUserImportSeedPhraseIntoKeycard:
controller.storeSeedPhraseToKeycard(controller.getSeedPhraseLength(), controller.getSeedPhrase())
if self.flowType == FlowType.FirstRunOldUserKeycardImport:
self.correctKeycard = controller.getKeyUidForSeedPhrase(controller.getSeedPhrase()) == controller.getKeyUid()
if self.correctKeycard:
self.enteredMnemonicMatchTargetedKeyUid = controller.getKeyUidForSeedPhrase(controller.getSeedPhrase()) == controller.getKeyUid()
if self.enteredMnemonicMatchTargetedKeyUid:
controller.runLoadAccountFlow(controller.getSeedPhraseLength(), controller.getSeedPhrase(), pin = "", puk = "",
factoryReset = true)
if self.flowType == FlowType.LostKeycardReplacement:
controller.setKeycardData(updatePredefinedKeycardData(controller.getKeycardData(), PredefinedKeycardData.WrongSeedPhrase, add = false))
let keyUid = controller.getKeyUidForSeedPhrase(controller.getSeedPhrase())
self.enteredMnemonicMatchTargetedKeyUid = controller.keyUidMatchSelectedLoginAccount(keyUid)
if self.enteredMnemonicMatchTargetedKeyUid:
controller.storeSeedPhraseToKeycard(controller.getSeedPhraseLength(), controller.getSeedPhrase())
else:
controller.setKeycardData(updatePredefinedKeycardData(controller.getKeycardData(), PredefinedKeycardData.WrongSeedPhrase, add = true))
method resolveKeycardNextState*(self: UserProfileEnterSeedPhraseState, keycardFlowType: string, keycardEvent: KeycardEvent,
controller: Controller): State =

View File

@ -11,7 +11,7 @@ proc delete*(self: WelcomeStateNewUser) =
method executeBackCommand*(self: WelcomeStateNewUser, controller: Controller) =
if self.flowType == FlowType.FirstRunNewUserNewKeycardKeys:
controller.cancelCurrentFlow()
elif self.flowType == FlowType.AppLogin and controller.isKeycardCreatedAccountSelectedOne():
elif self.flowType == FlowType.AppLogin and controller.isSelectedAccountAKeycardAccount():
controller.runLoginFlow()
method executeSecondaryCommand*(self: WelcomeStateNewUser, controller: Controller) =

View File

@ -9,7 +9,7 @@ proc delete*(self: WelcomeStateOldUser) =
self.State.delete
method executeBackCommand*(self: WelcomeStateOldUser, controller: Controller) =
if self.flowType == FlowType.AppLogin and controller.isKeycardCreatedAccountSelectedOne():
if self.flowType == FlowType.AppLogin and controller.isSelectedAccountAKeycardAccount():
controller.runLoginFlow()
method getNextPrimaryState*(self: WelcomeStateOldUser, controller: Controller): State =

View File

@ -143,6 +143,9 @@ method runFactoryResetPopup*(self: AccessInterface) {.base.} =
method storeKeyPairForNewKeycardUser*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method syncWalletAccountsOnLoginForReplacedKeycard*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method checkForStoringPasswordToKeychain*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
@ -165,3 +168,4 @@ type
c.userLoggedIn()
c.finishAppLoading()
c.storeKeyPairForNewKeycardUser()
c.syncWalletAccountsOnLoginForReplacedKeycard()

View File

@ -113,6 +113,8 @@ method load*[T](self: Module[T]) =
thumbnailImage = "", largeImage = "", keyUid = ""))
items.add(login_acc_item.initItem(order = items.len, name = atc.LOGIN_ACCOUNTS_LIST_ADD_EXISTING_USER, icon = "wallet",
thumbnailImage = "", largeImage = "", keyUid = ""))
items.add(login_acc_item.initItem(order = items.len, name = atc.LOGIN_ACCOUNTS_LIST_LOST_KEYCARD, icon = "keycard",
thumbnailImage = "", largeImage = "", keyUid = ""))
self.view.setLoginAccountsModelItems(items)
self.setSelectedLoginAccount(items[0])
self.delegate.startupDidLoad()
@ -451,11 +453,24 @@ method onSharedKeycarModuleFlowTerminated*[T](self: Module[T], lastStepInTheCurr
self.keycardSharedModule.delete
self.keycardSharedModule = nil
if lastStepInTheCurrentFlow:
self.controller.cleanTmpData()
self.view.setCurrentStartupState(newWelcomeState(FlowType.General, nil))
# self.controller.cleanTmpData()
let currStateObj = self.view.currentStartupStateObj()
if currStateObj.isNil:
error "cannot resolve current state for onboarding/login flow continuation"
return
if currStateObj.flowType() == FlowType.LostKeycardReplacement:
let newState = currStateObj.getBackState()
if newState.isNil:
error "cannot resolve new state for onboarding/login flow continuation after shared flow is terminated"
return
self.view.setCurrentStartupState(newState)
debug "new state for onboarding/login flow continuation after shared flow is terminated", setCurrFlow=newState.flowType(), newCurrState=newState.stateType()
method storeKeyPairForNewKeycardUser*[T](self: Module[T]) =
self.delegate.storeKeyPairForNewKeycardUser()
method syncWalletAccountsOnLoginForReplacedKeycard*[T](self: Module[T]) =
self.delegate.syncWalletAccountsOnLoginForReplacedKeycard()
method checkForStoringPasswordToKeychain*[T](self: Module[T]) =
self.controller.checkForStoringPasswordToKeychain()

View File

@ -42,6 +42,7 @@ OnboardingBasePage {
case Constants.startupState.userProfileImportSeedPhrase:
case Constants.startupState.profileFetchingAnnouncement:
case Constants.startupState.userProfileCreateSameChatKey:
case Constants.startupState.lostKeycardOptions:
return keysMainViewComponent
case Constants.startupState.userProfileCreate:

View File

@ -238,10 +238,20 @@ Item {
}
}
StatusBaseText {
Row {
id: button3
property string text: ""
property string link: ""
property bool useLinkForButton: false
Layout.alignment: Qt.AlignHCenter
visible: text !== ""
visible: button3.text !== ""
spacing: 0
padding: 0
StatusBaseText {
text: button3.text
color: Theme.palette.primaryColor1
font.pixelSize: Constants.onboarding.fontSize3
MouseArea {
@ -255,11 +265,28 @@ Item {
parent.font.underline = false
}
onClicked: {
if (button3.useLinkForButton) {
Qt.openUrlExternally(button3.link)
return
}
root.startupStore.doTertiaryAction()
}
}
}
StatusFlatRoundButton {
visible: button3.link !== ""
height: 20
width: 20
icon.name: "external"
icon.width: 16
icon.height: 16
onClicked: {
Qt.openUrlExternally(button3.link)
}
}
}
Item {
Layout.fillWidth: true
Layout.fillHeight: true
@ -451,6 +478,39 @@ Item {
target: button3
text: ""
}
},
State {
name: Constants.startupState.lostKeycardOptions
when: root.startupStore.currentStartupState.stateType === Constants.startupState.lostKeycardOptions
PropertyChanges {
target: keysImg
Layout.preferredWidth: Constants.keycard.general.imageWidth
Layout.preferredHeight: Constants.keycard.general.imageHeight
source: Style.png("keycard/keycard-new")
}
PropertyChanges {
target: txtTitle
text: ""
}
PropertyChanges {
target: txtDesc
text: qsTr("Sorry to hear youve lost your Keycard, you have 3 options")
height: Constants.onboarding.loginInfoHeight2
}
PropertyChanges {
target: button1
text: qsTr("Create replacement Keycard with seed phrase")
}
PropertyChanges {
target: button2
text: qsTr("Start using account without keycard")
}
PropertyChanges {
target: button3
text: qsTr("Order new keycard")
link: "https://get.keycard.tech"
useLinkForButton: true
}
}
]
}

View File

@ -64,6 +64,8 @@ Item {
readonly property string stateLoginRegularUser: "regularUserLogin"
readonly property string stateLoginKeycardUser: "keycardUserLogin"
readonly property string lostKeycardItemKey: Constants.appTranslatableConstants.loginAccountsListLostKeycard
property int remainingAttempts: root.startupStore.startupModuleInst.remainingAttempts
onRemainingAttemptsChanged: {
pinInputField.statesInitialization()
@ -280,11 +282,22 @@ Item {
roleName: "order"
sortOrder: Qt.AscendingOrder
}
filters: ValueFilter {
filters: [
ExpressionFilter {
expression: {
if (!root.startupStore.selectedLoginAccount.keycardCreatedAccount &&
model.username === d.lostKeycardItemKey) {
return false
}
return true
}
},
ValueFilter {
roleName: "keyUid"
value: root.startupStore.selectedLoginAccount.keyUid
inverted: true
}
]
}
onAboutToShow: {
@ -305,7 +318,8 @@ Item {
}
label: {
if (model.username === Constants.appTranslatableConstants.loginAccountsListAddNewUser ||
model.username === Constants.appTranslatableConstants.loginAccountsListAddExistingUser) {
model.username === Constants.appTranslatableConstants.loginAccountsListAddExistingUser ||
model.username === Constants.appTranslatableConstants.loginAccountsListLostKeycard) {
return Constants.appTranslationMap[model.username]
}
return model.username
@ -324,6 +338,10 @@ Item {
accountsPopup.close()
root.startupStore.doQuaternaryAction()
}
else if (model.username === Constants.appTranslatableConstants.loginAccountsListLostKeycard) {
accountsPopup.close()
root.startupStore.doQuinaryAction()
}
else {
d.resetLogin()
accountsPopup.close()

View File

@ -30,6 +30,21 @@ Item {
id: timer
}
QtObject {
id: d
property bool wrongSeedPhrase: root.startupStore.startupModuleInst.keycardData & Constants.predefinedKeycardData.wrongSeedPhrase
onWrongSeedPhraseChanged: {
if (wrongSeedPhrase) {
invalidSeedTxt.text = qsTr("Seed phrase doesnt match the profile of an existing Keycard user on this device")
}
else {
invalidSeedTxt.text = ""
}
}
}
function pasteWords () {
const clipboardText = globalUtils.getFromClipboard()
// Split words separated by commas and or blank spaces (spaces, enters, tabs)
@ -200,7 +215,7 @@ Item {
property int itemIndex: index
z: (grid.currentIndex === index) ? 150000000 : 0
onTextChanged: {
invalidSeedTxt.visible = false
invalidSeedTxt.text = ""
}
onDoneInsertingWord: {
grid.addWord(mnemonicIndex, word)
@ -272,8 +287,7 @@ Item {
anchors.top: grid.bottom
anchors.topMargin: 24
color: Theme.palette.dangerColor1
visible: false
text: qsTr("Invalid seed")
visible: text !== ""
}
StatusButton {
@ -297,7 +311,8 @@ Item {
root.startupStore.currentStartupState.flowType === Constants.startupFlow.appLogin) {
return qsTr("Recover Keycard")
}
else if (root.startupStore.currentStartupState.flowType === Constants.startupFlow.firstRunNewUserImportSeedPhraseIntoKeycard) {
else if (root.startupStore.currentStartupState.flowType === Constants.startupFlow.firstRunNewUserImportSeedPhraseIntoKeycard ||
root.startupStore.currentStartupState.flowType === Constants.startupFlow.lostKeycardReplacement) {
return qsTr("Next")
}
return ""
@ -313,7 +328,7 @@ Item {
root.mnemonicInput = []
root.startupStore.doPrimaryAction()
} else {
invalidSeedTxt.visible = true
invalidSeedTxt.text = qsTr("Invalid seed")
enabled = false
}
}

View File

@ -34,11 +34,11 @@ ColumnLayout {
Image {
visible: d.noKeycardsSet
Layout.alignment: Qt.AlignCenter
Layout.preferredHeight: sourceSize.height
Layout.preferredWidth: sourceSize.width
Layout.preferredHeight: 240
Layout.preferredWidth: 350
fillMode: Image.PreserveAspectFit
antialiasing: true
source: Style.png("keycard/security-keycard@2x")
source: Style.png("keycard/keycard-security")
mipmap: true
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

View File

@ -23,6 +23,7 @@ QtObject {
readonly property string firstRunOldUserKeycardImport: "FirstRunOldUserKeycardImport"
readonly property string firstRunOldUserImportSeedPhrase: "FirstRunOldUserImportSeedPhrase"
readonly property string appLogin: "AppLogin"
readonly property string lostKeycardReplacement: "LostKeycardReplacement"
}
readonly property QtObject startupState: QtObject {
@ -83,6 +84,7 @@ QtObject {
readonly property string profileFetchingSuccess: "ProfileFetchingSuccess"
readonly property string profileFetchingTimeout: "ProfileFetchingTimeout"
readonly property string profileFetchingAnnouncement: "ProfileFetchingAnnouncement"
readonly property string lostKeycardOptions: "LostKeycardOptions"
}
readonly property QtObject predefinedKeycardData: QtObject {
@ -781,11 +783,13 @@ QtObject {
readonly property QtObject appTranslatableConstants: QtObject {
readonly property string loginAccountsListAddNewUser: "LOGIN-ACCOUNTS-LIST-ADD-NEW-USER"
readonly property string loginAccountsListAddExistingUser: "LOGIN-ACCOUNTS-LIST-ADD-EXISTING-USER"
readonly property string loginAccountsListLostKeycard: "LOGIN-ACCOUNTS-LIST-LOST-KEYCARD"
}
readonly property var appTranslationMap: ({})
Component.onCompleted: {
appTranslationMap[appTranslatableConstants.loginAccountsListAddNewUser] = qsTr("Add new user")
appTranslationMap[appTranslatableConstants.loginAccountsListAddExistingUser] = qsTr("Add existing Status user")
appTranslationMap[appTranslatableConstants.loginAccountsListLostKeycard] = qsTr("Lost Keycard")
}
}