feat(@desktop/keycard): keycard settings -> migrate keypair flow

- Added flow which covers `Setup a new Keycard with an existing account` from
the keycard settings part (though two sub-flows there are missing, `Unlock Keycard`
and `Authentication` cause we don't have them yet).
- Updated factory reset flow (part of shared module) that it can read and display metadata
from a keycard if they are set, with this update this flow is almost complete, we are missing
`Unlock Keycard` flow for it as well.
This commit is contained in:
Sale Djenic 2022-08-31 19:09:07 +02:00 committed by saledjenic
parent ec7710490d
commit afa7928bae
62 changed files with 4123 additions and 160 deletions

View File

@ -7,7 +7,7 @@ const LS_KEY_STORE_TO_KEYCHAIN* = "storeToKeychain"
const DEFAULT_STORE_TO_KEYCHAIN = "notNow" const DEFAULT_STORE_TO_KEYCHAIN = "notNow"
# Local Account Settings values: # Local Account Settings values:
const LS_VALUE_STORE* = "store" const LS_VALUE_STORE* = "store"
const LS_VALUE_NOTNOW* = "notNow" const LS_VALUE_NOT_NOW* = "notNow"
const LS_VALUE_NEVER* = "never" const LS_VALUE_NEVER* = "never"
QtObject: QtObject:

View File

@ -4,6 +4,8 @@ import io_interface
import ../../../../core/eventemitter import ../../../../core/eventemitter
import ../../../shared_modules/keycard_popup/io_interface as keycard_shared_module
logScope: logScope:
topics = "profile-section-keycard-module-controller" topics = "profile-section-keycard-module-controller"
@ -23,4 +25,9 @@ proc delete*(self: Controller) =
discard discard
proc init*(self: Controller) = proc init*(self: Controller) =
discard self.events.on(SignalSharedKeycarModuleFlowTerminated) do(e: Args):
let args = SharedKeycarModuleFlowTerminatedArgs(e)
self.delegate.onSharedKeycarModuleFlowTerminated(args.lastStepInTheCurrentFlow)
self.events.on(SignalSharedKeycarModuleDisplayPopup) do(e: Args):
self.delegate.onDisplayKeycardSharedModuleFlow()

View File

@ -16,6 +16,19 @@ method isLoaded*(self: AccessInterface): bool {.base.} =
method getModuleAsVariant*(self: AccessInterface): QVariant {.base.} = method getModuleAsVariant*(self: AccessInterface): QVariant {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
method getKeycardSharedModule*(self: AccessInterface): QVariant {.base.} =
raise newException(ValueError, "No implementation available")
method onDisplayKeycardSharedModuleFlow*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method onSharedKeycarModuleFlowTerminated*(self: AccessInterface, lastStepInTheCurrentFlow: bool) {.base.} =
raise newException(ValueError, "No implementation available")
method runSetupKeycardPopup*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
# View Delegate Interface # View Delegate Interface
# Delegate for the view must be declared here due to use of QtObject and multi # Delegate for the view must be declared here due to use of QtObject and multi
# inheritance, which is not well supported in Nim. # inheritance, which is not well supported in Nim.

View File

@ -6,8 +6,12 @@ import ../io_interface as delegate_interface
import ../../../../core/eventemitter import ../../../../core/eventemitter
import ../../../../../app_service/service/keycard/service as keycard_service import ../../../../../app_service/service/keycard/service as keycard_service
import ../../../../../app_service/service/privacy/service as privacy_service
import ../../../../../app_service/service/accounts/service as accounts_service
import ../../../../../app_service/service/wallet_account/service as wallet_account_service import ../../../../../app_service/service/wallet_account/service as wallet_account_service
import ../../../shared_modules/keycard_popup/module as keycard_shared_module
export io_interface export io_interface
logScope: logScope:
@ -22,16 +26,23 @@ type
moduleLoaded: bool moduleLoaded: bool
events: EventEmitter events: EventEmitter
keycardService: keycard_service.Service keycardService: keycard_service.Service
privacyService: privacy_service.Service
accountsService: accounts_service.Service
walletAccountService: wallet_account_service.Service walletAccountService: wallet_account_service.Service
keycardSharedModule: keycard_shared_module.AccessInterface
proc newModule*(delegate: delegate_interface.AccessInterface, proc newModule*(delegate: delegate_interface.AccessInterface,
events: EventEmitter, events: EventEmitter,
keycardService: keycard_service.Service, keycardService: keycard_service.Service,
privacyService: privacy_service.Service,
accountsService: accounts_service.Service,
walletAccountService: wallet_account_service.Service): Module = walletAccountService: wallet_account_service.Service): Module =
result = Module() result = Module()
result.delegate = delegate result.delegate = delegate
result.events = events result.events = events
result.keycardService = keycardService result.keycardService = keycardService
result.privacyService = privacyService
result.accountsService = accountsService
result.walletAccountService = walletAccountService result.walletAccountService = walletAccountService
result.view = view.newView(result) result.view = view.newView(result)
result.viewVariant = newQVariant(result.view) result.viewVariant = newQVariant(result.view)
@ -42,6 +53,8 @@ method delete*(self: Module) =
self.view.delete self.view.delete
self.viewVariant.delete self.viewVariant.delete
self.controller.delete self.controller.delete
if not self.keycardSharedModule.isNil:
self.keycardSharedModule.delete
method load*(self: Module) = method load*(self: Module) =
self.controller.init() self.controller.init()
@ -55,4 +68,29 @@ method viewDidLoad*(self: Module) =
self.delegate.profileModuleDidLoad() self.delegate.profileModuleDidLoad()
method getModuleAsVariant*(self: Module): QVariant = method getModuleAsVariant*(self: Module): QVariant =
return self.viewVariant return self.viewVariant
method getKeycardSharedModule*(self: Module): QVariant =
return self.keycardSharedModule.getModuleAsVariant()
proc createSharedKeycardModule(self: Module) =
self.keycardSharedModule = keycard_shared_module.newModule[Module](self, self.events, self.keycardService,
self.privacyService, self.accountsService, self.walletAccountService)
proc isSharedKeycardModuleFlowRunning(self: Module): bool =
return not self.keycardSharedModule.isNil
method onSharedKeycarModuleFlowTerminated*(self: Module, lastStepInTheCurrentFlow: bool) =
if self.isSharedKeycardModuleFlowRunning():
self.view.emitDestroyKeycardSharedModuleFlow()
self.keycardSharedModule.delete
self.keycardSharedModule = nil
method runSetupKeycardPopup*(self: Module) =
self.createSharedKeycardModule()
if self.keycardSharedModule.isNil:
return
self.keycardSharedModule.runFlow(keycard_shared_module.FlowType.SetupNewKeycard)
method onDisplayKeycardSharedModuleFlow*(self: Module) =
self.view.emitDisplayKeycardSharedModuleFlow()

View File

@ -17,4 +17,19 @@ QtObject:
proc load*(self: View) = proc load*(self: View) =
self.delegate.viewDidLoad() self.delegate.viewDidLoad()
proc getKeycardSharedModule(self: View): QVariant {.slot.} =
return self.delegate.getKeycardSharedModule()
QtProperty[QVariant] keycardSharedModule:
read = getKeycardSharedModule
proc displayKeycardSharedModuleFlow*(self: View) {.signal.}
proc emitDisplayKeycardSharedModuleFlow*(self: View) =
self.displayKeycardSharedModuleFlow()
proc destroyKeycardSharedModuleFlow*(self: View) {.signal.}
proc emitDestroyKeycardSharedModuleFlow*(self: View) =
self.destroyKeycardSharedModuleFlow()
proc runSetupKeycardPopup*(self: View) {.slot.} =
self.delegate.runSetupKeycardPopup()

View File

@ -99,7 +99,8 @@ proc newModule*(delegate: delegate_interface.AccessInterface,
result, events, settingsService, ensService, walletAccountService, networkService result, events, settingsService, ensService, walletAccountService, networkService
) )
result.communitiesModule = communities_module.newModule(result, communityService) result.communitiesModule = communities_module.newModule(result, communityService)
result.keycardModule = keycard_module.newModule(result, events, keycardService, walletAccountService) result.keycardModule = keycard_module.newModule(result, events, keycardService, privacyService, accountsService,
walletAccountService)
singletonInstance.engine.setRootContextProperty("profileSectionModule", result.viewVariant) singletonInstance.engine.setRootContextProperty("profileSectionModule", result.viewVariant)

View File

@ -6,6 +6,9 @@ import ../../../global/global_singleton
import ../../../core/signals/types import ../../../core/signals/types
import ../../../core/eventemitter import ../../../core/eventemitter
import ../../../../app_service/service/keycard/service as keycard_service import ../../../../app_service/service/keycard/service as keycard_service
import ../../../../app_service/service/privacy/service as privacy_service
import ../../../../app_service/service/accounts/service as accounts_service
import ../../../../app_service/service/wallet_account/service as wallet_account_service
logScope: logScope:
topics = "keycard-popup-controller" topics = "keycard-popup-controller"
@ -15,21 +18,46 @@ type
delegate: io_interface.AccessInterface delegate: io_interface.AccessInterface
events: EventEmitter events: EventEmitter
keycardService: keycard_service.Service keycardService: keycard_service.Service
privacyService: privacy_service.Service
accountsService: accounts_service.Service
walletAccountService: wallet_account_service.Service
connectionIds: seq[UUID] connectionIds: seq[UUID]
tmpKeycardContainsMetadata: bool tmpKeycardContainsMetadata: bool
tmpPin: string
tmpPinMatch: bool
tmpPassword: string
tmpKeyUid: string
tmpSelectedKeyPairIsProfile: bool
tmpSelectedKeyPairName: string
tmpSelectedKeyPairWalletPaths: seq[string]
tmpSeedPhrase: string
tmpSeedPhraseLength: int
proc newController*(delegate: io_interface.AccessInterface, proc newController*(delegate: io_interface.AccessInterface,
events: EventEmitter, events: EventEmitter,
keycardService: keycard_service.Service): keycardService: keycard_service.Service,
privacyService: privacy_service.Service,
accountsService: accounts_service.Service,
walletAccountService: wallet_account_service.Service):
Controller = Controller =
result = Controller() result = Controller()
result.delegate = delegate result.delegate = delegate
result.events = events result.events = events
result.keycardService = keycardService result.keycardService = keycardService
result.privacyService = privacyService
result.accountsService = accountsService
result.walletAccountService = walletAccountService
result.tmpKeycardContainsMetadata = false result.tmpKeycardContainsMetadata = false
result.tmpPinMatch = false
result.tmpSeedPhraseLength = 0
result.tmpSelectedKeyPairIsProfile = false
proc disconnect*(self: Controller) =
for id in self.connectionIds:
self.events.disconnect(id)
proc delete*(self: Controller) = proc delete*(self: Controller) =
discard self.disconnect()
proc init*(self: Controller) = proc init*(self: Controller) =
let handlerId = self.events.onWithUUID(SignalKeycardResponse) do(e: Args): let handlerId = self.events.onWithUUID(SignalKeycardResponse) do(e: Args):
@ -37,9 +65,8 @@ proc init*(self: Controller) =
self.delegate.onKeycardResponse(args.flowType, args.flowEvent) self.delegate.onKeycardResponse(args.flowType, args.flowEvent)
self.connectionIds.add(handlerId) self.connectionIds.add(handlerId)
proc disconnect*(self: Controller) = proc getKeycardData*(self: Controller): string =
for id in self.connectionIds: return self.delegate.getKeycardData()
self.events.disconnect(id)
proc setKeycardData*(self: Controller, value: string) = proc setKeycardData*(self: Controller, value: string) =
self.delegate.setKeycardData(value) self.delegate.setKeycardData(value)
@ -50,6 +77,83 @@ proc containsMetadata*(self: Controller): bool =
proc setContainsMetadata*(self: Controller, value: bool) = proc setContainsMetadata*(self: Controller, value: bool) =
self.tmpKeycardContainsMetadata = value self.tmpKeycardContainsMetadata = value
proc setPin*(self: Controller, value: string) =
self.tmpPin = value
proc getPin*(self: Controller): string =
return self.tmpPin
proc setPinMatch*(self: Controller, value: bool) =
self.tmpPinMatch = value
proc getPinMatch*(self: Controller): bool =
return self.tmpPinMatch
proc setPassword*(self: Controller, value: string) =
self.tmpPassword = value
proc getPassword*(self: Controller): string =
return self.tmpPassword
proc setKeyUid*(self: Controller, value: string) =
self.tmpKeyUid = value
proc setSelectedKeyPairIsProfile*(self: Controller, value: bool) =
self.tmpSelectedKeyPairIsProfile = value
proc getSelectedKeyPairIsProfile*(self: Controller): bool =
return self.tmpSelectedKeyPairIsProfile
proc setSelectedKeyPairName*(self: Controller, value: string) =
self.tmpSelectedKeyPairName = value
proc getSelectedKeyPairName*(self: Controller): string =
return self.tmpSelectedKeyPairName
proc setSelectedKeyPairWalletPaths*(self: Controller, paths: seq[string]) =
self.tmpSelectedKeyPairWalletPaths = paths
proc getSelectedKeyPairWalletPaths*(self: Controller): seq[string] =
return self.tmpSelectedKeyPairWalletPaths
proc setSeedPhrase*(self: Controller, value: string) =
let words = value.split(" ")
self.tmpSeedPhrase = value
self.tmpSeedPhraseLength = words.len
proc getSeedPhrase*(self: Controller): string =
return self.tmpSeedPhrase
proc getSeedPhraseLength*(self: Controller): int =
return self.tmpSeedPhraseLength
proc validSeedPhrase*(self: Controller, seedPhrase: string): bool =
let err = self.accountsService.validateMnemonic(seedPhrase)
return err.len == 0
proc seedPhraseRefersToLoggedInUser*(self: Controller, seedPhrase: string): bool =
let acc = self.accountsService.createAccountFromMnemonic(seedPhrase)
return acc.keyUid == singletonInstance.userProfile.getAddress()
proc verifyPassword*(self: Controller, password: string): bool =
return self.accountsService.verifyPassword(password)
proc convertToKeycardAccount*(self: Controller, password: string): bool =
singletonInstance.localAccountSettings.setStoreToKeychainValue(LS_VALUE_NOT_NOW)
return self.accountsService.convertToKeycardAccount(self.tmpKeyUid, password)
proc getLoggedInAccount*(self: Controller): AccountDto =
return self.accountsService.getLoggedInAccount()
proc getCurrentKeycardServiceFlow*(self: Controller): keycard_service.KCSFlowType =
return self.keycardService.getCurrentFlow()
proc getLastReceivedKeycardData*(self: Controller): tuple[flowType: string, flowEvent: KeycardEvent] =
return self.keycardService.getLastReceivedKeycardData()
proc setMetadataFromKeycard*(self: Controller, cardMetadata: CardMetadata) =
self.delegate.setKeyPairStoredOnKeycard(cardMetadata)
proc cancelCurrentFlow(self: Controller) = proc cancelCurrentFlow(self: Controller) =
self.keycardService.cancelCurrentFlow() self.keycardService.cancelCurrentFlow()
# in most cases we're running another flow after canceling the current one, # in most cases we're running another flow after canceling the current one,
@ -64,9 +168,76 @@ proc runGetMetadataFlow*(self: Controller) =
self.cancelCurrentFlow() self.cancelCurrentFlow()
self.keycardService.startGetMetadataFlow() self.keycardService.startGetMetadataFlow()
proc runStoreMetadataFlow*(self: Controller, cardName: string, pin: string, walletPaths: seq[string]) =
self.cancelCurrentFlow()
self.keycardService.startStoreMetadataFlow(cardName, pin, walletPaths)
proc runLoadAccountFlow*(self: Controller, factoryReset = false) =
self.cancelCurrentFlow()
self.keycardService.startLoadAccountFlow(factoryReset)
proc resumeCurrentFlowLater*(self: Controller) = proc resumeCurrentFlowLater*(self: Controller) =
self.keycardService.resumeCurrentFlowLater() self.keycardService.resumeCurrentFlowLater()
proc readyToDisplayPopup*(self: Controller) =
self.events.emit(SignalSharedKeycarModuleDisplayPopup, Args())
proc terminateCurrentFlow*(self: Controller, lastStepInTheCurrentFlow: bool) = proc terminateCurrentFlow*(self: Controller, lastStepInTheCurrentFlow: bool) =
let data = SharedKeycarModuleFlowTerminatedArgs(lastStepInTheCurrentFlow: lastStepInTheCurrentFlow) let data = SharedKeycarModuleFlowTerminatedArgs(lastStepInTheCurrentFlow: lastStepInTheCurrentFlow)
self.events.emit(SignalSharedKeycarModuleFlowTerminated, data) self.events.emit(SignalSharedKeycarModuleFlowTerminated, data)
proc getWalletAccounts*(self: Controller): seq[wallet_account_service.WalletAccountDto] =
if self.walletAccountService.isNil:
debug "walletAccountService doesn't meant to be used from the context it's used, check the context shared popup module is used"
return
return self.walletAccountService.fetchAccounts()
proc getBalanceForAddress*(self: Controller, address: string): float64 =
if self.walletAccountService.isNil:
debug "walletAccountService doesn't meant to be used from the context it's used, check the context shared popup module is used"
return
return self.walletAccountService.fetchBalanceForAddress(address)
proc enterKeycardPin*(self: Controller, pin: string) =
self.keycardService.enterPin(pin)
proc storePinToKeycard*(self: Controller, pin: string, puk: string) =
self.keycardService.storePin(pin, puk)
proc storeSeedPhraseToKeycard*(self: Controller, seedPhraseLength: int, seedPhrase: string) =
self.keycardService.storeSeedPhrase(seedPhraseLength, seedPhrase)
proc generateRandomPUK*(self: Controller): string =
return self.keycardService.generateRandomPUK()
proc isMnemonicBackedUp*(self: Controller): bool =
if self.privacyService.isNil:
debug "privacyService doesn't meant to be used from the context it's used, check the context shared popup module is used"
return
return self.privacyService.isMnemonicBackedUp()
proc getMnemonic*(self: Controller): string =
if self.privacyService.isNil:
debug "privacyService doesn't meant to be used from the context it's used, check the context shared popup module is used"
return
return self.privacyService.getMnemonic()
proc removeMnemonic*(self: Controller) =
if self.privacyService.isNil:
debug "privacyService doesn't meant to be used from the context it's used, check the context shared popup module is used"
return
self.privacyService.removeMnemonic()
proc getMnemonicWordAtIndex*(self: Controller, index: int): string =
if self.privacyService.isNil:
debug "privacyService doesn't meant to be used from the context it's used, check the context shared popup module is used"
return
return self.privacyService.getMnemonicWordAtIndex(index)
proc loggedInUserUsesBiometricLogin*(self: Controller): bool =
if(not defined(macosx)):
return false
let value = singletonInstance.localAccountSettings.getStoreToKeychainValue()
if (value != LS_VALUE_STORE):
return false
return true

View File

@ -0,0 +1,23 @@
type
CreatePinState* = ref object of State
proc newCreatePinState*(flowType: FlowType, backState: State): CreatePinState =
result = CreatePinState()
result.setup(flowType, StateType.CreatePin, backState)
proc delete*(self: CreatePinState) =
self.State.delete
method executeBackCommand*(self: CreatePinState, controller: Controller) =
controller.setPin("")
controller.setPinMatch(false)
method executeSecondaryCommand*(self: CreatePinState, controller: Controller) =
if self.flowType == FlowType.SetupNewKeycard:
controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false)
method getNextTertiaryState*(self: CreatePinState, controller: Controller): State =
if self.flowType == FlowType.SetupNewKeycard:
if controller.getPin().len == PINLengthForStatusApp:
return createState(StateType.RepeatPin, self.flowType, self)
return nil

View File

@ -7,3 +7,62 @@ proc newEnterPinState*(flowType: FlowType, backState: State): EnterPinState =
proc delete*(self: EnterPinState) = proc delete*(self: EnterPinState) =
self.State.delete self.State.delete
method getNextPrimaryState*(self: EnterPinState, controller: Controller): State =
if self.flowType == FlowType.FactoryReset or
self.flowType == FlowType.SetupNewKeycard:
return createState(StateType.FactoryResetConfirmation, self.flowType, self)
return nil
method executeSecondaryCommand*(self: EnterPinState, controller: Controller) =
if self.flowType == FlowType.FactoryReset or
self.flowType == FlowType.SetupNewKeycard:
controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false)
method executeTertiaryCommand*(self: EnterPinState, controller: Controller) =
if self.flowType == FlowType.SetupNewKeycard or
self.flowType == FlowType.FactoryReset:
if controller.getPin().len == PINLengthForStatusApp:
controller.enterKeycardPin(controller.getPin())
method resolveKeycardNextState*(self: EnterPinState, keycardFlowType: string, keycardEvent: KeycardEvent,
controller: Controller): State =
let state = ensureReaderAndCardPresence(self, keycardFlowType, keycardEvent, controller)
if not state.isNil:
return state
if self.flowType == FlowType.FactoryReset:
if keycardFlowType == ResponseTypeValueEnterPIN and
keycardEvent.error.len > 0 and
keycardEvent.error == RequestParamPIN:
controller.setKeycardData($keycardEvent.pinRetries)
if keycardEvent.pinRetries > 0:
return createState(StateType.WrongPin, self.flowType, nil)
return createState(StateType.MaxPinRetriesReached, self.flowType, nil)
if keycardFlowType == ResponseTypeValueEnterPIN and
keycardEvent.error.len == 0:
return createState(StateType.MaxPinRetriesReached, self.flowType, nil)
if keycardFlowType == ResponseTypeValueEnterPUK and
keycardEvent.error.len == 0:
if keycardEvent.pinRetries == 0 and keycardEvent.pukRetries > 0:
return createState(StateType.MaxPinRetriesReached, self.flowType, nil)
if keycardFlowType == ResponseTypeValueKeycardFlowResult:
controller.setMetadataFromKeycard(keycardEvent.cardMetadata)
return createState(StateType.PinVerified, self.flowType, nil)
if self.flowType == FlowType.SetupNewKeycard:
if keycardFlowType == ResponseTypeValueEnterPIN and
keycardEvent.error.len > 0 and
keycardEvent.error == RequestParamPIN:
controller.setKeycardData($keycardEvent.pinRetries)
if keycardEvent.pinRetries > 0:
return createState(StateType.WrongPin, self.flowType, nil)
return createState(StateType.MaxPinRetriesReached, self.flowType, nil)
if keycardFlowType == ResponseTypeValueEnterPIN and
keycardEvent.error.len == 0:
return createState(StateType.MaxPinRetriesReached, self.flowType, nil)
if keycardFlowType == ResponseTypeValueEnterPUK and
keycardEvent.error.len == 0:
if keycardEvent.pinRetries == 0 and keycardEvent.pukRetries > 0:
return createState(StateType.MaxPinRetriesReached, self.flowType, nil)
if keycardFlowType == ResponseTypeValueKeycardFlowResult:
controller.setMetadataFromKeycard(keycardEvent.cardMetadata)
return createState(StateType.PinVerified, self.flowType, nil)

View File

@ -0,0 +1,42 @@
type
EnterSeedPhraseState* = ref object of State
verifiedSeedPhrase: bool
proc newEnterSeedPhraseState*(flowType: FlowType, backState: State): EnterSeedPhraseState =
result = EnterSeedPhraseState()
result.setup(flowType, StateType.EnterSeedPhrase, backState)
result.verifiedSeedPhrase = false
proc delete*(self: EnterSeedPhraseState) =
self.State.delete
method executePrimaryCommand*(self: EnterSeedPhraseState, controller: Controller) =
if self.flowType == FlowType.SetupNewKeycard:
self.verifiedSeedPhrase = controller.validSeedPhrase(controller.getSeedPhrase()) and
(not controller.getSelectedKeyPairIsProfile() or
controller.getSelectedKeyPairIsProfile() and
controller.seedPhraseRefersToLoggedInUser(controller.getSeedPhrase()))
if self.verifiedSeedPhrase:
controller.storeSeedPhraseToKeycard(controller.getSeedPhraseLength(), controller.getSeedPhrase())
else:
controller.setKeycardData(getPredefinedKeycardData(controller.getKeycardData(), PredefinedKeycardData.WrongSeedPhrase, add = true))
method executeSecondaryCommand*(self: EnterSeedPhraseState, controller: Controller) =
if self.flowType == FlowType.SetupNewKeycard:
controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false)
method getNextPrimaryState*(self: EnterSeedPhraseState, controller: Controller): State =
if self.flowType == FlowType.SetupNewKeycard:
if not self.verifiedSeedPhrase:
return createState(StateType.WrongSeedPhrase, self.flowType, nil)
method resolveKeycardNextState*(self: EnterSeedPhraseState, keycardFlowType: string, keycardEvent: KeycardEvent,
controller: Controller): State =
let state = ensureReaderAndCardPresence(self, keycardFlowType, keycardEvent, controller)
if not state.isNil:
return state
if self.flowType == FlowType.SetupNewKeycard:
if keycardFlowType == ResponseTypeValueKeycardFlowResult and
keycardEvent.keyUid.len > 0:
controller.setKeyUid(keycardEvent.keyUid)
return createState(StateType.MigratingKeyPair, self.flowType, nil)

View File

@ -0,0 +1,25 @@
type
FactoryResetConfirmationDisplayMetadataState* = ref object of State
proc newFactoryResetConfirmationDisplayMetadataState*(flowType: FlowType, backState: State): FactoryResetConfirmationDisplayMetadataState =
result = FactoryResetConfirmationDisplayMetadataState()
result.setup(flowType, StateType.FactoryResetConfirmationDisplayMetadata, backState)
proc delete*(self: FactoryResetConfirmationDisplayMetadataState) =
self.State.delete
method executePrimaryCommand*(self: FactoryResetConfirmationDisplayMetadataState, controller: Controller) =
if self.flowType == FlowType.FactoryReset:
controller.runGetAppInfoFlow(factoryReset = true)
elif self.flowType == FlowType.SetupNewKeycard:
controller.setKeycardData(getPredefinedKeycardData(controller.getKeycardData(), PredefinedKeycardData.HideKeyPair, add = true))
controller.runGetAppInfoFlow(factoryReset = true)
method executeSecondaryCommand*(self: FactoryResetConfirmationDisplayMetadataState, controller: Controller) =
if self.flowType == FlowType.FactoryReset or
self.flowType == FlowType.SetupNewKeycard:
controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false)
method resolveKeycardNextState*(self: FactoryResetConfirmationDisplayMetadataState, keycardFlowType: string, keycardEvent: KeycardEvent,
controller: Controller): State =
return ensureReaderAndCardPresenceAndResolveNextState(self, keycardFlowType, keycardEvent, controller)

View File

@ -11,10 +11,15 @@ proc delete*(self: FactoryResetConfirmationState) =
method executePrimaryCommand*(self: FactoryResetConfirmationState, controller: Controller) = method executePrimaryCommand*(self: FactoryResetConfirmationState, controller: Controller) =
if self.flowType == FlowType.FactoryReset: if self.flowType == FlowType.FactoryReset:
controller.runGetAppInfoFlow(factoryReset = true) controller.runGetAppInfoFlow(factoryReset = true)
elif self.flowType == FlowType.SetupNewKeycard:
controller.setKeycardData(getPredefinedKeycardData(controller.getKeycardData(), PredefinedKeycardData.HideKeyPair, add = true))
controller.runGetAppInfoFlow(factoryReset = true)
method executeSecondaryCommand*(self: FactoryResetConfirmationState, controller: Controller) = method executeSecondaryCommand*(self: FactoryResetConfirmationState, controller: Controller) =
if self.flowType == FlowType.FactoryReset: if self.flowType == FlowType.FactoryReset or
self.flowType == FlowType.SetupNewKeycard:
controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false) controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false)
method getNextPrimaryState*(self: FactoryResetConfirmationState, controller: Controller): State = method resolveKeycardNextState*(self: FactoryResetConfirmationState, keycardFlowType: string, keycardEvent: KeycardEvent,
return createState(StateType.PluginReader, self.flowType, nil) controller: Controller): State =
return ensureReaderAndCardPresenceAndResolveNextState(self, keycardFlowType, keycardEvent, controller)

View File

@ -11,3 +11,13 @@ proc delete*(self: FactoryResetSuccessState) =
method executePrimaryCommand*(self: FactoryResetSuccessState, controller: Controller) = method executePrimaryCommand*(self: FactoryResetSuccessState, controller: Controller) =
if self.flowType == FlowType.FactoryReset: if self.flowType == FlowType.FactoryReset:
controller.terminateCurrentFlow(lastStepInTheCurrentFlow = true) controller.terminateCurrentFlow(lastStepInTheCurrentFlow = true)
elif self.flowType == FlowType.SetupNewKeycard:
controller.runLoadAccountFlow()
method executeSecondaryCommand*(self: FactoryResetSuccessState, controller: Controller) =
if self.flowType == FlowType.SetupNewKeycard:
controller.terminateCurrentFlow(lastStepInTheCurrentFlow = true)
method resolveKeycardNextState*(self: FactoryResetSuccessState, keycardFlowType: string, keycardEvent: KeycardEvent,
controller: Controller): State =
return ensureReaderAndCardPresenceAndResolveNextState(self, keycardFlowType, keycardEvent, controller)

View File

@ -9,17 +9,23 @@ proc delete*(self: InsertKeycardState) =
self.State.delete self.State.delete
method executePrimaryCommand*(self: InsertKeycardState, controller: Controller) = method executePrimaryCommand*(self: InsertKeycardState, controller: Controller) =
if self.flowType == FlowType.FactoryReset: if self.flowType == FlowType.FactoryReset or
self.flowType == FlowType.SetupNewKeycard:
controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false) controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false)
method resolveKeycardNextState*(self: InsertKeycardState, keycardFlowType: string, keycardEvent: KeycardEvent, method resolveKeycardNextState*(self: InsertKeycardState, keycardFlowType: string, keycardEvent: KeycardEvent,
controller: Controller): State = controller: Controller): State =
let state = ensureReaderAndCardPresenceAndResolveNextState(self, keycardFlowType, keycardEvent, controller)
if not state.isNil:
return state
if keycardFlowType == ResponseTypeValueInsertCard and if keycardFlowType == ResponseTypeValueInsertCard and
keycardEvent.error.len > 0 and keycardEvent.error.len > 0 and
keycardEvent.error == ErrorConnection: keycardEvent.error == ErrorConnection:
controller.setKeycardData(ResponseTypeValueInsertCard) controller.setKeycardData(getPredefinedKeycardData(controller.getKeycardData(), PredefinedKeycardData.WronglyInsertedCard, add = true))
return nil return nil
if keycardFlowType == ResponseTypeValueCardInserted: if keycardFlowType == ResponseTypeValueCardInserted:
controller.setKeycardData("") controller.setKeycardData(getPredefinedKeycardData(controller.getKeycardData(), PredefinedKeycardData.WronglyInsertedCard, add = false))
return createState(StateType.ReadingKeycard, self.flowType, nil) if self.flowType == FlowType.SetupNewKeycard:
return createState(StateType.KeycardInserted, self.flowType, self.getBackState)
return createState(StateType.KeycardInserted, self.flowType, nil)
return nil return nil

View File

@ -0,0 +1,13 @@
type
KeyPairMigrateFailureState* = ref object of State
proc newKeyPairMigrateFailureState*(flowType: FlowType, backState: State): KeyPairMigrateFailureState =
result = KeyPairMigrateFailureState()
result.setup(flowType, StateType.KeyPairMigrateFailure, backState)
proc delete*(self: KeyPairMigrateFailureState) =
self.State.delete
method executePrimaryCommand*(self: KeyPairMigrateFailureState, controller: Controller) =
if self.flowType == FlowType.SetupNewKeycard:
controller.terminateCurrentFlow(lastStepInTheCurrentFlow = true)

View File

@ -0,0 +1,16 @@
type
KeyPairMigrateSuccessState* = ref object of State
proc newKeyPairMigrateSuccessState*(flowType: FlowType, backState: State): KeyPairMigrateSuccessState =
result = KeyPairMigrateSuccessState()
result.setup(flowType, StateType.KeyPairMigrateSuccess, backState)
proc delete*(self: KeyPairMigrateSuccessState) =
self.State.delete
method executePrimaryCommand*(self: KeyPairMigrateSuccessState, controller: Controller) =
if self.flowType == FlowType.SetupNewKeycard:
controller.terminateCurrentFlow(lastStepInTheCurrentFlow = true)
if controller.getSelectedKeyPairIsProfile():
info "restart the app because of successfully migrated profile keypair"
quit() # quit the app

View File

@ -0,0 +1,20 @@
type
KeycardEmptyMetadataState* = ref object of State
proc newKeycardEmptyMetadataState*(flowType: FlowType, backState: State): KeycardEmptyMetadataState =
result = KeycardEmptyMetadataState()
result.setup(flowType, StateType.KeycardEmptyMetadata, backState)
proc delete*(self: KeycardEmptyMetadataState) =
self.State.delete
method executeSecondaryCommand*(self: KeycardEmptyMetadataState, controller: Controller) =
if self.flowType == FlowType.FactoryReset or
self.flowType == FlowType.SetupNewKeycard:
controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false)
method getNextPrimaryState*(self: KeycardEmptyMetadataState, controller: Controller): State =
if self.flowType == FlowType.FactoryReset or
self.flowType == FlowType.SetupNewKeycard:
return createState(StateType.FactoryResetConfirmation, self.flowType, self)
return nil

View File

@ -9,5 +9,6 @@ proc delete*(self: KeycardEmptyState) =
self.State.delete self.State.delete
method executePrimaryCommand*(self: KeycardEmptyState, controller: Controller) = method executePrimaryCommand*(self: KeycardEmptyState, controller: Controller) =
if self.flowType == FlowType.FactoryReset: if self.flowType == FlowType.FactoryReset or
self.flowType == FlowType.SetupNewKeycard:
controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false) controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false)

View File

@ -0,0 +1,19 @@
type
KeycardInsertedState* = ref object of State
proc newKeycardInsertedState*(flowType: FlowType, backState: State): KeycardInsertedState =
result = KeycardInsertedState()
result.setup(flowType, StateType.KeycardInserted, backState)
proc delete*(self: KeycardInsertedState) =
self.State.delete
method getNextSecondaryState*(self: KeycardInsertedState, controller: Controller): State =
if self.flowType == FlowType.SetupNewKeycard:
return createState(StateType.ReadingKeycard, self.flowType, self.getBackState)
return createState(StateType.ReadingKeycard, self.flowType, nil)
method executePrimaryCommand*(self: KeycardInsertedState, controller: Controller) =
if self.flowType == FlowType.FactoryReset or
self.flowType == FlowType.SetupNewKeycard:
controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false)

View File

@ -0,0 +1,20 @@
type
KeycardMetadataDisplayState* = ref object of State
proc newKeycardMetadataDisplayState*(flowType: FlowType, backState: State): KeycardMetadataDisplayState =
result = KeycardMetadataDisplayState()
result.setup(flowType, StateType.KeycardMetadataDisplay, backState)
proc delete*(self: KeycardMetadataDisplayState) =
self.State.delete
method getNextPrimaryState*(self: KeycardMetadataDisplayState, controller: Controller): State =
if self.flowType == FlowType.FactoryReset or
self.flowType == FlowType.SetupNewKeycard:
return createState(StateType.FactoryResetConfirmationDisplayMetadata, self.flowType, self)
return nil
method executeSecondaryCommand*(self: KeycardMetadataDisplayState, controller: Controller) =
if self.flowType == FlowType.FactoryReset or
self.flowType == FlowType.SetupNewKeycard:
controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false)

View File

@ -0,0 +1,22 @@
type
KeycardNotEmptyState* = ref object of State
proc newKeycardNotEmptyState*(flowType: FlowType, backState: State): KeycardNotEmptyState =
result = KeycardNotEmptyState()
result.setup(flowType, StateType.KeycardNotEmpty, backState)
proc delete*(self: KeycardNotEmptyState) =
self.State.delete
method executePrimaryCommand*(self: KeycardNotEmptyState, controller: Controller) =
if self.flowType == FlowType.SetupNewKeycard:
controller.setKeycardData(getPredefinedKeycardData(controller.getKeycardData(), PredefinedKeycardData.HideKeyPair, add = true))
controller.runGetMetadataFlow()
method executeSecondaryCommand*(self: KeycardNotEmptyState, controller: Controller) =
if self.flowType == FlowType.SetupNewKeycard:
controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false)
method resolveKeycardNextState*(self: KeycardNotEmptyState, keycardFlowType: string, keycardEvent: KeycardEvent,
controller: Controller): State =
return ensureReaderAndCardPresenceAndResolveNextState(self, keycardFlowType, keycardEvent, controller)

View File

@ -0,0 +1,20 @@
type
MaxPinRetriesReachedState* = ref object of State
proc newMaxPinRetriesReachedState*(flowType: FlowType, backState: State): MaxPinRetriesReachedState =
result = MaxPinRetriesReachedState()
result.setup(flowType, StateType.MaxPinRetriesReached, backState)
proc delete*(self: MaxPinRetriesReachedState) =
self.State.delete
method getNextPrimaryState*(self: MaxPinRetriesReachedState, controller: Controller): State =
if self.flowType == FlowType.FactoryReset or
self.flowType == FlowType.SetupNewKeycard:
return createState(StateType.FactoryResetConfirmation, self.flowType, self)
return nil
method executeSecondaryCommand*(self: MaxPinRetriesReachedState, controller: Controller) =
if self.flowType == FlowType.FactoryReset or
self.flowType == FlowType.SetupNewKeycard:
controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false)

View File

@ -0,0 +1,38 @@
type
MigratingKeyPairState* = ref object of State
migrationSuccess: bool
proc newMigratingKeyPairState*(flowType: FlowType, backState: State): MigratingKeyPairState =
result = MigratingKeyPairState()
result.setup(flowType, StateType.MigratingKeyPair, backState)
result.migrationSuccess = false
proc delete*(self: MigratingKeyPairState) =
self.State.delete
method executePrimaryCommand*(self: MigratingKeyPairState, controller: Controller) =
if self.flowType == FlowType.SetupNewKeycard:
# Ran authentication popup and get pass from there...
let password = controller.getPassword()
self.migrationSuccess = controller.verifyPassword(password)
if controller.getSelectedKeyPairIsProfile():
self.migrationSuccess = self.migrationSuccess and controller.convertToKeycardAccount(password)
if not self.migrationSuccess:
return
controller.runStoreMetadataFlow(controller.getSelectedKeyPairName(), controller.getPin(),
controller.getSelectedKeyPairWalletPaths())
method getNextPrimaryState*(self: MigratingKeyPairState, controller: Controller): State =
if self.flowType == FlowType.SetupNewKeycard:
if not self.migrationSuccess:
return createState(StateType.KeyPairMigrateFailure, self.flowType, nil)
method resolveKeycardNextState*(self: MigratingKeyPairState, keycardFlowType: string, keycardEvent: KeycardEvent,
controller: Controller): State =
let state = ensureReaderAndCardPresenceAndResolveNextState(self, keycardFlowType, keycardEvent, controller)
if not state.isNil:
return state
if self.flowType == FlowType.SetupNewKeycard:
if keycardFlowType == ResponseTypeValueKeycardFlowResult and
keycardEvent.error.len == 0:
return createState(StateType.KeyPairMigrateSuccess, self.flowType, nil)

View File

@ -9,5 +9,6 @@ proc delete*(self: NotKeycardState) =
self.State.delete self.State.delete
method executePrimaryCommand*(self: NotKeycardState, controller: Controller) = method executePrimaryCommand*(self: NotKeycardState, controller: Controller) =
if self.flowType == FlowType.FactoryReset: if self.flowType == FlowType.FactoryReset or
controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false) self.flowType == FlowType.SetupNewKeycard:
controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false)

View File

@ -0,0 +1,21 @@
type
PinSetState* = ref object of State
proc newPinSetState*(flowType: FlowType, backState: State): PinSetState =
result = PinSetState()
result.setup(flowType, StateType.PinSet, backState)
proc delete*(self: PinSetState) =
self.State.delete
method getNextPrimaryState*(self: PinSetState, controller: Controller): State =
if self.flowType == FlowType.SetupNewKeycard:
if controller.isMnemonicBackedUp() or not controller.getSelectedKeyPairIsProfile():
return createState(StateType.EnterSeedPhrase, self.flowType, nil)
else:
return createState(StateType.SeedPhraseDisplay, self.flowType, nil)
return nil
method executeSecondaryCommand*(self: PinSetState, controller: Controller) =
if self.flowType == FlowType.SetupNewKeycard:
controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false)

View File

@ -0,0 +1,20 @@
type
PinVerifiedState* = ref object of State
proc newPinVerifiedState*(flowType: FlowType, backState: State): PinVerifiedState =
result = PinVerifiedState()
result.setup(flowType, StateType.PinVerified, backState)
proc delete*(self: PinVerifiedState) =
self.State.delete
method getNextPrimaryState*(self: PinVerifiedState, controller: Controller): State =
if self.flowType == FlowType.FactoryReset or
self.flowType == FlowType.SetupNewKeycard:
return createState(StateType.KeycardMetadataDisplay, self.flowType, nil)
return nil
method executeSecondaryCommand*(self: PinVerifiedState, controller: Controller) =
if self.flowType == FlowType.FactoryReset or
self.flowType == FlowType.SetupNewKeycard:
controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false)

View File

@ -9,15 +9,10 @@ proc delete*(self: PluginReaderState) =
self.State.delete self.State.delete
method executePrimaryCommand*(self: PluginReaderState, controller: Controller) = method executePrimaryCommand*(self: PluginReaderState, controller: Controller) =
if self.flowType == FlowType.FactoryReset: if self.flowType == FlowType.FactoryReset or
self.flowType == FlowType.SetupNewKeycard:
controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false) controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false)
method resolveKeycardNextState*(self: PluginReaderState, keycardFlowType: string, keycardEvent: KeycardEvent, method resolveKeycardNextState*(self: PluginReaderState, keycardFlowType: string, keycardEvent: KeycardEvent,
controller: Controller): State = controller: Controller): State =
if keycardFlowType == ResponseTypeValueKeycardFlowResult and return ensureReaderAndCardPresenceAndResolveNextState(self, keycardFlowType, keycardEvent, controller)
keycardEvent.error.len > 0 and
keycardEvent.error == ErrorConnection:
controller.resumeCurrentFlowLater()
return nil
if keycardFlowType == ResponseTypeValueInsertCard:
return createState(StateType.InsertKeycard, self.flowType, nil)

View File

@ -9,21 +9,16 @@ proc delete*(self: ReadingKeycardState) =
self.State.delete self.State.delete
method executePrimaryCommand*(self: ReadingKeycardState, controller: Controller) = method executePrimaryCommand*(self: ReadingKeycardState, controller: Controller) =
if self.flowType == FlowType.FactoryReset: if self.flowType == FlowType.FactoryReset or
self.flowType == FlowType.SetupNewKeycard:
controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false) controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false)
method getNextSecondaryState*(self: ReadingKeycardState, controller: Controller): State =
let (flowType, flowEvent) = controller.getLastReceivedKeycardData()
# this is used in case a keycard is not inserted in the moment when flow is run (we're animating an insertion)
return ensureReaderAndCardPresenceAndResolveNextState(self, flowType, flowEvent, controller)
method resolveKeycardNextState*(self: ReadingKeycardState, keycardFlowType: string, keycardEvent: KeycardEvent, method resolveKeycardNextState*(self: ReadingKeycardState, keycardFlowType: string, keycardEvent: KeycardEvent,
controller: Controller): State = controller: Controller): State =
if self.flowType == FlowType.FactoryReset: # this is used in case a keycard is inserted and we jump to the first meaningful screen
if keycardFlowType == ResponseTypeValueSwapCard and return ensureReaderAndCardPresenceAndResolveNextState(self, keycardFlowType, keycardEvent, controller)
keycardEvent.error.len > 0 and
keycardEvent.error == ErrorNotAKeycard:
return createState(StateType.NotKeycard, self.flowType, nil)
if keycardFlowType == ResponseTypeValueKeycardFlowResult and
keycardEvent.error.len > 0:
if keycardEvent.error == ErrorOk:
return createState(StateType.FactoryResetSuccess, self.flowType, nil)
if keycardEvent.error == ErrorNoKeys:
return createState(StateType.KeycardEmpty, self.flowType, nil)
controller.setContainsMetadata(keycardEvent.error != ErrorNoData)
return createState(StateType.RecognizedKeycard, self.flowType, nil)

View File

@ -9,11 +9,15 @@ proc delete*(self: RecognizedKeycardState) =
self.State.delete self.State.delete
method executePrimaryCommand*(self: RecognizedKeycardState, controller: Controller) = method executePrimaryCommand*(self: RecognizedKeycardState, controller: Controller) =
if self.flowType == FlowType.FactoryReset: if self.flowType == FlowType.FactoryReset or
self.flowType == FlowType.SetupNewKeycard:
controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false) controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false)
method getNextSecondaryState*(self: RecognizedKeycardState, controller: Controller): State = method getNextSecondaryState*(self: RecognizedKeycardState, controller: Controller): State =
if controller.containsMetadata(): if self.flowType == FlowType.FactoryReset:
discard # from here we will jump to enter pin view once we add that in keycard settings if controller.containsMetadata():
else: return createState(StateType.EnterPin, self.flowType, nil)
return createState(StateType.FactoryResetConfirmation, self.flowType, nil) else:
return createState(StateType.FactoryResetConfirmation, self.flowType, nil)
if self.flowType == FlowType.SetupNewKeycard:
return createState(StateType.CreatePin, self.flowType, self.getBackState)

View File

@ -0,0 +1,34 @@
type
RepeatPinState* = ref object of State
proc newRepeatPinState*(flowType: FlowType, backState: State): RepeatPinState =
result = RepeatPinState()
result.setup(flowType, StateType.RepeatPin, backState)
proc delete*(self: RepeatPinState) =
self.State.delete
method executeBackCommand*(self: RepeatPinState, controller: Controller) =
controller.setPin("")
controller.setPinMatch(false)
method executeSecondaryCommand*(self: RepeatPinState, controller: Controller) =
if self.flowType == FlowType.SetupNewKeycard:
controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false)
method executeTertiaryCommand*(self: RepeatPinState, controller: Controller) =
if not controller.getPinMatch():
return
if self.flowType == FlowType.SetupNewKeycard:
controller.storePinToKeycard(controller.getPin(), controller.generateRandomPUK())
method resolveKeycardNextState*(self: RepeatPinState, keycardFlowType: string, keycardEvent: KeycardEvent,
controller: Controller): State =
let state = ensureReaderAndCardPresence(self, keycardFlowType, keycardEvent, controller)
if not state.isNil:
return state
if self.flowType == FlowType.SetupNewKeycard:
if keycardFlowType == ResponseTypeValueEnterMnemonic and
keycardEvent.error.len > 0 and
keycardEvent.error == ErrorLoadingKeys:
return createState(StateType.PinSet, self.flowType, nil)

View File

@ -0,0 +1,16 @@
type
SeedPhraseDisplayState* = ref object of State
proc newSeedPhraseDisplayState*(flowType: FlowType, backState: State): SeedPhraseDisplayState =
result = SeedPhraseDisplayState()
result.setup(flowType, StateType.SeedPhraseDisplay, backState)
proc delete*(self: SeedPhraseDisplayState) =
self.State.delete
method executeSecondaryCommand*(self: SeedPhraseDisplayState, controller: Controller) =
if self.flowType == FlowType.SetupNewKeycard:
controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false)
method getNextPrimaryState*(self: SeedPhraseDisplayState, controller: Controller): State =
return createState(StateType.SeedPhraseEnterWords, self.flowType, self)

View File

@ -0,0 +1,31 @@
import strutils
type
SeedPhraseEnterWordsState* = ref object of State
proc newSeedPhraseEnterWordsState*(flowType: FlowType, backState: State): SeedPhraseEnterWordsState =
result = SeedPhraseEnterWordsState()
result.setup(flowType, StateType.SeedPhraseEnterWords, backState)
proc delete*(self: SeedPhraseEnterWordsState) =
self.State.delete
method executePrimaryCommand*(self: SeedPhraseEnterWordsState, controller: Controller) =
let mnemonic = controller.getMnemonic()
controller.storeSeedPhraseToKeycard(mnemonic.split(" ").len, mnemonic)
method executeSecondaryCommand*(self: SeedPhraseEnterWordsState, controller: Controller) =
if self.flowType == FlowType.SetupNewKeycard:
controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false)
method resolveKeycardNextState*(self: SeedPhraseEnterWordsState, keycardFlowType: string, keycardEvent: KeycardEvent,
controller: Controller): State =
let state = ensureReaderAndCardPresence(self, keycardFlowType, keycardEvent, controller)
if not state.isNil:
return state
if self.flowType == FlowType.SetupNewKeycard:
if keycardFlowType == ResponseTypeValueKeycardFlowResult and
keycardEvent.keyUid.len > 0:
controller.setKeyUid(keycardEvent.keyUid)
controller.removeMnemonic()
return createState(StateType.MigratingKeyPair, self.flowType, nil)

View File

@ -0,0 +1,21 @@
type
SelectExistingKeyPairState* = ref object of State
proc newSelectExistingKeyPairState*(flowType: FlowType, backState: State): SelectExistingKeyPairState =
result = SelectExistingKeyPairState()
result.setup(flowType, StateType.SelectExistingKeyPair, backState)
proc delete*(self: SelectExistingKeyPairState) =
self.State.delete
method executePrimaryCommand*(self: SelectExistingKeyPairState, controller: Controller) =
if self.flowType == FlowType.SetupNewKeycard:
controller.runLoadAccountFlow()
method executeSecondaryCommand*(self: SelectExistingKeyPairState, controller: Controller) =
if self.flowType == FlowType.SetupNewKeycard:
controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false)
method resolveKeycardNextState*(self: SelectExistingKeyPairState, keycardFlowType: string, keycardEvent: KeycardEvent,
controller: Controller): State =
return ensureReaderAndCardPresenceAndResolveNextState(self, keycardFlowType, keycardEvent, controller)

View File

@ -1,24 +1,39 @@
import ../controller import ../controller
from ../../../../../app_service/service/keycard/service import KeycardEvent, KeyDetails from ../../../../../app_service/service/keycard/service import KeycardEvent, KeyDetails
from ../io_interface import FlowType
export KeycardEvent, KeyDetails export FlowType, KeycardEvent, KeyDetails
type FlowType* {.pure.} = enum
General = "General"
FactoryReset = "FactoryReset"
type StateType* {.pure.} = enum type StateType* {.pure.} = enum
NoState = "NoState" NoState = "NoState"
PluginReader = "PluginReader" PluginReader = "PluginReader"
ReadingKeycard = "ReadingKeycard" ReadingKeycard = "ReadingKeycard"
InsertKeycard = "InsertKeycard" InsertKeycard = "InsertKeycard"
KeycardInserted = "KeycardInserted"
CreatePin = "CreatePin"
RepeatPin = "RepeatPin"
PinSet = "PinSet"
PinVerified = "PinVerified"
EnterPin = "EnterPin" EnterPin = "EnterPin"
WrongPin = "WrongPin"
MaxPinRetriesReached = "MaxPinRetriesReached"
FactoryResetConfirmation = "FactoryResetConfirmation" FactoryResetConfirmation = "FactoryResetConfirmation"
FactoryResetConfirmationDisplayMetadata = "FactoryResetConfirmationDisplayMetadata"
FactoryResetSuccess = "FactoryResetSuccess" FactoryResetSuccess = "FactoryResetSuccess"
KeycardEmptyMetadata = "KeycardEmptyMetadata"
KeycardMetadataDisplay = "KeycardMetadataDisplay"
KeycardEmpty = "KeycardEmpty" KeycardEmpty = "KeycardEmpty"
KeycardNotEmpty = "KeycardNotEmpty"
NotKeycard = "NotKeycard" NotKeycard = "NotKeycard"
RecognizedKeycard = "RecognizedKeycard" RecognizedKeycard = "RecognizedKeycard"
SelectExistingKeyPair = "SelectExistingKeyPair"
EnterSeedPhrase = "EnterSeedPhrase"
WrongSeedPhrase = "WrongSeedPhrase"
SeedPhraseDisplay = "SeedPhraseDisplay"
SeedPhraseEnterWords = "SeedPhraseEnterWords"
KeyPairMigrateSuccess = "KeyPairMigrateSuccess"
KeyPairMigrateFailure = "KeyPairMigrateFailure"
MigratingKeyPair = "MigratingKeyPair"
## This is the base class for all state we may have in onboarding/login flow. ## This is the base class for all state we may have in onboarding/login flow.
@ -70,6 +85,10 @@ method getNextPrimaryState*(self: State, controller: Controller): State {.inlin
method getNextSecondaryState*(self: State, controller: Controller): State {.inline base.} = method getNextSecondaryState*(self: State, controller: Controller): State {.inline base.} =
return nil return nil
## Returns next state instance in case the "tertiary" action is triggered
method getNextTertiaryState*(self: State, controller: Controller): State {.inline base.} =
return nil
## This method is executed in case "back" button is clicked ## This method is executed in case "back" button is clicked
method executeBackCommand*(self: State, controller: Controller) {.inline base.} = method executeBackCommand*(self: State, controller: Controller) {.inline base.} =
discard discard
@ -82,6 +101,10 @@ method executePrimaryCommand*(self: State, controller: Controller) {.inline base
method executeSecondaryCommand*(self: State, controller: Controller) {.inline base.} = method executeSecondaryCommand*(self: State, controller: Controller) {.inline base.} =
discard discard
## This method is executed in case "tertiary" action is triggered
method executeTertiaryCommand*(self: State, controller: Controller) {.inline base.} =
discard
## This method is used for handling aync responses for keycard related states ## This method is used for handling aync responses for keycard related states
method resolveKeycardNextState*(self: State, keycardFlowType: string, keycardEvent: KeycardEvent, method resolveKeycardNextState*(self: State, keycardFlowType: string, keycardEvent: KeycardEvent,
controller: Controller): State {.inline base.} = controller: Controller): State {.inline base.} =

View File

@ -1,6 +1,7 @@
import chronicles import parseutils, chronicles
import ../../../../../app_service/service/keycard/constants import ../../../../../app_service/service/keycard/constants
import ../controller import ../controller
from ../../../../../app_service/service/keycard/service import KCSFlowType
from ../../../../../app_service/service/keycard/service import PINLengthForStatusApp from ../../../../../app_service/service/keycard/service import PINLengthForStatusApp
from ../../../../../app_service/service/keycard/service import PUKLengthForStatusApp from ../../../../../app_service/service/keycard/service import PUKLengthForStatusApp
import state import state
@ -8,37 +9,217 @@ import state
logScope: logScope:
topics = "startup-module-state-factory" topics = "startup-module-state-factory"
# The following constants will be used in bitwise operation
type PredefinedKeycardData* {.pure.} = enum
WronglyInsertedCard = 1
HideKeyPair = 2
WrongSeedPhrase = 4
# Forward declaration # Forward declaration
proc createState*(stateToBeCreated: StateType, flowType: FlowType, backState: State): State proc createState*(stateToBeCreated: StateType, flowType: FlowType, backState: State): State
proc getPredefinedKeycardData*(currValue: string, value: PredefinedKeycardData, add: bool): string
proc ensureReaderAndCardPresence*(state: State, keycardFlowType: string, keycardEvent: KeycardEvent, controller: Controller): State
proc ensureReaderAndCardPresenceAndResolveNextState*(state: State, keycardFlowType: string, keycardEvent: KeycardEvent, controller: Controller): State
include enter_pin_state include create_pin_state
include enter_pin_state
include enter_seed_phrase_state
include factory_reset_confirmation_displayed_metadata_state
include factory_reset_confirmation_state include factory_reset_confirmation_state
include factory_reset_success_state include factory_reset_success_state
include insert_keycard_state include insert_keycard_state
include key_pair_migrate_failure_state
include key_pair_migrate_success_state
include keycard_empty_metadata_state
include keycard_empty_state include keycard_empty_state
include keycard_inserted_state
include keycard_metadata_display_state
include keycard_not_empty_state
include max_pin_retries_reached_state
include migrating_key_pair_state
include not_keycard_state include not_keycard_state
include pin_set_state
include pin_verified_state
include plugin_reader_state include plugin_reader_state
include reading_keycard_state include reading_keycard_state
include recognized_keycard_state include recognized_keycard_state
include repeat_pin_state
include seed_phrase_display_state
include seed_phrase_enter_words_state
include select_existing_key_pair_state
include wrong_pin_state
include wrong_seed_phrase_state
proc getPredefinedKeycardData*(currValue: string, value: PredefinedKeycardData, add: bool): string =
var currNum: int
try:
if add:
if parseInt(currValue, currNum) == 0:
return $(value.int)
else:
return $(currNum or value.int)
else:
if parseInt(currValue, currNum) == 0:
return ""
else:
return $(currNum and (not value.int))
except:
return if add: $(value.int) else: ""
proc createState*(stateToBeCreated: StateType, flowType: FlowType, backState: State): State = proc createState*(stateToBeCreated: StateType, flowType: FlowType, backState: State): State =
if stateToBeCreated == StateType.CreatePin:
return newCreatePinState(flowType, backState)
if stateToBeCreated == StateType.EnterPin: if stateToBeCreated == StateType.EnterPin:
return newEnterPinState(flowType, backState) return newEnterPinState(flowType, backState)
if stateToBeCreated == StateType.EnterSeedPhrase:
return newEnterSeedPhraseState(flowType, backState)
if stateToBeCreated == StateType.FactoryResetConfirmationDisplayMetadata:
return newFactoryResetConfirmationDisplayMetadataState(flowType, backState)
if stateToBeCreated == StateType.FactoryResetConfirmation: if stateToBeCreated == StateType.FactoryResetConfirmation:
return newFactoryResetConfirmationState(flowType, backState) return newFactoryResetConfirmationState(flowType, backState)
if stateToBeCreated == StateType.FactoryResetSuccess: if stateToBeCreated == StateType.FactoryResetSuccess:
return newFactoryResetSuccessState(flowType, backState) return newFactoryResetSuccessState(flowType, backState)
if stateToBeCreated == StateType.InsertKeycard: if stateToBeCreated == StateType.InsertKeycard:
return newInsertKeycardState(flowType, backState) return newInsertKeycardState(flowType, backState)
if stateToBeCreated == StateType.KeyPairMigrateFailure:
return newKeyPairMigrateFailureState(flowType, backState)
if stateToBeCreated == StateType.KeyPairMigrateSuccess:
return newKeyPairMigrateSuccessState(flowType, backState)
if stateToBeCreated == StateType.KeycardInserted:
return newKeycardInsertedState(flowType, backState)
if stateToBeCreated == StateType.KeycardEmptyMetadata:
return newKeycardEmptyMetadataState(flowType, backState)
if stateToBeCreated == StateType.KeycardEmpty: if stateToBeCreated == StateType.KeycardEmpty:
return newKeycardEmptyState(flowType, backState) return newKeycardEmptyState(flowType, backState)
if stateToBeCreated == StateType.KeycardMetadataDisplay:
return newKeycardMetadataDisplayState(flowType, backState)
if stateToBeCreated == StateType.KeycardNotEmpty:
return newKeycardNotEmptyState(flowType, backState)
if stateToBeCreated == StateType.MaxPinRetriesReached:
return newMaxPinRetriesReachedState(flowType, backState)
if stateToBeCreated == StateType.MigratingKeyPair:
return newMigratingKeyPairState(flowType, backState)
if stateToBeCreated == StateType.NotKeycard: if stateToBeCreated == StateType.NotKeycard:
return newNotKeycardState(flowType, backState) return newNotKeycardState(flowType, backState)
if stateToBeCreated == StateType.PinSet:
return newPinSetState(flowType, backState)
if stateToBeCreated == StateType.PinVerified:
return newPinVerifiedState(flowType, backState)
if stateToBeCreated == StateType.PluginReader: if stateToBeCreated == StateType.PluginReader:
return newPluginReaderState(flowType, backState) return newPluginReaderState(flowType, backState)
if stateToBeCreated == StateType.ReadingKeycard: if stateToBeCreated == StateType.ReadingKeycard:
return newReadingKeycardState(flowType, backState) return newReadingKeycardState(flowType, backState)
if stateToBeCreated == StateType.RecognizedKeycard: if stateToBeCreated == StateType.RecognizedKeycard:
return newRecognizedKeycardState(flowType, backState) return newRecognizedKeycardState(flowType, backState)
if stateToBeCreated == StateType.RepeatPin:
return newRepeatPinState(flowType, backState)
if stateToBeCreated == StateType.SeedPhraseDisplay:
return newSeedPhraseDisplayState(flowType, backState)
if stateToBeCreated == StateType.SeedPhraseEnterWords:
return newSeedPhraseEnterWordsState(flowType, backState)
if stateToBeCreated == StateType.SelectExistingKeyPair:
return newSelectExistingKeyPairState(flowType, backState)
if stateToBeCreated == StateType.WrongPin:
return newWrongPinState(flowType, backState)
if stateToBeCreated == StateType.WrongSeedPhrase:
return newWrongSeedPhraseState(flowType, backState)
error "No implementation available for state ", state=stateToBeCreated error "No implementation available for state ", state=stateToBeCreated
proc ensureReaderAndCardPresence*(state: State, keycardFlowType: string, keycardEvent: KeycardEvent, controller: Controller): State =
## Handling factory reset flow
if state.flowType == FlowType.FactoryReset:
if keycardFlowType == ResponseTypeValueKeycardFlowResult and
keycardEvent.error.len > 0 and
keycardEvent.error == ErrorConnection:
controller.resumeCurrentFlowLater()
if state.stateType == StateType.PluginReader:
return nil
return createState(StateType.PluginReader, state.flowType, nil)
if keycardFlowType == ResponseTypeValueInsertCard and
keycardEvent.error.len > 0 and
keycardEvent.error == ErrorConnection:
if state.stateType == StateType.InsertKeycard:
return nil
return createState(StateType.InsertKeycard, state.flowType, state)
if keycardFlowType == ResponseTypeValueCardInserted:
controller.setKeycardData(getPredefinedKeycardData(controller.getKeycardData(), PredefinedKeycardData.WronglyInsertedCard, add = false))
return createState(StateType.KeycardInserted, state.flowType, nil)
## Handling setup new keycard flow
if state.flowType == FlowType.SetupNewKeycard:
if keycardFlowType == ResponseTypeValueKeycardFlowResult and
keycardEvent.error.len > 0 and
keycardEvent.error == ErrorConnection:
controller.resumeCurrentFlowLater()
if state.stateType == StateType.PluginReader:
return nil
return createState(StateType.PluginReader, state.flowType, state)
if keycardFlowType == ResponseTypeValueInsertCard and
keycardEvent.error.len > 0 and
keycardEvent.error == ErrorConnection:
if state.stateType == StateType.InsertKeycard:
return nil
return createState(StateType.InsertKeycard, state.flowType, state)
if keycardFlowType == ResponseTypeValueCardInserted:
controller.setKeycardData(getPredefinedKeycardData(controller.getKeycardData(), PredefinedKeycardData.WronglyInsertedCard, add = false))
return createState(StateType.KeycardInserted, state.flowType, state.getBackState)
proc ensureReaderAndCardPresenceAndResolveNextState*(state: State, keycardFlowType: string, keycardEvent: KeycardEvent, controller: Controller): State =
let ensureState = ensureReaderAndCardPresence(state, keycardFlowType, keycardEvent, controller)
if not ensureState.isNil:
return ensureState
## Handling factory reset flow
if state.flowType == FlowType.FactoryReset:
if keycardFlowType == ResponseTypeValueEnterPIN:
return createState(StateType.EnterPin, state.flowType, nil)
if keycardFlowType == ResponseTypeValueEnterPUK and
keycardEvent.error.len == 0:
if keycardEvent.pinRetries == 0 and keycardEvent.pukRetries > 0:
return createState(StateType.MaxPinRetriesReached, state.flowType, nil)
if keycardFlowType == ResponseTypeValueSwapCard and
keycardEvent.error.len > 0 and
keycardEvent.error == ErrorNotAKeycard:
return createState(StateType.NotKeycard, state.flowType, nil)
if keycardFlowType == ResponseTypeValueKeycardFlowResult:
if keycardEvent.error.len > 0:
if keycardEvent.error == ErrorOk:
return createState(StateType.FactoryResetSuccess, state.flowType, nil)
if keycardEvent.error == ErrorNoKeys:
return createState(StateType.KeycardEmpty, state.flowType, nil)
if keycardEvent.error == ErrorNoData:
return createState(StateType.KeycardEmptyMetadata, state.flowType, nil)
if keycardEvent.error.len == 0:
if keycardEvent.cardMetadata.name.len > 0 and keycardEvent.cardMetadata.walletAccounts.len > 0:
controller.setContainsMetadata(true)
return createState(StateType.RecognizedKeycard, state.flowType, nil)
## Handling setup new keycard flow
if state.flowType == FlowType.SetupNewKeycard:
if keycardFlowType == ResponseTypeValueSwapCard and
keycardEvent.error.len > 0:
if keycardEvent.error == ErrorNotAKeycard:
return createState(StateType.NotKeycard, state.flowType, nil)
if keycardEvent.error == ErrorHasKeys:
return createState(StateType.KeycardNotEmpty, state.flowType, nil)
if keycardFlowType == ResponseTypeValueEnterPIN:
if controller.getCurrentKeycardServiceFlow() == KCSFlowType.GetMetadata:
return createState(StateType.EnterPin, state.flowType, nil)
return createState(StateType.KeycardNotEmpty, state.flowType, nil)
if keycardFlowType == ResponseTypeValueEnterPUK and
keycardEvent.error.len == 0:
if keycardEvent.pinRetries == 0 and keycardEvent.pukRetries > 0:
return createState(StateType.MaxPinRetriesReached, state.flowType, nil)
if keycardFlowType == ResponseTypeValueKeycardFlowResult and
keycardEvent.error.len > 0:
controller.setKeycardData("")
if keycardEvent.error == ErrorOk:
return createState(StateType.FactoryResetSuccess, state.flowType, nil)
if keycardEvent.error == ErrorNoData:
return createState(StateType.KeycardEmptyMetadata, state.flowType, nil)
if keycardEvent.error == ErrorNoKeys:
return createState(StateType.KeycardEmptyMetadata, state.flowType, nil)
if keycardFlowType == ResponseTypeValueEnterNewPIN and
keycardEvent.error.len > 0 and
keycardEvent.error == ErrorRequireInit:
return createState(StateType.RecognizedKeycard, state.flowType, state.getBackState)

View File

@ -55,4 +55,8 @@ QtObject:
proc secondaryActionClicked*(self: StateWrapper) {.signal.} proc secondaryActionClicked*(self: StateWrapper) {.signal.}
proc doSecondaryAction*(self: StateWrapper) {.slot.} = proc doSecondaryAction*(self: StateWrapper) {.slot.} =
self.secondaryActionClicked() self.secondaryActionClicked()
proc tertiaryActionClicked*(self: StateWrapper) {.signal.}
proc doTertiaryAction*(self: StateWrapper) {.slot.} =
self.tertiaryActionClicked()

View File

@ -0,0 +1,62 @@
type
WrongPinState* = ref object of State
proc newWrongPinState*(flowType: FlowType, backState: State): WrongPinState =
result = WrongPinState()
result.setup(flowType, StateType.WrongPin, backState)
proc delete*(self: WrongPinState) =
self.State.delete
method getNextPrimaryState*(self: WrongPinState, controller: Controller): State =
if self.flowType == FlowType.FactoryReset or
self.flowType == FlowType.SetupNewKeycard:
return createState(StateType.FactoryResetConfirmation, self.flowType, self)
return nil
method executeSecondaryCommand*(self: WrongPinState, controller: Controller) =
if self.flowType == FlowType.FactoryReset or
self.flowType == FlowType.SetupNewKeycard:
controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false)
method executeTertiaryCommand*(self: WrongPinState, controller: Controller) =
if self.flowType == FlowType.FactoryReset or
self.flowType == FlowType.SetupNewKeycard:
if controller.getPin().len == PINLengthForStatusApp:
controller.enterKeycardPin(controller.getPin())
method resolveKeycardNextState*(self: WrongPinState, keycardFlowType: string, keycardEvent: KeycardEvent,
controller: Controller): State =
let state = ensureReaderAndCardPresence(self, keycardFlowType, keycardEvent, controller)
if not state.isNil:
return state
if self.flowType == FlowType.FactoryReset:
if keycardFlowType == ResponseTypeValueEnterPIN and
keycardEvent.error.len > 0 and
keycardEvent.error == RequestParamPIN:
controller.setKeycardData($keycardEvent.pinRetries)
if keycardEvent.pinRetries > 0:
return self
return createState(StateType.MaxPinRetriesReached, self.flowType, nil)
if keycardFlowType == ResponseTypeValueEnterPUK and
keycardEvent.error.len == 0:
if keycardEvent.pinRetries == 0 and keycardEvent.pukRetries > 0:
return createState(StateType.MaxPinRetriesReached, self.flowType, nil)
if keycardFlowType == ResponseTypeValueKeycardFlowResult:
controller.setMetadataFromKeycard(keycardEvent.cardMetadata)
return createState(StateType.PinVerified, self.flowType, nil)
if self.flowType == FlowType.SetupNewKeycard:
if keycardFlowType == ResponseTypeValueEnterPIN and
keycardEvent.error.len > 0 and
keycardEvent.error == RequestParamPIN:
controller.setKeycardData($keycardEvent.pinRetries)
if keycardEvent.pinRetries > 0:
return self
return createState(StateType.MaxPinRetriesReached, self.flowType, nil)
if keycardFlowType == ResponseTypeValueEnterPUK and
keycardEvent.error.len == 0:
if keycardEvent.pinRetries == 0 and keycardEvent.pukRetries > 0:
return createState(StateType.MaxPinRetriesReached, self.flowType, nil)
if keycardFlowType == ResponseTypeValueKeycardFlowResult:
controller.setMetadataFromKeycard(keycardEvent.cardMetadata)
return createState(StateType.PinVerified, self.flowType, nil)

View File

@ -0,0 +1,40 @@
import os
type
WrongSeedPhraseState* = ref object of State
verifiedSeedPhrase: bool
proc newWrongSeedPhraseState*(flowType: FlowType, backState: State): WrongSeedPhraseState =
result = WrongSeedPhraseState()
result.setup(flowType, StateType.WrongSeedPhrase, backState)
result.verifiedSeedPhrase = false
proc delete*(self: WrongSeedPhraseState) =
self.State.delete
method executePrimaryCommand*(self: WrongSeedPhraseState, controller: Controller) =
if self.flowType == FlowType.SetupNewKeycard:
controller.setKeycardData(getPredefinedKeycardData(controller.getKeycardData(), PredefinedKeycardData.WrongSeedPhrase, add = false))
sleep(500) # just to shortly remove text on the UI side
self.verifiedSeedPhrase = controller.validSeedPhrase(controller.getSeedPhrase()) and
controller.seedPhraseRefersToLoggedInUser(controller.getSeedPhrase())
if self.verifiedSeedPhrase:
controller.storeSeedPhraseToKeycard(controller.getSeedPhraseLength(), controller.getSeedPhrase())
else:
controller.setKeycardData(getPredefinedKeycardData(controller.getKeycardData(), PredefinedKeycardData.WrongSeedPhrase, add = true))
method executeSecondaryCommand*(self: WrongSeedPhraseState, controller: Controller) =
if self.flowType == FlowType.SetupNewKeycard:
controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false)
method resolveKeycardNextState*(self: WrongSeedPhraseState, keycardFlowType: string, keycardEvent: KeycardEvent,
controller: Controller): State =
let state = ensureReaderAndCardPresence(self, keycardFlowType, keycardEvent, controller)
if not state.isNil:
return state
if self.flowType == FlowType.SetupNewKeycard:
if keycardFlowType == ResponseTypeValueKeycardFlowResult and
keycardEvent.keyUid.len > 0:
controller.setKeyUid(keycardEvent.keyUid)
controller.setKeycardData(getPredefinedKeycardData(controller.getKeycardData(), PredefinedKeycardData.WrongSeedPhrase, add = false))
return createState(StateType.MigratingKeyPair, self.flowType, nil)

View File

@ -1,13 +1,20 @@
import NimQml import NimQml
import ../../../../app/core/eventemitter import ../../../../app/core/eventemitter
from ../../../../app_service/service/keycard/service import KeycardEvent, KeyDetails from ../../../../app_service/service/keycard/service import KeycardEvent, CardMetadata, KeyDetails
import models/key_pair_item
const SignalSharedKeycarModuleDisplayPopup* = "SignalSharedKeycarModuleDisplayPopup"
const SignalSharedKeycarModuleFlowTerminated* = "sharedKeycarModuleFlowTerminated" const SignalSharedKeycarModuleFlowTerminated* = "sharedKeycarModuleFlowTerminated"
type type
SharedKeycarModuleFlowTerminatedArgs* = ref object of Args SharedKeycarModuleFlowTerminatedArgs* = ref object of Args
lastStepInTheCurrentFlow*: bool lastStepInTheCurrentFlow*: bool
type FlowType* {.pure.} = enum
General = "General"
FactoryReset = "FactoryReset"
SetupNewKeycard = "SetupNewKeycard"
type type
AccessInterface* {.pure inheritable.} = ref object of RootObj AccessInterface* {.pure inheritable.} = ref object of RootObj
@ -17,6 +24,9 @@ method delete*(self: AccessInterface) {.base.} =
method getModuleAsVariant*(self: AccessInterface): QVariant {.base.} = method getModuleAsVariant*(self: AccessInterface): QVariant {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
method getKeycardData*(self: AccessInterface): string {.base.} =
raise newException(ValueError, "No implementation available")
method setKeycardData*(self: AccessInterface, value: string) {.base.} = method setKeycardData*(self: AccessInterface, value: string) {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
@ -29,13 +39,51 @@ method onPrimaryActionClicked*(self: AccessInterface) {.base.} =
method onSecondaryActionClicked*(self: AccessInterface) {.base.} = method onSecondaryActionClicked*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
method onTertiaryActionClicked*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method onKeycardResponse*(self: AccessInterface, keycardFlowType: string, keycardEvent: KeycardEvent) {.base.} = method onKeycardResponse*(self: AccessInterface, keycardFlowType: string, keycardEvent: KeycardEvent) {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
method runFactoryResetFlow*(self: AccessInterface) {.base.} = method runFlow*(self: AccessInterface, flowToRun: FlowType) {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
method setPin*(self: AccessInterface, value: string) {.base.} =
raise newException(ValueError, "No implementation available")
method setPassword*(self: AccessInterface, value: string) {.base.} =
raise newException(ValueError, "No implementation available")
method checkRepeatedKeycardPinWhileTyping*(self: AccessInterface, pin: string): bool {.base.} =
raise newException(ValueError, "No implementation available")
method getMnemonic*(self: AccessInterface): string {.base.} =
raise newException(ValueError, "No implementation available")
method setSeedPhrase*(self: AccessInterface, value: string) {.base.} =
raise newException(ValueError, "No implementation available")
method getSeedPhrase*(self: AccessInterface): string {.base.} =
raise newException(ValueError, "No implementation available")
method validSeedPhrase*(self: AccessInterface, value: string): bool {.base.} =
raise newException(ValueError, "No implementation available")
method setSelectedKeyPair*(self: AccessInterface, item: KeyPairItem) {.base.} =
raise newException(ValueError, "No implementation available")
method setKeyPairStoredOnKeycard*(self: AccessInterface, cardMetadata: CardMetadata) {.base.} =
raise newException(ValueError, "No implementation available")
method loggedInUserUsesBiometricLogin*(self: AccessInterface): bool {.base.} =
raise newException(ValueError, "No implementation available")
method migratingProfileKeyPair*(self: AccessInterface): bool {.base.} =
raise newException(ValueError, "No implementation available")
method isProfileKeyPairMigrated*(self: AccessInterface): bool {.base.} =
raise newException(ValueError, "No implementation available")
type type
DelegateInterface* = concept c DelegateInterface* = concept c
#c.startupDidLoad()
#c.userLoggedIn()

View File

@ -0,0 +1,82 @@
import strformat, marshal
type
KeyPairType* {.pure.} = enum
Unknown = -1
Profile
SeedImport
PrivateKeyImport
type
WalletAccountDetails = tuple
name: string
path: string
address: string
emoji: string
color: string
icon: string
balance: float64
type
KeyPairItem* = ref object of RootObj
pubKey: string
name: string
image: string
icon: string
derivedFrom: string
pairType: KeyPairType
accounts: seq[WalletAccountDetails]
proc initKeyPairItem*(
pubKey: string,
name: string,
image: string,
icon: string,
pairType: KeyPairType,
derivedFrom: string
): KeyPairItem =
result = KeyPairItem()
result.pubKey = pubKey
result.name = name
result.image = image
result.icon = icon
result.pairType = pairType
result.derivedFrom = derivedFrom
proc `$`*(self: KeyPairItem): string =
result = fmt"""KeyPairItem[
pubKey: {self.pubkey},
name: {self.name},
image: {self.image},
icon: {self.icon},
pairType: {$self.pairType},
derivedFrom: {self.derivedFrom},
accounts: {$self.accounts}
]"""
proc pubKey*(self: KeyPairItem): string {.inline.} =
self.pubKey
proc name*(self: KeyPairItem): string {.inline.} =
self.name
proc image*(self: KeyPairItem): string {.inline.} =
self.image
proc icon*(self: KeyPairItem): string {.inline.} =
self.icon
proc pairType*(self: KeyPairItem): KeyPairType {.inline.} =
self.pairType
proc derivedFrom*(self: KeyPairItem): string {.inline.} =
self.derivedFrom
proc addAccount*(self: KeyPairItem, name, path, address, emoji, color, icon: string, balance: float64) {.inline.} =
self.accounts.add((name: name, path: path, address: address, emoji: emoji, color: color, icon: icon, balance: balance))
proc accounts*(self: KeyPairItem): string {.inline.} =
return $$self.accounts
proc accountsAsArr*(self: KeyPairItem): seq[WalletAccountDetails] {.inline.} =
return self.accounts

View File

@ -0,0 +1,90 @@
import NimQml, Tables, strformat
import key_pair_item
type
ModelRole {.pure.} = enum
PubKey = UserRole + 1
Name
Image
Icon
PairType
Accounts
DerivedFrom
QtObject:
type
KeyPairModel* = ref object of QAbstractListModel
items: seq[KeyPairItem]
proc delete(self: KeyPairModel) =
self.items = @[]
self.QAbstractListModel.delete
proc setup(self: KeyPairModel) =
self.QAbstractListModel.setup
proc newKeyPairModel*(): KeyPairModel =
new(result, delete)
result.setup
proc countChanged(self: KeyPairModel) {.signal.}
proc getCount*(self: KeyPairModel): int {.slot.} =
self.items.len
QtProperty[int]count:
read = getCount
notify = countChanged
proc setItems*(self: KeyPairModel, items: seq[KeyPairItem]) =
self.beginResetModel()
self.items = items
self.endResetModel()
self.countChanged()
proc `$`*(self: KeyPairModel): string =
for i in 0 ..< self.items.len:
result &= fmt"""KeyPairModel:
[{i}]:({$self.items[i]})
"""
method rowCount(self: KeyPairModel, index: QModelIndex = nil): int =
return self.items.len
method roleNames(self: KeyPairModel): Table[int, string] =
{
ModelRole.PubKey.int: "pubKey",
ModelRole.Name.int: "name",
ModelRole.Image.int: "image",
ModelRole.Icon.int: "icon",
ModelRole.PairType.int: "pairType",
ModelRole.Accounts.int: "accounts",
ModelRole.DerivedFrom.int: "derivedFrom"
}.toTable
method data(self: KeyPairModel, index: QModelIndex, role: int): QVariant =
if (not index.isValid):
return
if (index.row < 0 or index.row >= self.items.len):
return
let item = self.items[index.row]
let enumRole = role.ModelRole
case enumRole:
of ModelRole.PubKey:
result = newQVariant(item.pubKey)
of ModelRole.Name:
result = newQVariant(item.name)
of ModelRole.Image:
result = newQVariant(item.image)
of ModelRole.Icon:
result = newQVariant(item.icon)
of ModelRole.PairType:
result = newQVariant(item.pairType.int)
of ModelRole.Accounts:
result = newQVariant(item.accounts)
of ModelRole.DerivedFrom:
result = newQVariant(item.derivedFrom)
proc findItemByDerivedFromAddress*(self: KeyPairModel, address: string): KeyPairItem =
for i in 0 ..< self.items.len:
if(self.items[i].derivedFrom == address):
return self.items[i]
return nil

View File

@ -0,0 +1,75 @@
import NimQml
import key_pair_item
QtObject:
type KeyPairSelectedItem* = ref object of QObject
item: KeyPairItem
proc delete*(self: KeyPairSelectedItem) =
self.QObject.delete
proc newKeyPairSelectedItem*(): KeyPairSelectedItem =
new(result, delete)
result.QObject.setup
proc keyPairSelectedItemChanged*(self: KeyPairSelectedItem) {.signal.}
proc setItem*(self: KeyPairSelectedItem, item: KeyPairItem) =
self.item = item
self.keyPairSelectedItemChanged()
proc getPubKey*(self: KeyPairSelectedItem): string {.slot.} =
if(self.item.isNil):
return ""
return self.item.pubKey()
QtProperty[string] pubKey:
read = getPubKey
notify = keyPairSelectedItemChanged
proc getName*(self: KeyPairSelectedItem): string {.slot.} =
if(self.item.isNil):
return ""
return self.item.name()
QtProperty[string] name:
read = getName
notify = keyPairSelectedItemChanged
proc getImage*(self: KeyPairSelectedItem): string {.slot.} =
if(self.item.isNil):
return ""
return self.item.image()
QtProperty[string] image:
read = getImage
notify = keyPairSelectedItemChanged
proc getIcon*(self: KeyPairSelectedItem): string {.slot.} =
if(self.item.isNil):
return ""
return self.item.icon()
QtProperty[string] icon:
read = getIcon
notify = keyPairSelectedItemChanged
proc getPairType*(self: KeyPairSelectedItem): int {.slot.} =
if(self.item.isNil):
return KeyPairType.Profile.int
return self.item.pairType().int
QtProperty[int] pairType:
read = getPairType
notify = keyPairSelectedItemChanged
proc getDerivedFrom*(self: KeyPairSelectedItem): string {.slot.} =
if(self.item.isNil):
return ""
return self.item.derivedFrom()
QtProperty[string] derivedFrom:
read = getDerivedFrom
notify = keyPairSelectedItemChanged
proc getAccounts*(self: KeyPairSelectedItem): string {.slot.} =
if(self.item.isNil):
return ""
return self.item.accounts()
QtProperty[string] accounts:
read = getAccounts
notify = keyPairSelectedItemChanged

View File

@ -1,11 +1,16 @@
import NimQml, chronicles import NimQml, random, strutils, marshal, chronicles
import io_interface import io_interface
import view, controller import view, controller
import internal/[state, state_factory] import internal/[state, state_factory]
import models/[key_pair_model, key_pair_item]
import ../../../global/global_singleton
import ../../../core/eventemitter import ../../../core/eventemitter
import ../../../../app_service/service/keycard/service as keycard_service import ../../../../app_service/service/keycard/service as keycard_service
import ../../../../app_service/service/privacy/service as privacy_service
import ../../../../app_service/service/accounts/service as accounts_service
import ../../../../app_service/service/wallet_account/service as wallet_account_service
export io_interface export io_interface
@ -19,20 +24,24 @@ type
viewVariant: QVariant viewVariant: QVariant
controller: Controller controller: Controller
initialized: bool initialized: bool
tmpLocalState: State # used when flow is run, until response arrives to determine next state appropriatelly
proc newModule*[T](delegate: T, proc newModule*[T](delegate: T,
events: EventEmitter, events: EventEmitter,
keycardService: keycard_service.Service): keycardService: keycard_service.Service,
privacyService: privacy_service.Service,
accountsService: accounts_service.Service,
walletAccountService: wallet_account_service.Service):
Module[T] = Module[T] =
result = Module[T]() result = Module[T]()
result.delegate = delegate result.delegate = delegate
result.view = view.newView(result) result.view = view.newView(result)
result.viewVariant = newQVariant(result.view) result.viewVariant = newQVariant(result.view)
result.controller = controller.newController(result, events, keycardService) result.controller = controller.newController(result, events, keycardService, privacyService, accountsService,
walletAccountService)
result.initialized = false result.initialized = false
method delete*[T](self: Module[T]) = method delete*[T](self: Module[T]) =
self.controller.disconnect()
self.view.delete self.view.delete
self.viewVariant.delete self.viewVariant.delete
self.controller.delete self.controller.delete
@ -40,9 +49,54 @@ method delete*[T](self: Module[T]) =
method getModuleAsVariant*[T](self: Module[T]): QVariant = method getModuleAsVariant*[T](self: Module[T]): QVariant =
return self.viewVariant return self.viewVariant
method getKeycardData*[T](self: Module[T]): string =
return self.view.getKeycardData()
method setKeycardData*[T](self: Module[T], value: string) = method setKeycardData*[T](self: Module[T], value: string) =
self.view.setKeycardData(value) self.view.setKeycardData(value)
method setPin*[T](self: Module[T], value: string) =
self.controller.setPin(value)
method setPassword*[T](self: Module[T], value: string) =
self.controller.setPassword(value)
method checkRepeatedKeycardPinWhileTyping*[T](self: Module[T], pin: string): bool =
self.controller.setPinMatch(false)
let storedPin = self.controller.getPin()
if pin.len > storedPin.len:
return false
elif pin.len < storedPin.len:
for i in 0 ..< pin.len:
if pin[i] != storedPin[i]:
return false
return true
else:
let match = pin == storedPin
self.controller.setPinMatch(match)
return match
method getMnemonic*[T](self: Module[T]): string =
return self.controller.getMnemonic()
method setSeedPhrase*[T](self: Module[T], value: string) =
self.controller.setSeedPhrase(value)
method getSeedPhrase*[T](self: Module[T]): string =
return self.controller.getSeedPhrase()
method validSeedPhrase*[T](self: Module[T], value: string): bool =
return self.controller.validSeedPhrase(value)
method loggedInUserUsesBiometricLogin*[T](self: Module[T]): bool =
return self.controller.loggedInUserUsesBiometricLogin()
method migratingProfileKeyPair*[T](self: Module[T]): bool =
return self.controller.getSelectedKeyPairIsProfile()
method isProfileKeyPairMigrated*[T](self: Module[T]): bool =
return self.controller.getLoggedInAccount().keycardPairing.len > 0
method onBackActionClicked*[T](self: Module[T]) = method onBackActionClicked*[T](self: Module[T]) =
let currStateObj = self.view.currentStateObj() let currStateObj = self.view.currentStateObj()
if currStateObj.isNil: if currStateObj.isNil:
@ -81,11 +135,32 @@ method onSecondaryActionClicked*[T](self: Module[T]) =
self.view.setCurrentState(nextState) self.view.setCurrentState(nextState)
debug "sm_secondary_action - set state", setCurrFlow=nextState.flowType(), setCurrState=nextState.stateType() debug "sm_secondary_action - set state", setCurrFlow=nextState.flowType(), setCurrState=nextState.stateType()
method onKeycardResponse*[T](self: Module[T], keycardFlowType: string, keycardEvent: KeycardEvent) = method onTertiaryActionClicked*[T](self: Module[T]) =
let currStateObj = self.view.currentStateObj() let currStateObj = self.view.currentStateObj()
if currStateObj.isNil: if currStateObj.isNil:
error "sm_cannot resolve current state" error "sm_cannot resolve current state"
return return
debug "sm_tertiary_action", currFlow=currStateObj.flowType(), currState=currStateObj.stateType()
currStateObj.executeTertiaryCommand(self.controller)
let nextState = currStateObj.getNextTertiaryState(self.controller)
if nextState.isNil:
return
self.view.setCurrentState(nextState)
debug "sm_tertiary_action - set state", setCurrFlow=nextState.flowType(), setCurrState=nextState.stateType()
method onKeycardResponse*[T](self: Module[T], keycardFlowType: string, keycardEvent: KeycardEvent) =
let currStateObj = self.view.currentStateObj()
if currStateObj.isNil:
if self.tmpLocalState.isNil:
error "sm_cannot resolve current state"
return
let nextState = self.tmpLocalState.resolveKeycardNextState(keycardFlowType, keycardEvent, self.controller)
if nextState.isNil:
return
self.view.setCurrentState(nextState)
self.controller.readyToDisplayPopup()
debug "sm_on_keycard_response - from_local - set state", setCurrFlow=nextState.flowType(), setCurrState=nextState.stateType()
return
debug "sm_on_keycard_response", currFlow=currStateObj.flowType(), currState=currStateObj.stateType() debug "sm_on_keycard_response", currFlow=currStateObj.flowType(), currState=currStateObj.stateType()
let nextState = currStateObj.resolveKeycardNextState(keycardFlowType, keycardEvent, self.controller) let nextState = currStateObj.resolveKeycardNextState(keycardFlowType, keycardEvent, self.controller)
if nextState.isNil: if nextState.isNil:
@ -93,8 +168,131 @@ method onKeycardResponse*[T](self: Module[T], keycardFlowType: string, keycardEv
self.view.setCurrentState(nextState) self.view.setCurrentState(nextState)
debug "sm_on_keycard_response - set state", setCurrFlow=nextState.flowType(), setCurrState=nextState.stateType() debug "sm_on_keycard_response - set state", setCurrFlow=nextState.flowType(), setCurrState=nextState.stateType()
method runFactoryResetFlow*[T](self: Module[T]) = proc prepareKeyPairsModel[T](self: Module[T]) =
let findItemByDerivedFromAddress = proc(items: seq[KeyPairItem], address: string): KeyPairItem =
if address.len == 0:
return nil
for i in 0 ..< items.len:
if(items[i].derivedFrom == address):
return items[i]
return nil
let countOfKeyPairsForType = proc(items: seq[KeyPairItem], keyPairType: KeyPairType): int =
result = 0
for i in 0 ..< items.len:
if(items[i].pairType == keyPairType):
result.inc
let accounts = self.controller.getWalletAccounts()
var items: seq[KeyPairItem]
for a in accounts:
if a.isChat or a.walletType == WalletTypeWatch:
continue
var item = findItemByDerivedFromAddress(items, a.derivedfrom)
if a.walletType == WalletTypeDefaultStatusAccount or a.walletType == WalletTypeGenerated:
if self.isProfileKeyPairMigrated():
continue
if item.isNil:
item = initKeyPairItem(pubKey = singletonInstance.userProfile.getPubKey(),
name = singletonInstance.userProfile.getName(),
image = singletonInstance.userProfile.getIcon(),
icon = "",
pairType = KeyPairType.Profile,
derivedFrom = a.derivedfrom)
items.insert(item, 0) # Status Account must be at first place
var icon = ""
if a.walletType == WalletTypeDefaultStatusAccount:
icon = "wallet"
items[0].addAccount(a.name, a.path, a.address, a.emoji, a.color, icon, balance = 0.0)
continue
if a.walletType == WalletTypeSeed:
let diffImports = countOfKeyPairsForType(items, KeyPairType.SeedImport)
if item.isNil:
item = initKeyPairItem(pubKey = "",
name = "Seed Phrase " & $(diffImports + 1), # string created here should be transalted, but so far it's like it is
image = "",
icon = "key_pair_seed_phrase",
pairType = KeyPairType.SeedImport,
derivedFrom = a.derivedfrom)
items.add(item)
item.addAccount(a.name, a.path, a.address, a.emoji, a.color, icon = "", balance = 0.0)
continue
if a.walletType == WalletTypeKey:
let diffImports = countOfKeyPairsForType(items, KeyPairType.PrivateKeyImport)
if item.isNil:
item = initKeyPairItem(pubKey = "",
name = "Key " & $(diffImports + 1), # string created here should be transalted, but so far it's like it is
image = "",
icon = "key_pair_private_key",
pairType = KeyPairType.SeedImport,
derivedFrom = a.derivedfrom)
items.add(item)
item.addAccount(a.name, a.path, a.address, a.emoji, a.color, icon = "", balance = 0.0)
continue
self.view.createKeyPairModel(items)
if items.len == 0:
debug "sm_there is no any key pair for the logged in user that is not already migrated to a keycard"
return
self.view.setSelectedKeyPairByTheAddressItIsDerivedFrom(items[0].derivedFrom())
method runFlow*[T](self: Module[T], flowToRun: FlowType) =
if flowToRun == FlowType.General:
self.controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false)
error "sm_cannot run an general flow"
return
if not self.initialized: if not self.initialized:
self.controller.init() self.controller.init()
self.view.setCurrentState(newPluginReaderState(FlowType.FactoryReset, nil)) if flowToRun == FlowType.FactoryReset:
self.controller.runGetMetadataFlow() self.prepareKeyPairsModel()
self.tmpLocalState = newReadingKeycardState(flowToRun, nil)
self.controller.runGetMetadataFlow()
return
if flowToRun == FlowType.SetupNewKeycard:
self.prepareKeyPairsModel()
self.view.setCurrentState(newSelectExistingKeyPairState(flowToRun, nil))
self.controller.readyToDisplayPopup()
return
method setSelectedKeyPair*[T](self: Module[T], item: KeyPairItem) =
var paths: seq[string]
for a in item.accountsAsArr():
paths.add(a.path)
self.controller.setSelectedKeyPairIsProfile(item.pairType == KeyPairType.Profile)
self.controller.setSelectedKeyPairName(item.name)
self.controller.setSelectedKeyPairWalletPaths(paths)
proc generateRandomColor[T](self: Module[T]): string =
let r = rand(0 .. 255)
let g = rand(0 .. 255)
let b = rand(0 .. 255)
return "#" & r.toHex(2) & g.toHex(2) & b.toHex(2)
proc updateKeyPairItemIfDataAreKnown[T](self: Module[T], address: string, item: var KeyPairItem): bool =
let accounts = self.controller.getWalletAccounts()
for a in accounts:
if a.isChat or a.walletType == WalletTypeWatch or cmpIgnoreCase(a.address, address) != 0:
continue
var icon = ""
if a.walletType == WalletTypeDefaultStatusAccount:
icon = "wallet"
item.addAccount(a.name, a.path, a.address, a.emoji, a.color, icon, balance = 0.0)
return true
return false
method setKeyPairStoredOnKeycard*[T](self: Module[T], cardMetadata: CardMetadata) =
var item = initKeyPairItem(pubKey = "",
name = cardMetadata.name,
image = "",
icon = "keycard",
pairType = KeyPairType.Unknown,
derivedFrom = "")
var knownKeyPair = true
for wa in cardMetadata.walletAccounts:
if self.updateKeyPairItemIfDataAreKnown(wa.address, item):
continue
let balance = self.controller.getBalanceForAddress(wa.address)
knownKeyPair = false
item.addAccount(name = "", wa.path, wa.address, emoji = "", color = self.generateRandomColor(), icon = "wallet", balance)
self.view.setKeyPairStoredOnKeycardIsKnown(knownKeyPair)
self.view.setKeyPairStoredOnKeycard(item)

View File

@ -1,6 +1,7 @@
import NimQml import NimQml
import io_interface import io_interface
import internal/[state, state_wrapper] import internal/[state, state_wrapper]
import models/[key_pair_model, key_pair_item, key_pair_selected_item]
QtObject: QtObject:
type type
@ -8,11 +9,30 @@ QtObject:
delegate: io_interface.AccessInterface delegate: io_interface.AccessInterface
currentState: StateWrapper currentState: StateWrapper
currentStateVariant: QVariant currentStateVariant: QVariant
keyPairModel: KeyPairModel
keyPairModelVariant: QVariant
selectedKeyPairItem: KeyPairSelectedItem
selectedKeyPairItemVariant: QVariant
keyPairStoredOnKeycardIsKnown: bool
keyPairStoredOnKeycard: KeyPairSelectedItem
keyPairStoredOnKeycardVariant: QVariant
keycardData: string # used to temporary store the data coming from keycard, depends on current state different data may be stored keycardData: string # used to temporary store the data coming from keycard, depends on current state different data may be stored
proc delete*(self: View) = proc delete*(self: View) =
self.currentStateVariant.delete self.currentStateVariant.delete
self.currentState.delete self.currentState.delete
if not self.keyPairModel.isNil:
self.keyPairModel.delete
if not self.keyPairModelVariant.isNil:
self.keyPairModelVariant.delete
if not self.selectedKeyPairItem.isNil:
self.selectedKeyPairItem.delete
if not self.selectedKeyPairItemVariant.isNil:
self.selectedKeyPairItemVariant.delete
if not self.keyPairStoredOnKeycard.isNil:
self.keyPairStoredOnKeycard.delete
if not self.keyPairStoredOnKeycardVariant.isNil:
self.keyPairStoredOnKeycardVariant.delete
self.QObject.delete self.QObject.delete
proc newView*(delegate: io_interface.AccessInterface): View = proc newView*(delegate: io_interface.AccessInterface): View =
@ -25,6 +45,7 @@ QtObject:
signalConnect(result.currentState, "backActionClicked()", result, "onBackActionClicked()", 2) signalConnect(result.currentState, "backActionClicked()", result, "onBackActionClicked()", 2)
signalConnect(result.currentState, "primaryActionClicked()", result, "onPrimaryActionClicked()", 2) signalConnect(result.currentState, "primaryActionClicked()", result, "onPrimaryActionClicked()", 2)
signalConnect(result.currentState, "secondaryActionClicked()", result, "onSecondaryActionClicked()", 2) signalConnect(result.currentState, "secondaryActionClicked()", result, "onSecondaryActionClicked()", 2)
signalConnect(result.currentState, "tertiaryActionClicked()", result, "onTertiaryActionClicked()", 2)
proc currentStateObj*(self: View): State = proc currentStateObj*(self: View): State =
return self.currentState.getStateObj() return self.currentState.getStateObj()
@ -57,5 +78,84 @@ QtObject:
proc onSecondaryActionClicked*(self: View) {.slot.} = proc onSecondaryActionClicked*(self: View) {.slot.} =
self.delegate.onSecondaryActionClicked() self.delegate.onSecondaryActionClicked()
proc runFactoryResetFlow*(self: View) {.slot.} = proc onTertiaryActionClicked*(self: View) {.slot.} =
self.delegate.runFactoryResetFlow() self.delegate.onTertiaryActionClicked()
proc keyPairModel*(self: View): KeyPairModel =
return self.keyPairModel
proc keyPairModelChanged(self: View) {.signal.}
proc getKeyPairModel(self: View): QVariant {.slot.} =
return self.keyPairModelVariant
QtProperty[QVariant] keyPairModel:
read = getKeyPairModel
notify = keyPairModelChanged
proc createKeyPairModel*(self: View, items: seq[KeyPairItem]) =
if self.keyPairModel.isNil:
self.keyPairModel = newKeyPairModel()
if self.keyPairModelVariant.isNil:
self.keyPairModelVariant = newQVariant(self.keyPairModel)
if self.selectedKeyPairItem.isNil:
self.selectedKeyPairItem = newKeyPairSelectedItem()
if self.selectedKeyPairItemVariant.isNil:
self.selectedKeyPairItemVariant = newQVariant(self.selectedKeyPairItem)
if self.keyPairStoredOnKeycard.isNil:
self.keyPairStoredOnKeycard = newKeyPairSelectedItem()
if self.keyPairStoredOnKeycardVariant.isNil:
self.keyPairStoredOnKeycardVariant = newQVariant(self.keyPairStoredOnKeycard)
self.keyPairModel.setItems(items)
self.keyPairModelChanged()
proc getSelectedKeyPairItem*(self: View): QVariant {.slot.} =
return self.selectedKeyPairItemVariant
QtProperty[QVariant] selectedKeyPairItem:
read = getSelectedKeyPairItem
proc setSelectedKeyPairByTheAddressItIsDerivedFrom*(self: View, address: string) {.slot.} =
let item = self.keyPairModel.findItemByDerivedFromAddress(address)
self.delegate.setSelectedKeyPair(item)
self.selectedKeyPairItem.setItem(item)
proc getKeyPairStoredOnKeycardIsKnown*(self: View): bool {.slot.} =
return self.keyPairStoredOnKeycardIsKnown
QtProperty[bool] keyPairStoredOnKeycardIsKnown:
read = getKeyPairStoredOnKeycardIsKnown
proc setKeyPairStoredOnKeycardIsKnown*(self: View, value: bool) =
self.keyPairStoredOnKeycardIsKnown = value
proc getKeyPairStoredOnKeycard*(self: View): QVariant {.slot.} =
return self.keyPairStoredOnKeycardVariant
QtProperty[QVariant] keyPairStoredOnKeycard:
read = getKeyPairStoredOnKeycard
proc setKeyPairStoredOnKeycard*(self: View, item: KeyPairItem) =
self.keyPairStoredOnKeycard.setItem(item)
proc setPin*(self: View, value: string) {.slot.} =
self.delegate.setPin(value)
proc setPassword*(self: View, value: string) {.slot.} =
self.delegate.setPassword(value)
proc checkRepeatedKeycardPinWhileTyping*(self: View, pin: string): bool {.slot.} =
return self.delegate.checkRepeatedKeycardPinWhileTyping(pin)
proc getMnemonic*(self: View): string {.slot.} =
return self.delegate.getMnemonic()
proc setSeedPhrase*(self: View, value: string) {.slot.} =
self.delegate.setSeedPhrase(value)
proc getSeedPhrase*(self: View): string {.slot.} =
return self.delegate.getSeedPhrase()
proc validSeedPhrase*(self: View, value: string): bool {.slot.} =
return self.delegate.validSeedPhrase(value)
proc loggedInUserUsesBiometricLogin*(self: View): bool {.slot.} =
return self.delegate.loggedInUserUsesBiometricLogin()
proc migratingProfileKeyPair*(self: View): bool {.slot.} =
return self.delegate.migratingProfileKeyPair()
proc isProfileKeyPairMigrated*(self: View): bool {.slot.} =
return self.delegate.isProfileKeyPairMigrated()

View File

@ -2,6 +2,12 @@ import tables, json, sequtils, sugar, strutils
include ../../common/json_utils include ../../common/json_utils
const WalletTypeDefaultStatusAccount* = ""
const WalletTypeGenerated* = "generated"
const WalletTypeSeed* = "seed"
const WalletTypeWatch* = "watch"
const WalletTypeKey* = "key"
type BalanceDto* = object type BalanceDto* = object
balance*: float64 balance*: float64
currencyBalance*: float64 currencyBalance*: float64

View File

@ -253,8 +253,8 @@ StatusSectionLayout {
implicitWidth: parent.width implicitWidth: parent.width
implicitHeight: parent.height implicitHeight: parent.height
keycardStore: profileView.store.keycardStore keycardStore: root.store.keycardStore
sectionTitle: profileView.store.getNameForSubsection(Constants.settingsSubsection.keycard) sectionTitle: root.store.getNameForSubsection(Constants.settingsSubsection.keycard)
contentWidth: d.contentWidth contentWidth: d.contentWidth
} }
} }

View File

@ -6,4 +6,7 @@ QtObject {
property var keycardModule property var keycardModule
function runSetupKeycardPopup() {
root.keycardModule.runSetupKeycardPopup()
}
} }

View File

@ -12,10 +12,12 @@ import utils 1.0
import shared.panels 1.0 import shared.panels 1.0
import shared.controls 1.0 import shared.controls 1.0
import shared.status 1.0 import shared.status 1.0
import shared.popups.keycard 1.0
import "../stores" import "../stores"
import "../controls" import "../controls"
import "../panels" import "../panels"
import "../popups"
SettingsContentBase { SettingsContentBase {
id: root id: root
@ -33,6 +35,29 @@ SettingsContentBase {
id: contentColumn id: contentColumn
spacing: Constants.settingsSection.itemSpacing spacing: Constants.settingsSection.itemSpacing
Connections {
target: root.keycardStore.keycardModule
onDisplayKeycardSharedModuleFlow: {
keycardPopup.active = true
}
onDestroyKeycardSharedModuleFlow: {
keycardPopup.active = false
}
}
Loader {
id: keycardPopup
active: false
sourceComponent: KeycardPopup {
sharedKeycardModule: root.keycardStore.keycardModule.keycardSharedModule
}
onLoaded: {
keycardPopup.item.open()
}
}
Image { Image {
Layout.alignment: Qt.AlignCenter Layout.alignment: Qt.AlignCenter
Layout.preferredHeight: sourceSize.height Layout.preferredHeight: sourceSize.height
@ -70,8 +95,8 @@ SettingsContentBase {
color: Theme.palette.baseColor1 color: Theme.palette.baseColor1
} }
] ]
sensor.onClicked: { onClicked: {
console.warn("TODO: Run Set up Keycard flow...") root.keycardStore.runSetupKeycardPopup()
} }
} }
@ -92,7 +117,7 @@ SettingsContentBase {
color: Theme.palette.baseColor1 color: Theme.palette.baseColor1
} }
] ]
sensor.onClicked: { onClicked: {
console.warn("TODO: Generate a seed phrase...") console.warn("TODO: Generate a seed phrase...")
} }
} }
@ -107,7 +132,7 @@ SettingsContentBase {
color: Theme.palette.baseColor1 color: Theme.palette.baseColor1
} }
] ]
sensor.onClicked: { onClicked: {
console.warn("TODO: Import or restore via a seed phrase...") console.warn("TODO: Import or restore via a seed phrase...")
} }
} }
@ -122,7 +147,7 @@ SettingsContentBase {
color: Theme.palette.baseColor1 color: Theme.palette.baseColor1
} }
] ]
sensor.onClicked: { onClicked: {
console.warn("TODO: Import from Keycard to Status Desktop...") console.warn("TODO: Import from Keycard to Status Desktop...")
} }
} }
@ -144,7 +169,7 @@ SettingsContentBase {
color: Theme.palette.baseColor1 color: Theme.palette.baseColor1
} }
] ]
sensor.onClicked: { onClicked: {
console.warn("TODO: Check whats on a Keycard...") console.warn("TODO: Check whats on a Keycard...")
} }
} }
@ -159,7 +184,7 @@ SettingsContentBase {
color: Theme.palette.baseColor1 color: Theme.palette.baseColor1
} }
] ]
sensor.onClicked: { onClicked: {
console.warn("TODO: Factory reset a Keycard...") console.warn("TODO: Factory reset a Keycard...")
} }
} }

View File

@ -15,30 +15,91 @@ StatusModal {
property var sharedKeycardModule property var sharedKeycardModule
width: 640 width: Constants.keycard.general.popupWidth
height: 640 height: {
margins: 8 if (!root.sharedKeycardModule.keyPairStoredOnKeycardIsKnown) {
if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.setupNewKeycard) {
if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardMetadataDisplay ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.factoryResetConfirmationDisplayMetadata) {
return Constants.keycard.general.popupBiggerHeight
}
}
if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.factoryReset) {
if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardMetadataDisplay ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.factoryResetConfirmationDisplayMetadata) {
return Constants.keycard.general.popupBiggerHeight
}
}
}
return Constants.keycard.general.popupHeight
}
margins: Style.current.halfPadding
anchors.centerIn: parent anchors.centerIn: parent
closePolicy: d.resetInProgress? Popup.NoAutoClose : Popup.CloseOnEscape closePolicy: d.disablePopupClose? Popup.NoAutoClose : Popup.CloseOnEscape
header.title: qsTr("Factory reset a Keycard") header.title: {
if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.setupNewKeycard) {
return qsTr("Set up a new Keycard with an existing account")
}
if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.factoryReset) {
return qsTr("Factory reset a Keycard")
}
return ""
}
QtObject { QtObject {
id: d id: d
property bool factoryResetConfirmed: false property bool primaryButtonEnabled: false
property bool resetInProgress: d.factoryResetConfirmed && root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.readingKeycard property bool seedPhraseRevealed: false
property bool disablePopupClose: root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.readingKeycard ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.migratingKeyPair ||
(root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keyPairMigrateSuccess &&
root.sharedKeycardModule.migratingProfileKeyPair())
onResetInProgressChanged: { onDisablePopupCloseChanged: {
hasCloseButton = !resetInProgress hasCloseButton = !disablePopupClose
} }
} }
onClosed: { onClosed: {
// for all states but the `factoryResetConfirmation` cancel the flow is primary action if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.setupNewKeycard) {
if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.factoryResetConfirmation) if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.selectExistingKeyPair ||
{ root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardNotEmpty ||
root.sharedKeycardModule.currentState.doSecondaryAction() root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardEmptyMetadata ||
return root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.createPin ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.repeatPin ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.pinSet ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.pinVerified ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.enterPin ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongPin ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.maxPinRetriesReached ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.factoryResetConfirmation ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.factoryResetConfirmationDisplayMetadata ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.factoryResetSuccess ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.seedPhraseDisplay ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.seedPhraseEnterWords ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardMetadataDisplay ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.enterSeedPhrase ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongSeedPhrase)
{
root.sharedKeycardModule.currentState.doSecondaryAction()
return
}
}
else if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.factoryReset) {
if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.selectExistingKeyPair ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.pinVerified ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.enterPin ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongPin ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.maxPinRetriesReached ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardMetadataDisplay ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardEmptyMetadata ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.factoryResetConfirmationDisplayMetadata ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.factoryResetConfirmation)
{
root.sharedKeycardModule.currentState.doSecondaryAction()
return
}
} }
root.sharedKeycardModule.currentState.doPrimaryAction() root.sharedKeycardModule.currentState.doPrimaryAction()
} }
@ -50,19 +111,53 @@ StatusModal {
sourceComponent: { sourceComponent: {
if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.pluginReader || if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.pluginReader ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.insertKeycard || root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.insertKeycard ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardInserted ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.readingKeycard || root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.readingKeycard ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keyPairMigrateSuccess ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keyPairMigrateFailure ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.migratingKeyPair ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.factoryResetSuccess || root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.factoryResetSuccess ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardEmptyMetadata ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardEmpty || root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardEmpty ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardNotEmpty ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.notKeycard || root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.notKeycard ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.recognizedKeycard) root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.maxPinRetriesReached ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.recognizedKeycard ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardMetadataDisplay)
{ {
return initComponent return initComponent
} }
if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.factoryResetConfirmation ||
if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.factoryResetConfirmation) root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.factoryResetConfirmationDisplayMetadata)
{ {
return confirmationComponent return confirmationComponent
} }
if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.selectExistingKeyPair)
{
return selectKeyPairComponent
}
if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.createPin ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.repeatPin ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.enterPin ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongPin ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.pinSet ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.pinVerified)
{
return keycardPinComponent
}
if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.enterSeedPhrase ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongSeedPhrase)
{
return enterSeedPhraseComponent
}
if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.seedPhraseDisplay)
{
return seedPhraseComponent
}
if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.seedPhraseEnterWords)
{
return enterSeedPhraseWordsComponent
}
return undefined return undefined
} }
@ -80,8 +175,72 @@ StatusModal {
KeycardConfirmation { KeycardConfirmation {
sharedKeycardModule: root.sharedKeycardModule sharedKeycardModule: root.sharedKeycardModule
Component.onCompleted: {
d.primaryButtonEnabled = false
}
onConfirmationUpdated: { onConfirmationUpdated: {
d.factoryResetConfirmed = value d.primaryButtonEnabled = value
}
}
}
Component {
id: selectKeyPairComponent
SelectKeyPair {
sharedKeycardModule: root.sharedKeycardModule
}
}
Component {
id: keycardPinComponent
KeycardPin {
sharedKeycardModule: root.sharedKeycardModule
}
}
Component {
id: enterSeedPhraseComponent
EnterSeedPhrase {
sharedKeycardModule: root.sharedKeycardModule
Component.onCompleted: {
d.primaryButtonEnabled = false
}
onValidation: {
d.primaryButtonEnabled = result
}
}
}
Component {
id: seedPhraseComponent
SeedPhrase {
sharedKeycardModule: root.sharedKeycardModule
Component.onCompleted: {
hideSeed = !d.seedPhraseRevealed
d.primaryButtonEnabled = Qt.binding(function(){ return d.seedPhraseRevealed })
}
onSeedPhraseRevealed: {
d.seedPhraseRevealed = true
}
}
}
Component {
id: enterSeedPhraseWordsComponent
EnterSeedPhraseWords {
sharedKeycardModule: root.sharedKeycardModule
Component.onCompleted: {
d.primaryButtonEnabled = false
}
onValidation: {
d.primaryButtonEnabled = result
} }
} }
} }
@ -91,6 +250,8 @@ StatusModal {
StatusBackButton { StatusBackButton {
id: backButton id: backButton
visible: root.sharedKeycardModule.currentState.displayBackButton visible: root.sharedKeycardModule.currentState.displayBackButton
height: primaryButton.height
width: primaryButton.height
onClicked: { onClicked: {
root.sharedKeycardModule.currentState.backAction() root.sharedKeycardModule.currentState.backAction()
} }
@ -101,13 +262,62 @@ StatusModal {
StatusButton { StatusButton {
id: secondaryButton id: secondaryButton
text: { text: {
if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.factoryResetConfirmation) if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.setupNewKeycard) {
return qsTr("Cancel") if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.selectExistingKeyPair ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardNotEmpty ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardEmptyMetadata ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.enterPin ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongPin ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.maxPinRetriesReached ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.factoryResetConfirmation ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.factoryResetConfirmationDisplayMetadata ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.factoryResetSuccess ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.pinVerified ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardMetadataDisplay) {
return qsTr("Cancel")
}
}
else if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.factoryReset) {
if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.pinVerified ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.enterPin ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongPin ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.maxPinRetriesReached ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardMetadataDisplay ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardEmptyMetadata ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.factoryResetConfirmationDisplayMetadata ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.factoryResetConfirmation)
return qsTr("Cancel")
}
return "" return ""
} }
visible: { visible: {
if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.factoryResetConfirmation) if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.setupNewKeycard) {
return true if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.selectExistingKeyPair ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardNotEmpty ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardEmptyMetadata ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.enterPin ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongPin ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.maxPinRetriesReached ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.factoryResetConfirmation ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.factoryResetConfirmationDisplayMetadata ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.factoryResetSuccess ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.pinVerified ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardMetadataDisplay) {
return true
}
}
else if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.factoryReset) {
if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.pinVerified ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.enterPin ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongPin ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.maxPinRetriesReached ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardMetadataDisplay ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardEmptyMetadata ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.factoryResetConfirmationDisplayMetadata ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.factoryResetConfirmation) {
return true
}
}
return false return false
} }
highlighted: focus highlighted: focus
@ -119,20 +329,130 @@ StatusModal {
StatusButton { StatusButton {
id: primaryButton id: primaryButton
text: { text: {
if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.factoryResetConfirmation) if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.setupNewKeycard) {
return qsTr("Factory reset this Keycard") if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.pluginReader ||
if (d.resetInProgress || root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.readingKeycard ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.factoryResetSuccess) root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.insertKeycard ||
return qsTr("Done") root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardInserted ||
return qsTr("Cancel") root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.recognizedKeycard ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.createPin ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.repeatPin ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.pinSet) {
return qsTr("Input seed phrase")
}
if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.seedPhraseEnterWords) {
return qsTr("Yes, migrate key pair to this Keycard")
}
if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.enterSeedPhrase) {
return qsTr("Yes, migrate key pair to Keycard")
}
if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongSeedPhrase) {
return qsTr("Try entering seed phrase again")
}
if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardNotEmpty) {
return qsTr("Check what is stored on this Keycard")
}
if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.enterPin ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongPin) {
return qsTr("I dont know the pin")
}
if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.factoryResetConfirmation ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.factoryResetConfirmationDisplayMetadata) {
return qsTr("Factory reset this Keycard")
}
if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.selectExistingKeyPair ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardEmptyMetadata ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.factoryResetSuccess ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.seedPhraseDisplay ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.pinVerified ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardMetadataDisplay) {
return qsTr("Next")
}
if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.maxPinRetriesReached) {
return qsTr("Tmp-Next")
}
if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.migratingKeyPair ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keyPairMigrateFailure) {
return qsTr("Done")
}
if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keyPairMigrateSuccess &&
root.sharedKeycardModule.migratingProfileKeyPair()) {
return qsTr("Restart app & sign in using your new Keycard")
}
return qsTr("Cancel")
}
else if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.factoryReset) {
if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.enterPin ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongPin) {
return qsTr("I dont know the pin")
}
if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.factoryResetConfirmation ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.factoryResetConfirmationDisplayMetadata) {
return qsTr("Factory reset this Keycard")
}
if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.pinVerified ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardMetadataDisplay ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardEmptyMetadata) {
return qsTr("Next")
}
if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.maxPinRetriesReached) {
return qsTr("Tmp-Next")
}
if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.readingKeycard ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.factoryResetSuccess) {
return qsTr("Done")
}
return qsTr("Cancel")
}
return ""
} }
enabled: { enabled: {
if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.factoryResetConfirmation) if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.readingKeycard ||
return d.factoryResetConfirmed root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.migratingKeyPair) {
if (d.resetInProgress) if (d.disablePopupClose) {
return false return false
}
}
else if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.setupNewKeycard) {
if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.factoryResetConfirmation ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.factoryResetConfirmationDisplayMetadata ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.seedPhraseDisplay ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.seedPhraseEnterWords ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.enterSeedPhrase) {
return d.primaryButtonEnabled
}
if ((root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.selectExistingKeyPair &&
root.sharedKeycardModule.keyPairModel.count === 0) ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.pluginReader ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.insertKeycard ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardInserted ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.recognizedKeycard ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.createPin ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.repeatPin) {
return false
}
}
else if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.factoryReset) {
if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.factoryResetConfirmation ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.factoryResetConfirmationDisplayMetadata) {
return d.primaryButtonEnabled
}
}
return true return true
} }
icon.name: {
if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.setupNewKeycard) {
if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.seedPhraseEnterWords ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.enterSeedPhrase) {
if (root.sharedKeycardModule.migratingProfileKeyPair()) {
if (root.sharedKeycardModule.loggedInUserUsesBiometricLogin())
return "touch-id"
return "password"
}
}
}
return ""
}
highlighted: focus highlighted: focus
onClicked: { onClicked: {

View File

@ -0,0 +1,117 @@
import QtQuick 2.14
import QtQml.Models 2.14
import QtQuick.Controls 2.14
import StatusQ.Core.Theme 0.1
import StatusQ.Core.Utils 0.1
import StatusQ.Components 0.1
import StatusQ.Controls 0.1
import utils 1.0
StatusListItem {
id: root
property var sharedKeycardModule
property ButtonGroup buttonGroup
property bool usedAsSelectOption: false
property string keyPairPubKey: ""
property string keyPairName: ""
property string keyPairIcon: ""
property string keyPairImage: ""
property string keyPairDerivedFrom: ""
property string keyPairAccounts: ""
color: Style.current.grey
title: root.keyPairName
titleAsideText: Utils.getElidedCompressedPk(root.keyPairPubKey)
image {
width: 40
height: 40
source: root.keyPairImage
}
icon {
width: root.keyPairIcon? 24 : 40
height: root.keyPairIcon? 24 : 40
name: root.keyPairIcon
color: Utils.colorForPubkey(root.keyPairPubKey)
letterSize: Math.max(4, this.image.width / 2.4)
charactersLen: 2
isLetterIdenticon: !root.keyPairIcon && !this.image.source.toString()
background.color: Theme.palette.primaryColor3
}
ringSettings {
ringSpecModel: Utils.getColorHashAsJson(root.keyPairPubKey)
ringPxSize: Math.max(this.icon.width / 24.0)
}
tagsModel: ListModel{}
tagsDelegate: StatusListItemTag {
color: model.color
height: Style.current.bigPadding
radius: 6
closeButtonVisible: false
icon {
emoji: model.emoji
emojiSize: Emoji.size.verySmall
isLetterIdenticon: !!model.emoji
name: model.icon
color: Theme.palette.indirectColor1
width: 16
height: 16
}
title: model.name
titleText.font.pixelSize: 12
titleText.color: Theme.palette.indirectColor1
}
components: [
StatusRadioButton {
visible: root.usedAsSelectOption
ButtonGroup.group: root.buttonGroup
onCheckedChanged: {
if (!root.usedAsSelectOption)
return
let checkCondition = root.sharedKeycardModule.selectedKeyPairItem.derivedFrom === root.keyPairDerivedFrom
if (checked && checked != checkCondition) {
root.sharedKeycardModule.setSelectedKeyPairByTheAddressItIsDerivedFrom(root.keyPairDerivedFrom)
}
}
Component.onCompleted: {
if (!root.usedAsSelectOption)
return
checked = Qt.binding(function() {
return root.sharedKeycardModule.selectedKeyPairItem.derivedFrom === root.keyPairDerivedFrom
})
}
}
]
Component.onCompleted: {
if (root.keyPairAccounts === "") {
// should never be here, as it's not possible to have keypair item without at least a single account
console.debug("accounts list is empty for selecting keycard pair")
return
}
let obj = JSON.parse(root.keyPairAccounts)
if (obj.error) {
console.debug("error parsing accounts for selecting keycard pair, error: ", obj.error)
return
}
for (var i=0; i<obj.length; i++) {
this.tagsModel.append({"name": obj[i].Field0, "color": obj[i].Field4, "emoji": obj[i].Field3, "icon": obj[i].Field5})
}
}
onClicked: {
if (!root.usedAsSelectOption)
return
root.sharedKeycardModule.setSelectedKeyPairByTheAddressItIsDerivedFrom(root.keyPairDerivedFrom)
}
}

View File

@ -0,0 +1,77 @@
import QtQuick 2.14
import QtQml.Models 2.14
import QtQuick.Controls 2.14
import StatusQ.Core.Theme 0.1
import StatusQ.Core.Utils 0.1
import StatusQ.Components 0.1
import StatusQ.Controls 0.1
import utils 1.0
Item {
id: root
property var sharedKeycardModule
property bool filterProfilePair: false
property var keyPairModel
property ButtonGroup buttonGroup
DelegateModel {
id: delegateModel
function update() {
var visible = [];
for (var i = 0; i < items.count; ++i) {
var item = items.get(i);
if(root.filterProfilePair) {
if (item.model.pairType === Constants.keycard.keyPairType.profile)
visible.push(item);
}
else if (item.model.pairType !== Constants.keycard.keyPairType.profile) {
visible.push(item);
}
}
for (i = 0; i < visible.length; ++i) {
item = visible[i];
item.inPairType = true;
if (item.pairTypeIndex !== i) {
visibleItems.move(item.pairTypeIndex, i, 1);
}
}
}
model: root.keyPairModel
groups: [DelegateModelGroup {
id: visibleItems
name: "pairType"
includeByDefault: false
}]
filterOnGroup: "pairType"
items.onChanged: update()
delegate: KeyPairItem {
width: parent.width
sharedKeycardModule: root.sharedKeycardModule
buttonGroup: root.buttonGroup
usedAsSelectOption: true
keyPairPubKey: model.pubKey
keyPairName: model.name
keyPairIcon: model.icon
keyPairImage: model.image
keyPairDerivedFrom: model.derivedFrom
keyPairAccounts: model.accounts
}
}
ListView {
anchors.fill: parent
spacing: Style.current.padding
clip: true
model: delegateModel
}
}

View File

@ -0,0 +1,170 @@
import QtQuick 2.14
import QtQuick.Controls 2.14
import QtQuick.Layouts 1.14
import QtQml.Models 2.14
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Core.Utils 0.1 as StatusQUtils
import StatusQ.Components 0.1
import StatusQ.Controls 0.1
import utils 1.0
import shared.stores 1.0 as SharedStore
Rectangle {
id: root
property string keyPairPubKey: ""
property string keyPairName: ""
property string keyPairIcon: ""
property string keyPairImage: ""
property string keyPairDerivedFrom: ""
property string keyPairAccounts: ""
color: Style.current.grey
radius: Style.current.halfPadding
clip: true
implicitWidth: 448
implicitHeight: 198
Component.onCompleted: {
if (root.keyPairAccounts === "") {
// should never be here, as it's not possible to have keypair item without at least a single account
console.debug("accounts list is empty for selecting keycard pair")
return
}
let obj = JSON.parse(root.keyPairAccounts)
if (obj.error) {
console.debug("error parsing accounts for selecting keycard pair, error: ", obj.error)
return
}
for (var i=0; i<obj.length; i++) {
accountsListModel.append({"path": obj[i].Field1, "address": obj[i].Field2, "balance": obj[i].Field6})
}
accounts.model = accountsListModel
}
ListModel {
id: accountsListModel
}
ColumnLayout {
anchors.fill: parent
spacing: Style.current.halfPadding
StatusListItem {
Layout.fillWidth: true
Layout.preferredWidth: parent.width
color: "transparent"
title: root.keyPairName
icon {
width: 24
height: 24
name: root.keyPairIcon
color: Utils.colorForPubkey(root.keyPairPubKey)
letterSize: Math.max(4, this.image.width / 2.4)
charactersLen: 2
isLetterIdenticon: false
background.color: Theme.palette.primaryColor3
}
}
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 1
color: Style.current.grey3
}
StatusBaseText {
Layout.preferredWidth: parent.width - 2 * Style.current.padding
Layout.leftMargin: Style.current.padding
Layout.alignment: Qt.AlignLeft
text: qsTr("Active Accounts")
font.pixelSize: Constants.keycard.general.fontSize2
color: Theme.palette.baseColor1
wrapMode: Text.WordWrap
}
ListView {
id: accounts
Layout.fillWidth: true
Layout.fillHeight: true
Layout.preferredWidth: parent.width
Layout.bottomMargin: Style.current.padding
clip: true
spacing: Style.current.halfPadding * 0.5
delegate: Rectangle {
anchors.horizontalCenter: parent.horizontalCenter
width: parent.width - 2 * Style.current.padding
height: Style.current.xlPadding * 2
color: Theme.palette.statusModal.backgroundColor
radius: Style.current.halfPadding
RowLayout {
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.right: parent.right
anchors.leftMargin: Style.current.padding
anchors.rightMargin: Style.current.padding
ColumnLayout {
Row {
spacing: 0
padding: 0
StatusBaseText {
id: address
text: StatusQUtils.Utils.elideText(model.address, 6, 4)
wrapMode: Text.WordWrap
font.pixelSize: Constants.keycard.general.fontSize2
color: Theme.palette.directColor1
}
StatusFlatRoundButton {
height: 20
width: 20
icon.name: "external"
icon.width: 16
icon.height: 16
onClicked: {
Qt.openUrlExternally("https://etherscan.io/address/%1".arg(model.address))
}
}
}
StatusBaseText {
text: model.path
wrapMode: Text.WordWrap
font.pixelSize: Constants.keycard.general.fontSize2
color: Theme.palette.baseColor1
}
}
Item {
Layout.fillWidth: true
Layout.preferredHeight: parent.height
}
StatusBaseText {
Layout.alignment: Qt.AlignVCenter
text: {
if (Global.appMain) {
return "%1%2".arg(SharedStore.RootStore.currencyStore.currentCurrencySymbol)
.arg(Utils.toLocaleString(model.balance.toFixed(2), appSettings.locale, {"model.currency": true}))
}
// without language/model refactor no way to read currency symbol or `appSettings.locale` before user logs in
return "$%1".arg(Utils.toLocaleString(model.balance.toFixed(2), localAppSettings.language, {"model.currency": true}))
}
wrapMode: Text.WordWrap
font.pixelSize: Constants.keycard.general.fontSize2
color: Theme.palette.baseColor1
}
}
}
}
}
}

View File

@ -0,0 +1,93 @@
import QtQuick 2.14
import utils 1.0
Item {
id: root
property url source: ""
property string pattern: ""
property int startImgIndexForTheFirstLoop: 0
property int startImgIndexForOtherLoops: 0
property int endImgIndex: 0
property int duration: 0
property int loops: -1 // infinite
signal animationCompleted()
QtObject {
id: d
property int currentLoop: 1
property bool isAnimation: false
function restart() {
d.currentLoop = 1
animation.from = root.startImgIndexForTheFirstLoop
animation.to = root.endImgIndex
animation.duration = root.duration
img.currentImgIndex = root.startImgIndexForTheFirstLoop
if (d.isAnimation)
animation.restart()
}
}
onPatternChanged: {
d.isAnimation = root.duration > 0 && root.pattern !== ""
d.restart()
}
onStartImgIndexForTheFirstLoopChanged: {
d.restart()
}
onEndImgIndexChanged: {
d.restart()
}
onDurationChanged: {
d.isAnimation = root.duration > 0 && root.pattern !== ""
d.restart()
}
Image {
id: img
anchors.fill: parent
fillMode: Image.PreserveAspectFit
antialiasing: true
mipmap: true
source: d.isAnimation?
Style.png(root.pattern.arg(img.currentImgIndex)) :
root.source
property int currentImgIndex: root.startImgIndexForTheFirstLoop
onCurrentImgIndexChanged: {
if (currentImgIndex == root.endImgIndex) {
if (d.currentLoop === root.loops && root.loops > -1) {
animation.stop()
root.animationCompleted()
return
}
if (d.currentLoop === 1 && (root.loops === -1 || root.loops > 1)) {
animation.stop()
animation.duration = root.duration / (root.endImgIndex + 1) * (root.endImgIndex - root.startImgIndexForOtherLoops)
animation.from = root.startImgIndexForOtherLoops
animation.to = root.endImgIndex
animation.loops = root.loops == -1? Animation.Infinite : root.loops
animation.start()
}
d.currentLoop += 1
}
}
NumberAnimation on currentImgIndex {
id: animation
from: root.startImgIndexForTheFirstLoop
to: root.endImgIndex
duration: root.duration
}
}
}

View File

@ -0,0 +1,328 @@
import QtQuick 2.14
import QtQuick.Layouts 1.14
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Controls 0.1
import StatusQ.Controls.Validators 0.1
import utils 1.0
import shared.stores 1.0
import shared.controls 1.0
Item {
id: root
property var sharedKeycardModule
signal validation(bool result)
QtObject {
id: d
property bool allEntriesValid: false
property var mnemonicInput: []
readonly property var tabs: [12, 18, 24]
readonly property ListModel seedPhrases_en: BIP39_en {}
property bool wrongSeedPhrase: root.sharedKeycardModule.keycardData & Constants.predefinedKeycardData.wrongSeedPhrase
onWrongSeedPhraseChanged: {
if (wrongSeedPhrase) {
invalidSeedTxt.text = qsTr("The phrase youve entered does not match this Keycards seed phrase")
invalidSeedTxt.visible = true
}
else {
invalidSeedTxt.text = ""
invalidSeedTxt.visible = false
}
}
onAllEntriesValidChanged: {
if (d.allEntriesValid) {
let mnemonicString = ""
const sortTable = mnemonicInput.sort((a, b) => a.pos - b.pos)
for (let i = 0; i < mnemonicInput.length; i++) {
d.checkWordExistence(sortTable[i].seed)
mnemonicString += sortTable[i].seed + ((i === (grid.count-1)) ? "" : " ")
}
if (Utils.isMnemonic(mnemonicString) && root.sharedKeycardModule.validSeedPhrase(mnemonicString)) {
root.sharedKeycardModule.setSeedPhrase(mnemonicString)
} else {
invalidSeedTxt.text = qsTr("Invalid seed phrase")
invalidSeedTxt.visible = true
d.allEntriesValid = false
}
}
root.validation(d.allEntriesValid)
}
function checkMnemonicLength() {
d.allEntriesValid = d.mnemonicInput.length === d.tabs[switchTabBar.currentIndex]
}
function checkWordExistence(word) {
d.allEntriesValid = d.allEntriesValid && d.seedPhrases_en.words.includes(word)
if (d.allEntriesValid) {
invalidSeedTxt.text = ""
invalidSeedTxt.visible = false
}
else {
invalidSeedTxt.text = qsTr("The phrase youve entered is invalid")
invalidSeedTxt.visible = true
}
}
function pasteWords () {
const clipboardText = globalUtils.getFromClipboard()
// Split words separated by commas and or blank spaces (spaces, enters, tabs)
const words = clipboardText.split(/[, \s]+/)
let index = d.tabs.indexOf(words.length)
if (index === -1) {
return false
}
let timeout = 0
if (switchTabBar.currentIndex !== index) {
switchTabBar.currentIndex = index
// Set the teimeout to 100 so the grid has time to generate the new items
timeout = 100
}
d.mnemonicInput = []
timer.setTimeout(() => {
// Populate mnemonicInput
for (let i = 0; i < words.length; i++) {
grid.addWord(i + 1, words[i], true)
}
// Populate grid
for (let j = 0; j < grid.count; j++) {
const item = grid.itemAtIndex(j)
if (!item || !item.leftComponentText) {
// The grid has gaps in it and also sometimes doesn't return the item correctly when offscreen
// in those cases, we just add the word in the array but not in the grid.
// The button will still work and import correctly. The Grid itself will be partly empty, but offscreen
// With the re-design of the grid, this should be fixed
continue
}
const pos = item.mnemonicIndex
item.setWord(words[pos - 1])
}
d.checkMnemonicLength()
}, timeout)
return true
}
}
ColumnLayout {
anchors.fill: parent
anchors.topMargin: Style.current.xlPadding
anchors.bottomMargin: Style.current.halfPadding
anchors.leftMargin: Style.current.xlPadding
anchors.rightMargin: Style.current.xlPadding
spacing: Style.current.padding
clip: true
StatusBaseText {
id: title
Layout.preferredHeight: Constants.keycard.general.titleHeight
Layout.alignment: Qt.AlignHCenter
text: qsTr("Enter key pair seed phrase")
font.pixelSize: Constants.keycard.general.fontSize1
font.weight: Font.Bold
color: Theme.palette.directColor1
}
Timer {
id: timer
}
StatusSwitchTabBar {
id: switchTabBar
Layout.alignment: Qt.AlignHCenter
Repeater {
model: d.tabs
StatusSwitchTabButton {
text: qsTr("%1 words").arg(modelData)
id: seedPhraseWords
objectName: `${modelData}SeedButton`
}
}
onCurrentIndexChanged: {
d.mnemonicInput = d.mnemonicInput.filter(function(value) {
return value.pos <= d.tabs[switchTabBar.currentIndex]
})
d.checkMnemonicLength()
}
}
StatusGridView {
id: grid
readonly property var wordIndex: [
["1", "3", "5", "7", "9", "11", "2", "4", "6", "8", "10", "12"]
,["1", "4", "7", "10", "13", "16", "2", "5", "8",
"11", "14", "17", "3", "6", "9", "12", "15", "18"]
,["1", "5", "9", "13", "17", "21", "2", "6", "10", "14", "18", "22",
"3", "7", "11", "15", "19", "23", "4", "8", "12", "16", "20", "24"]
]
Layout.preferredWidth: parent.width
Layout.preferredHeight: 312
clip: false
flow: GridView.FlowTopToBottom
cellWidth: (parent.width/(count/6))
cellHeight: 52
interactive: false
z: 100000
cacheBuffer: 9999
model: switchTabBar.currentItem.text.substring(0,2)
function addWord(pos, word, ignoreGoingNext = false) {
d.mnemonicInput.push({pos: pos, seed: word.replace(/\s/g, '')})
for (let j = 0; j < d.mnemonicInput.length; j++) {
if (d.mnemonicInput[j].pos === pos && d.mnemonicInput[j].seed !== word) {
d.mnemonicInput[j].seed = word
break
}
}
//remove duplicates
const valueArr = d.mnemonicInput.map(item => item.pos)
const isDuplicate = valueArr.some((item, idx) => {
if (valueArr.indexOf(item) !== idx) {
d.mnemonicInput.splice(idx, 1)
}
return valueArr.indexOf(item) !== idx
})
if (!ignoreGoingNext) {
for (let i = 0; i < grid.count; i++) {
if (grid.itemAtIndex(i).mnemonicIndex !== (pos + 1)) {
continue
}
grid.currentIndex = grid.itemAtIndex(i).itemIndex
grid.itemAtIndex(i).textEdit.input.edit.forceActiveFocus()
if (grid.currentIndex !== 12) {
continue
}
grid.positionViewAtEnd()
if (grid.count === 20) {
grid.contentX = 1500
}
}
}
d.checkMnemonicLength()
}
delegate: StatusSeedPhraseInput {
id: seedWordInput
textEdit.input.edit.objectName: `statusSeedPhraseInputField${seedWordInput.leftComponentText}`
width: (grid.cellWidth - 8)
height: (grid.cellHeight - 8)
Behavior on width { NumberAnimation { duration: 180 } }
textEdit.text: {
const pos = seedWordInput.mnemonicIndex
for (let i in d.mnemonicInput) {
const p = d.mnemonicInput[i]
if (p.pos === pos) {
return p.seed
}
}
return ""
}
readonly property int mnemonicIndex: grid.wordIndex[(grid.count / 6) - 2][index]
leftComponentText: mnemonicIndex
inputList: d.seedPhrases_en
property int itemIndex: index
z: (grid.currentIndex === index) ? 150000000 : 0
onTextChanged: {
d.checkWordExistence(text)
}
onDoneInsertingWord: {
grid.addWord(mnemonicIndex, word)
}
onEditClicked: {
grid.currentIndex = index
grid.itemAtIndex(index).textEdit.input.edit.forceActiveFocus()
}
onKeyPressed: {
grid.currentIndex = index
if (event.key === Qt.Key_Backtab) {
for (let i = 0; i < grid.count; i++) {
if (grid.itemAtIndex(i).mnemonicIndex === ((mnemonicIndex - 1) >= 0 ? (mnemonicIndex - 1) : 0)) {
grid.itemAtIndex(i).textEdit.input.edit.forceActiveFocus(Qt.BacktabFocusReason)
textEdit.input.tabNavItem = grid.itemAtIndex(i).textEdit.input.edit
event.accepted = true
break
}
}
} else if (event.key === Qt.Key_Tab) {
for (let i = 0; i < grid.count; i++) {
if (grid.itemAtIndex(i).mnemonicIndex === ((mnemonicIndex + 1) <= grid.count ? (mnemonicIndex + 1) : grid.count)) {
grid.itemAtIndex(i).textEdit.input.edit.forceActiveFocus(Qt.TabFocusReason)
textEdit.input.tabNavItem = grid.itemAtIndex(i).textEdit.input.edit
event.accepted = true
break
}
}
}
if (event.matches(StandardKey.Paste)) {
if (d.pasteWords()) {
// Paste was done by splitting the words
event.accepted = true
}
return
}
if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
event.accepted = true
if (d.allEntriesValid) {
d.sharedKeycardModule.currentState.doPrimaryAction()
return
}
}
if (event.key === Qt.Key_Delete || event.key === Qt.Key_Backspace) {
const wordIndex = d.mnemonicInput.findIndex(x => x.pos === mnemonicIndex)
if (wordIndex > -1) {
d.mnemonicInput.splice(wordIndex, 1)
d.checkMnemonicLength()
}
}
}
Component.onCompleted: {
const item = grid.itemAtIndex(0)
if (item) {
item.textEdit.input.edit.forceActiveFocus()
}
}
}
}
StatusBaseText {
id: invalidSeedTxt
Layout.alignment: Qt.AlignHCenter
color: Theme.palette.dangerColor1
visible: false
}
}
states: [
State {
name: Constants.keycardSharedState.enterSeedPhrase
when: root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.enterSeedPhrase
},
State {
name: Constants.keycardSharedState.wrongSeedPhrase
when: root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongSeedPhrase
}
]
}

View File

@ -0,0 +1,168 @@
import QtQuick 2.14
import QtQuick.Layouts 1.14
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Controls 0.1
import StatusQ.Controls.Validators 0.1
import utils 1.0
Item {
id: root
property var sharedKeycardModule
signal validation(bool result)
QtObject {
id: d
property bool allEntriesValid: false
readonly property var seedPhrase: root.sharedKeycardModule.getMnemonic().split(" ")
readonly property var wordNumbers: {
let numbers = []
while (numbers.length < 3) {
let randomNo = Math.floor(Math.random() * 12)
if(numbers.indexOf(randomNo) == -1)
numbers.push(randomNo)
}
numbers.sort((a, b) => { return a < b? -1 : a > b? 1 : 0 })
return numbers
}
function processText(text) {
if(text.length === 0)
return ""
if(/(^\s|^\r|^\n)|(\s$|^\r$|^\n$)/.test(text)) {
return text.trim()
}
else if(/\s|\r|\n/.test(text)) {
return ""
}
return text
}
function updateValidity() {
d.allEntriesValid = word0.valid && word1.valid && word2.valid
root.validation(d.allEntriesValid)
}
}
ColumnLayout {
anchors.fill: parent
anchors.topMargin: Style.current.xlPadding
anchors.bottomMargin: Style.current.halfPadding
anchors.leftMargin: Style.current.xlPadding
anchors.rightMargin: Style.current.xlPadding
spacing: Style.current.padding
clip: true
StatusBaseText {
id: title
Layout.preferredHeight: Constants.keycard.general.titleHeight
Layout.alignment: Qt.AlignHCenter
text: qsTr("Confirm seed phrase words")
font.pixelSize: Constants.keycard.general.fontSize1
font.weight: Font.Bold
color: Theme.palette.directColor1
}
StatusInput {
id: word0
Layout.fillWidth: true
validationMode: StatusInput.ValidationMode.Always
label: qsTr("Word #%1").arg(d.wordNumbers[0] + 1)
placeholderText: qsTr("Enter word")
validators: [
StatusValidator {
validate: function (t) { return (d.seedPhrase[d.wordNumbers[0]] === word0.text); }
errorMessage: (word0.text.length) > 0 ? qsTr("This word doesnt match") : ""
}
]
input.acceptReturn: true
input.tabNavItem: word1.input.edit
onTextChanged: {
text = d.processText(text)
d.updateValidity()
}
onKeyPressed: {
if (d.allEntriesValid &&
(input.edit.keyEvent === Qt.Key_Return ||
input.edit.keyEvent === Qt.Key_Enter)) {
event.accepted = true
root.sharedKeycardModule.currentState.doPrimaryAction()
}
}
}
StatusInput {
id: word1
Layout.fillWidth: true
validationMode: StatusInput.ValidationMode.Always
label: qsTr("Word #%1").arg(d.wordNumbers[1] + 1)
placeholderText: qsTr("Enter word")
validators: [
StatusValidator {
validate: function (t) { return (d.seedPhrase[d.wordNumbers[1]] === word1.text); }
errorMessage: (word1.text.length) > 0 ? qsTr("This word doesnt match") : ""
}
]
input.acceptReturn: true
input.tabNavItem: word2.input.edit
onTextChanged: {
text = d.processText(text)
d.updateValidity()
}
onKeyPressed: {
if (d.allEntriesValid &&
(input.edit.keyEvent === Qt.Key_Return ||
input.edit.keyEvent === Qt.Key_Enter)) {
event.accepted = true
root.sharedKeycardModule.currentState.doPrimaryAction()
}
}
}
StatusInput {
id: word2
Layout.fillWidth: true
validationMode: StatusInput.ValidationMode.Always
label: qsTr("Word #%1").arg(d.wordNumbers[2] + 1)
placeholderText: qsTr("Enter word")
validators: [
StatusValidator {
validate: function (t) { return (d.seedPhrase[d.wordNumbers[2]] === word2.text); }
errorMessage: (word2.text.length) > 0 ? qsTr("This word doesnt match") : ""
}
]
input.acceptReturn: true
onTextChanged: {
text = d.processText(text)
d.updateValidity()
}
onKeyPressed: {
if (d.allEntriesValid &&
(input.edit.keyEvent === Qt.Key_Return ||
input.edit.keyEvent === Qt.Key_Enter)) {
event.accepted = true
root.sharedKeycardModule.currentState.doPrimaryAction()
}
}
}
Item {
Layout.fillWidth: true
Layout.fillHeight: true
}
}
}

View File

@ -8,6 +8,8 @@ import StatusQ.Controls 0.1
import utils 1.0 import utils 1.0
import "../helpers"
Item { Item {
id: root id: root
@ -15,42 +17,119 @@ Item {
signal confirmationUpdated(bool value) signal confirmationUpdated(bool value)
ColumnLayout { Component {
anchors.centerIn: parent id: knownKeyPairComponent
spacing: Style.current.padding KeyPairItem {
keyPairPubKey: root.sharedKeycardModule.keyPairStoredOnKeycard.pubKey
keyPairName: root.sharedKeycardModule.keyPairStoredOnKeycard.name
keyPairIcon: root.sharedKeycardModule.keyPairStoredOnKeycard.icon
keyPairImage: root.sharedKeycardModule.keyPairStoredOnKeycard.image
keyPairDerivedFrom: root.sharedKeycardModule.keyPairStoredOnKeycard.derivedFrom
keyPairAccounts: root.sharedKeycardModule.keyPairStoredOnKeycard.accounts
}
}
Image { Component {
id: unknownKeyPairCompontnt
KeyPairUnknownItem {
keyPairPubKey: root.sharedKeycardModule.keyPairStoredOnKeycard.pubKey
keyPairName: root.sharedKeycardModule.keyPairStoredOnKeycard.name
keyPairIcon: root.sharedKeycardModule.keyPairStoredOnKeycard.icon
keyPairImage: root.sharedKeycardModule.keyPairStoredOnKeycard.image
keyPairDerivedFrom: root.sharedKeycardModule.keyPairStoredOnKeycard.derivedFrom
keyPairAccounts: root.sharedKeycardModule.keyPairStoredOnKeycard.accounts
}
}
ColumnLayout {
anchors.fill: parent
anchors.topMargin: Style.current.xlPadding
anchors.bottomMargin: Style.current.halfPadding
anchors.leftMargin: Style.current.xlPadding
anchors.rightMargin: Style.current.xlPadding
spacing: Style.current.padding
clip: true
KeycardImage {
id: image id: image
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
Layout.preferredHeight: Constants.keycard.shared.imageHeight Layout.preferredHeight: Constants.keycard.shared.imageHeight
Layout.preferredWidth: Constants.keycard.shared.imageWidth Layout.preferredWidth: Constants.keycard.shared.imageWidth
fillMode: Image.PreserveAspectFit pattern: "keycard/strong_error/img-%1"
antialiasing: true source: ""
mipmap: true startImgIndexForTheFirstLoop: 0
source: Style.png("keycard/popup_card_red_sprayed@2x") startImgIndexForOtherLoops: 18
endImgIndex: 29
duration: 1300
loops: -1
} }
StatusBaseText { StatusBaseText {
id: title id: title
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
Layout.preferredHeight: Constants.keycard.general.titleHeight
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
text: qsTr("A factory reset will delete the key on this Keycard.\nAre you sure you want to do this?") text: qsTr("A factory reset will delete the key on this Keycard.\nAre you sure you want to do this?")
font.pixelSize: Constants.keycard.general.fontSize3 font.pixelSize: Constants.keycard.general.fontSize2
color: Theme.palette.dangerColor1 color: Theme.palette.dangerColor1
} }
StatusCheckBox { StatusCheckBox {
id: confirmation id: confirmation
Layout.alignment: Qt.AlignHCenter Layout.preferredHeight: Constants.keycard.general.messageHeight
Layout.alignment: Qt.AlignCenter
leftSide: false leftSide: false
spacing: Style.current.smallPadding spacing: Style.current.smallPadding
font.pixelSize: Constants.keycard.general.fontSize3 font.pixelSize: Constants.keycard.general.fontSize2
text: qsTr("I understand the key pair on this Keycard will be deleted") text: qsTr("I understand the key pair on this Keycard will be deleted")
onCheckedChanged: { onCheckedChanged: {
root.confirmationUpdated(checked) root.confirmationUpdated(checked)
} }
} }
Loader {
id: loader
Layout.preferredWidth: parent.width
active: {
if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.setupNewKeycard) {
if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.factoryResetConfirmationDisplayMetadata) {
return true
}
}
if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.factoryReset) {
if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.factoryResetConfirmationDisplayMetadata) {
return true
}
}
return false
}
sourceComponent: {
if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.setupNewKeycard) {
if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.factoryResetConfirmationDisplayMetadata) {
if (root.sharedKeycardModule.keyPairStoredOnKeycardIsKnown) {
return knownKeyPairComponent
}
return unknownKeyPairCompontnt
}
}
if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.factoryReset) {
if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.factoryResetConfirmationDisplayMetadata) {
if (root.sharedKeycardModule.keyPairStoredOnKeycardIsKnown) {
return knownKeyPairComponent
}
return unknownKeyPairCompontnt
}
}
}
}
Item {
visible: !loader.active
Layout.fillWidth: true
Layout.fillHeight: visible? true : false
}
} }
} }

View File

@ -6,13 +6,32 @@ import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1 import StatusQ.Core.Theme 0.1
import StatusQ.Components 0.1 import StatusQ.Components 0.1
///////// Remove Later////////////
import StatusQ.Popups 0.1
import StatusQ.Controls 0.1
//////////////////////////////////
import utils 1.0 import utils 1.0
import "../helpers"
Item { Item {
id: root id: root
property var sharedKeycardModule property var sharedKeycardModule
Component.onCompleted: {
if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.migratingKeyPair) {
passwordPopup.open()
}
}
QtObject {
id: d
property bool hideKeyPair: root.sharedKeycardModule.keycardData & Constants.predefinedKeycardData.hideKeyPair
}
Timer { Timer {
id: timer id: timer
interval: 1000 interval: 1000
@ -22,23 +41,100 @@ Item {
} }
} }
ColumnLayout { ///////// Remove Later////////////
StatusModal {
id: passwordPopup
width: 300
height: 200
anchors.centerIn: parent anchors.centerIn: parent
spacing: Style.current.padding header.title: qsTr("Temporary Popup")
closePolicy: Popup.NoAutoClose
contentItem: Item {
StatusInput {
id: password
width: parent.width - Style.current.padding * 2
anchors.centerIn: parent
input.clearable: true
placeholderText: qsTr("Enter password...")
}
}
rightButtons: [
StatusButton {
id: primaryButton
text: qsTr("Next")
onClicked: {
root.sharedKeycardModule.setPassword(password.text)
passwordPopup.close()
root.sharedKeycardModule.currentState.doPrimaryAction()
}
}
]
}
//////////////////////////////////
Image { Component {
id: keyPairComponent
KeyPairItem {
keyPairPubKey: root.sharedKeycardModule.selectedKeyPairItem.pubKey
keyPairName: root.sharedKeycardModule.selectedKeyPairItem.name
keyPairIcon: root.sharedKeycardModule.selectedKeyPairItem.icon
keyPairImage: root.sharedKeycardModule.selectedKeyPairItem.image
keyPairDerivedFrom: root.sharedKeycardModule.selectedKeyPairItem.derivedFrom
keyPairAccounts: root.sharedKeycardModule.selectedKeyPairItem.accounts
}
}
Component {
id: knownKeyPairComponent
KeyPairItem {
keyPairPubKey: root.sharedKeycardModule.keyPairStoredOnKeycard.pubKey
keyPairName: root.sharedKeycardModule.keyPairStoredOnKeycard.name
keyPairIcon: root.sharedKeycardModule.keyPairStoredOnKeycard.icon
keyPairImage: root.sharedKeycardModule.keyPairStoredOnKeycard.image
keyPairDerivedFrom: root.sharedKeycardModule.keyPairStoredOnKeycard.derivedFrom
keyPairAccounts: root.sharedKeycardModule.keyPairStoredOnKeycard.accounts
}
}
Component {
id: unknownKeyPairCompontnt
KeyPairUnknownItem {
keyPairPubKey: root.sharedKeycardModule.keyPairStoredOnKeycard.pubKey
keyPairName: root.sharedKeycardModule.keyPairStoredOnKeycard.name
keyPairIcon: root.sharedKeycardModule.keyPairStoredOnKeycard.icon
keyPairImage: root.sharedKeycardModule.keyPairStoredOnKeycard.image
keyPairDerivedFrom: root.sharedKeycardModule.keyPairStoredOnKeycard.derivedFrom
keyPairAccounts: root.sharedKeycardModule.keyPairStoredOnKeycard.accounts
}
}
ColumnLayout {
anchors.fill: parent
anchors.topMargin: Style.current.xlPadding
anchors.bottomMargin: Style.current.halfPadding
anchors.leftMargin: Style.current.xlPadding
anchors.rightMargin: Style.current.xlPadding
spacing: Style.current.padding
clip: true
KeycardImage {
id: image id: image
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
Layout.preferredHeight: Constants.keycard.shared.imageHeight Layout.preferredHeight: Constants.keycard.shared.imageHeight
Layout.preferredWidth: Constants.keycard.shared.imageWidth Layout.preferredWidth: Constants.keycard.shared.imageWidth
fillMode: Image.PreserveAspectFit
antialiasing: true onAnimationCompleted: {
mipmap: true if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardInserted ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.readingKeycard) {
root.sharedKeycardModule.currentState.doSecondaryAction()
}
}
} }
Row { Row {
spacing: Style.current.halfPadding spacing: Style.current.halfPadding
Layout.alignment: Qt.AlignCenter Layout.alignment: Qt.AlignCenter
Layout.preferredHeight: Constants.keycard.general.titleHeight
StatusIcon { StatusIcon {
id: icon id: icon
@ -50,7 +146,8 @@ Item {
} }
StatusLoadingIndicator { StatusLoadingIndicator {
id: loading id: loading
visible: root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.readingKeycard visible: root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.readingKeycard ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.migratingKeyPair
} }
StatusBaseText { StatusBaseText {
id: title id: title
@ -60,10 +157,71 @@ Item {
StatusBaseText { StatusBaseText {
id: message id: message
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignCenter
Layout.preferredWidth: parent.width
Layout.preferredHeight: Constants.keycard.general.messageHeight
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
} }
Loader {
id: loader
Layout.preferredWidth: parent.width
active: {
if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.setupNewKeycard) {
if((root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.pluginReader && !d.hideKeyPair) ||
(root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.insertKeycard && !d.hideKeyPair) ||
(root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardInserted && !d.hideKeyPair) ||
(root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.readingKeycard && !d.hideKeyPair) ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.recognizedKeycard ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keyPairMigrateSuccess ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keyPairMigrateFailure ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardMetadataDisplay) {
return true
}
}
if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.factoryReset) {
if(root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardMetadataDisplay) {
return true
}
}
return false
}
sourceComponent: {
if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.setupNewKeycard) {
if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardMetadataDisplay) {
if (root.sharedKeycardModule.keyPairStoredOnKeycardIsKnown) {
return knownKeyPairComponent
}
return unknownKeyPairCompontnt
}
if ((root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.pluginReader && !d.hideKeyPair) ||
(root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.insertKeycard && !d.hideKeyPair) ||
(root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardInserted && !d.hideKeyPair) ||
(root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.readingKeycard && !d.hideKeyPair) ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.recognizedKeycard ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keyPairMigrateSuccess ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keyPairMigrateFailure) {
return keyPairComponent
}
}
if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.factoryReset) {
if(root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardMetadataDisplay) {
if (root.sharedKeycardModule.keyPairStoredOnKeycardIsKnown) {
return knownKeyPairComponent
}
return unknownKeyPairCompontnt
}
}
}
}
Item {
visible: !loader.active
Layout.fillWidth: true
Layout.fillHeight: this.visible? true : false
}
} }
states: [ states: [
@ -79,12 +237,12 @@ Item {
} }
PropertyChanges { PropertyChanges {
target: image target: image
source: Style.png("keycard/popup_card_reader@2x") source: Style.png("keycard/empty-reader")
pattern: ""
} }
PropertyChanges { PropertyChanges {
target: message target: message
text: "" text: ""
visible: false
} }
}, },
State { State {
@ -99,16 +257,48 @@ Item {
} }
PropertyChanges { PropertyChanges {
target: image target: image
source: Style.png("keycard/popup_insert_card@2x") pattern: "keycard/card_insert/img-%1"
source: ""
startImgIndexForTheFirstLoop: 0
startImgIndexForOtherLoops: 0
endImgIndex: 16
duration: 1000
loops: 1
} }
PropertyChanges { PropertyChanges {
target: message target: message
visible: root.sharedKeycardModule.keycardData !== "" text: root.sharedKeycardModule.keycardData & Constants.predefinedKeycardData.wronglyInsertedCard?
text: qsTr("Check the card, it might be wrongly inserted") qsTr("Check the card, it might be wrongly inserted") :
font.pixelSize: Constants.keycard.general.fontSize3 ""
font.pixelSize: Constants.keycard.general.fontSize2
color: Theme.palette.baseColor1 color: Theme.palette.baseColor1
} }
}, },
State {
name: Constants.keycardSharedState.keycardInserted
when: root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardInserted
PropertyChanges {
target: title
text: qsTr("Keycard inserted...")
font.weight: Font.Bold
font.pixelSize: Constants.keycard.general.fontSize1
color: Theme.palette.directColor1
}
PropertyChanges {
target: image
pattern: "keycard/card_inserted/img-%1"
source: ""
startImgIndexForTheFirstLoop: 0
startImgIndexForOtherLoops: 0
endImgIndex: 29
duration: 1000
loops: 1
}
PropertyChanges {
target: message
text: ""
}
},
State { State {
name: Constants.keycardSharedState.readingKeycard name: Constants.keycardSharedState.readingKeycard
when: root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.readingKeycard when: root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.readingKeycard
@ -121,12 +311,17 @@ Item {
} }
PropertyChanges { PropertyChanges {
target: image target: image
source: Style.png("keycard/popup_card_yellow@2x") pattern: "keycard/warning/img-%1"
source: ""
startImgIndexForTheFirstLoop: 0
startImgIndexForOtherLoops: 0
endImgIndex: 55
duration: 3000
loops: 1
} }
PropertyChanges { PropertyChanges {
target: message target: message
text: "" text: ""
visible: false
} }
}, },
State { State {
@ -135,20 +330,74 @@ Item {
PropertyChanges { PropertyChanges {
target: title target: title
text: qsTr("This is not a Keycard") text: qsTr("This is not a Keycard")
font.pixelSize: Constants.keycard.general.fontSize2 font.pixelSize: Constants.keycard.general.fontSize1
font.weight: Font.Bold font.weight: Font.Bold
color: Theme.palette.dangerColor1 color: Theme.palette.dangerColor1
} }
PropertyChanges { PropertyChanges {
target: image target: image
source: Style.png("keycard/popup_card_red_wrong@2x") pattern: "keycard/strong_error/img-%1"
source: ""
startImgIndexForTheFirstLoop: 0
startImgIndexForOtherLoops: 18
endImgIndex: 29
duration: 1300
loops: -1
} }
PropertyChanges { PropertyChanges {
target: message target: message
text: qsTr("The card inserted is not a recognised Keycard,\nplease remove and try and again") text: qsTr("The card inserted is not a recognised Keycard,\nplease remove and try and again")
font.pixelSize: Constants.keycard.general.fontSize3 font.pixelSize: Constants.keycard.general.fontSize2
color: Theme.palette.dangerColor1 color: Theme.palette.dangerColor1
visible: true }
},
State {
name: Constants.keycardSharedState.maxPinRetriesReached
when: root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.maxPinRetriesReached
PropertyChanges {
target: title
text: qsTr("Keycard locked")
font.pixelSize: Constants.keycard.general.fontSize1
font.weight: Font.Bold
color: Theme.palette.dangerColor1
}
PropertyChanges {
target: image
pattern: "keycard/strong_error/img-%1"
source: ""
startImgIndexForTheFirstLoop: 0
startImgIndexForOtherLoops: 18
endImgIndex: 29
duration: 1300
loops: -1
}
PropertyChanges {
target: message
text: qsTr("Pin entered incorrectly too many times")
font.pixelSize: Constants.keycard.general.fontSize2
color: Theme.palette.dangerColor1
}
},
State {
name: Constants.keycardSharedState.keycardEmptyMetadata
when: root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardEmptyMetadata
PropertyChanges {
target: title
text: qsTr("This Keycard has empty metadata")
font.pixelSize: Constants.keycard.general.fontSize1
font.weight: Font.Bold
color: Theme.palette.directColor1
}
PropertyChanges {
target: image
source: Style.png("keycard/card-inserted")
pattern: ""
}
PropertyChanges {
target: message
text: qsTr("This Keycard already stores keys\nbut doesn't store any metadata")
font.pixelSize: Constants.keycard.general.fontSize2
color: Theme.palette.directColor1
} }
}, },
State { State {
@ -157,20 +406,45 @@ Item {
PropertyChanges { PropertyChanges {
target: title target: title
text: qsTr("Keycard is empty") text: qsTr("Keycard is empty")
font.pixelSize: Constants.keycard.general.fontSize2 font.pixelSize: Constants.keycard.general.fontSize1
font.weight: Font.Bold font.weight: Font.Bold
color: Theme.palette.directColor1 color: Theme.palette.directColor1
} }
PropertyChanges { PropertyChanges {
target: image target: image
source: Style.png("keycard/popup_card_dark@2x") source: Style.png("keycard/card-empty")
pattern: ""
} }
PropertyChanges { PropertyChanges {
target: message target: message
text: qsTr("There is no key pair on this Keycard") text: qsTr("There is no key pair on this Keycard")
font.pixelSize: Constants.keycard.general.fontSize3 font.pixelSize: Constants.keycard.general.fontSize2
color: Theme.palette.directColor1
}
},
State {
name: Constants.keycardSharedState.keycardNotEmpty
when: root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardNotEmpty
PropertyChanges {
target: title
text: qsTr("This Keycard already stores keys")
font.pixelSize: Constants.keycard.general.fontSize1
font.weight: Font.Bold
color: Theme.palette.directColor1
}
PropertyChanges {
target: image
source: Style.png("keycard/card-inserted")
pattern: ""
}
PropertyChanges {
target: message
text: root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.setupNewKeycard?
qsTr("To migrate %1 on to this Keycard, you\nwill need to perform a factory reset first")
.arg(root.sharedKeycardModule.selectedKeyPairItem.name) :
""
font.pixelSize: Constants.keycard.general.fontSize2
color: Theme.palette.directColor1 color: Theme.palette.directColor1
visible: true
} }
}, },
State { State {
@ -185,12 +459,17 @@ Item {
} }
PropertyChanges { PropertyChanges {
target: image target: image
source: Style.png("keycard/popup_card_green@2x") pattern: "keycard/success/img-%1"
source: ""
startImgIndexForTheFirstLoop: 0
startImgIndexForOtherLoops: 0
endImgIndex: 29
duration: 1300
loops: 1
} }
PropertyChanges { PropertyChanges {
target: message target: message
text: "" text: ""
visible: false
} }
}, },
State { State {
@ -198,21 +477,127 @@ Item {
when: root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.factoryResetSuccess when: root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.factoryResetSuccess
PropertyChanges { PropertyChanges {
target: title target: title
text: qsTr("Keycard successfully factory reset") text: root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.setupNewKeycard?
qsTr("Your Keycard has been reset") :
qsTr("Keycard successfully factory reset")
font.pixelSize: Constants.keycard.general.fontSize1 font.pixelSize: Constants.keycard.general.fontSize1
font.weight: Font.Bold font.weight: Font.Bold
color: Theme.palette.directColor1 color: Theme.palette.directColor1
} }
PropertyChanges { PropertyChanges {
target: image target: image
source: Style.png("keycard/popup_card_green_checked@2x") pattern: "keycard/strong_success/img-%1"
source: ""
startImgIndexForTheFirstLoop: 0
startImgIndexForOtherLoops: 0
endImgIndex: 20
duration: 1000
loops: 1
} }
PropertyChanges { PropertyChanges {
target: message target: message
text: qsTr("You can now use this Keycard as if it\nwas a brand new empty Keycard") text: root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.setupNewKeycard?
font.pixelSize: Constants.keycard.general.fontSize3 qsTr("You can now create a new key pair on this Keycard") :
qsTr("You can now use this Keycard as if it\nwas a brand new empty Keycard")
font.pixelSize: Constants.keycard.general.fontSize2
color: Theme.palette.directColor1 color: Theme.palette.directColor1
visible: true }
},
State {
name: Constants.keycardSharedState.migratingKeyPair
when: root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.migratingKeyPair
PropertyChanges {
target: title
text: qsTr("Migrating key pair to Keycard")
font.pixelSize: Constants.keycard.general.fontSize2
font.weight: Font.Bold
color: Theme.palette.baseColor1
}
PropertyChanges {
target: image
pattern: "keycard/warning/img-%1"
source: ""
startImgIndexForTheFirstLoop: 0
startImgIndexForOtherLoops: 0
endImgIndex: 55
duration: 3000
loops: -1
}
PropertyChanges {
target: message
text: ""
}
},
State {
name: Constants.keycardSharedState.keyPairMigrateSuccess
when: root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keyPairMigrateSuccess
PropertyChanges {
target: title
text: qsTr("Key pair successfully migrated")
font.pixelSize: Constants.keycard.general.fontSize1
font.weight: Font.Bold
color: Theme.palette.directColor1
}
PropertyChanges {
target: image
pattern: "keycard/strong_success/img-%1"
source: ""
startImgIndexForTheFirstLoop: 0
startImgIndexForOtherLoops: 0
endImgIndex: 20
duration: 1000
loops: 1
}
PropertyChanges {
target: message
text: qsTr("To complete migration close Status and log in with your new Keycard")
font.pixelSize: Constants.keycard.general.fontSize2
color: Theme.palette.directColor1
}
},
State {
name: Constants.keycardSharedState.keyPairMigrateFailure
when: root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keyPairMigrateFailure
PropertyChanges {
target: title
text: qsTr("Key pair failed to migrated")
font.pixelSize: Constants.keycard.general.fontSize1
font.weight: Font.Bold
color: Theme.palette.directColor1
}
PropertyChanges {
target: image
pattern: "keycard/strong_error/img-%1"
source: ""
startImgIndexForTheFirstLoop: 0
startImgIndexForOtherLoops: 18
endImgIndex: 29
duration: 1300
loops: 1
}
PropertyChanges {
target: message
text: ""
}
},
State {
name: Constants.keycardSharedState.keycardMetadataDisplay
when: root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardMetadataDisplay
PropertyChanges {
target: title
text: qsTr("Accounts on this Keycard")
font.pixelSize: Constants.keycard.general.fontSize1
font.weight: Font.Bold
color: Theme.palette.directColor1
}
PropertyChanges {
target: image
source: Style.png("keycard/card-inserted")
pattern: ""
}
PropertyChanges {
target: message
text: ""
} }
} }
] ]

View File

@ -0,0 +1,292 @@
import QtQuick 2.14
import QtQuick.Layouts 1.14
import QtQuick.Controls 2.14
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Components 0.1
import StatusQ.Controls 0.1
import StatusQ.Controls.Validators 0.1
import utils 1.0
import "../helpers"
Item {
id: root
property var sharedKeycardModule
property int remainingAttempts: parseInt(root.sharedKeycardModule.keycardData, 10)
onRemainingAttemptsChanged: {
if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongPin) {
pinInputField.statesInitialization()
pinInputField.forceFocus()
}
}
onStateChanged: {
if(state === Constants.keycardSharedState.pinSet ||
state === Constants.keycardSharedState.pinVerified) {
pinInputField.setPin("123456") // we are free to set fake pin in this case
} else {
pinInputField.statesInitialization()
pinInputField.forceFocus()
}
}
ColumnLayout {
anchors.fill: parent
anchors.topMargin: Style.current.xlPadding
anchors.bottomMargin: Style.current.halfPadding
anchors.leftMargin: Style.current.xlPadding
anchors.rightMargin: Style.current.xlPadding
spacing: Style.current.padding
clip: true
KeycardImage {
id: image
Layout.alignment: Qt.AlignHCenter
Layout.preferredHeight: Constants.keycard.shared.imageHeight
Layout.preferredWidth: Constants.keycard.shared.imageWidth
}
StatusBaseText {
id: title
Layout.alignment: Qt.AlignCenter
font.weight: Font.Bold
}
StatusPinInput {
id: pinInputField
Layout.alignment: Qt.AlignHCenter
validator: StatusIntValidator{bottom: 0; top: 999999;}
pinLen: Constants.keycard.general.keycardPinLength
enabled: root.sharedKeycardModule.currentState.stateType !== Constants.keycardSharedState.pinSet &&
root.sharedKeycardModule.currentState.stateType !== Constants.keycardSharedState.pinVerified
onPinInputChanged: {
if (root.state !== Constants.keycardSharedState.wrongPin) {
image.source = Style.png("keycard/enter-pin-%1".arg(pinInput.length))
}
if(pinInput.length == 0) {
return
}
if(root.state === Constants.keycardSharedState.createPin ||
root.state === Constants.keycardSharedState.enterPin ||
root.state === Constants.keycardSharedState.wrongPin) {
root.sharedKeycardModule.setPin(pinInput)
root.sharedKeycardModule.currentState.doTertiaryAction()
}
else if(root.state === Constants.keycardSharedState.repeatPin) {
let pinsMatch = root.sharedKeycardModule.checkRepeatedKeycardPinWhileTyping(pinInput)
if (pinsMatch) {
info.text = qsTr("It is very important that you do not loose this PIN")
root.sharedKeycardModule.currentState.doTertiaryAction()
} else {
info.text = qsTr("PINs don't match")
image.source = Style.png("keycard/plain-error")
}
}
}
}
StatusBaseText {
id: info
Layout.alignment: Qt.AlignCenter
wrapMode: Text.WordWrap
visible: text !== ""
}
StatusBaseText {
id: message
Layout.alignment: Qt.AlignCenter
wrapMode: Text.WordWrap
visible: text !== ""
}
Item {
Layout.fillWidth: true
Layout.fillHeight: true
}
Loader {
Layout.preferredWidth: parent.width
active: root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.setupNewKeycard &&
(root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.createPin ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.repeatPin ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.pinSet)
sourceComponent: KeyPairItem {
keyPairPubKey: root.sharedKeycardModule.selectedKeyPairItem.pubKey
keyPairName: root.sharedKeycardModule.selectedKeyPairItem.name
keyPairIcon: root.sharedKeycardModule.selectedKeyPairItem.icon
keyPairImage: root.sharedKeycardModule.selectedKeyPairItem.image
keyPairDerivedFrom: root.sharedKeycardModule.selectedKeyPairItem.derivedFrom
keyPairAccounts: root.sharedKeycardModule.selectedKeyPairItem.accounts
}
}
}
states: [
State {
name: Constants.keycardSharedState.enterPin
when: root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.enterPin
PropertyChanges {
target: image
source: Style.png("keycard/card-empty")
pattern: ""
}
PropertyChanges {
target: title
text: qsTr("Enter this Keycards PIN")
font.pixelSize: Constants.keycard.general.fontSize1
color: Theme.palette.directColor1
}
PropertyChanges {
target: info
text: ""
}
PropertyChanges {
target: message
text: ""
}
},
State {
name: Constants.keycardSharedState.wrongPin
when: root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongPin
PropertyChanges {
target: image
source: Style.png("keycard/plain-error")
pattern: ""
}
PropertyChanges {
target: title
text: qsTr("Enter Keycard PIN")
font.pixelSize: Constants.keycard.general.fontSize1
color: Theme.palette.directColor1
}
PropertyChanges {
target: info
text: qsTr("PIN incorrect")
color: Theme.palette.dangerColor1
font.pixelSize: Constants.keycard.general.fontSize3
}
PropertyChanges {
target: message
text: qsTr("%n attempt(s) remaining", "", root.remainingAttempts)
color: root.remainingAttempts === 1?
Theme.palette.dangerColor1 :
Theme.palette.baseColor1
font.pixelSize: Constants.keycard.general.fontSize3
}
},
State {
name: Constants.keycardSharedState.createPin
when: root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.createPin
PropertyChanges {
target: image
source: Style.png("keycard/enter-pin-0")
pattern: ""
}
PropertyChanges {
target: title
text: qsTr("Choose a Keycard PIN")
font.pixelSize: Constants.keycard.general.fontSize1
color: Theme.palette.directColor1
}
PropertyChanges {
target: info
text: qsTr("It is very important that you do not loose this PIN")
color: Theme.palette.dangerColor1
font.pixelSize: Constants.keycard.general.fontSize3
}
PropertyChanges {
target: message
text: ""
}
},
State {
name: Constants.keycardSharedState.repeatPin
when: root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.repeatPin
PropertyChanges {
target: image
source: Style.png("keycard/enter-pin-0")
pattern: ""
}
PropertyChanges {
target: title
text: qsTr("Repeat Keycard PIN")
font.pixelSize: Constants.keycard.general.fontSize1
color: Theme.palette.directColor1
}
PropertyChanges {
target: info
text: qsTr("It is very important that you do not loose this PIN")
color: Theme.palette.dangerColor1
font.pixelSize: Constants.keycard.general.fontSize3
}
PropertyChanges {
target: message
text: ""
}
},
State {
name: Constants.keycardSharedState.pinSet
when: root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.pinSet
PropertyChanges {
target: image
pattern: "keycard/strong_success/img-%1"
source: ""
startImgIndexForTheFirstLoop: 0
startImgIndexForOtherLoops: 0
endImgIndex: 20
duration: 1300
loops: 1
}
PropertyChanges {
target: title
text: qsTr("Keycard PIN set")
font.pixelSize: Constants.keycard.general.fontSize1
color: Theme.palette.directColor1
}
PropertyChanges {
target: info
text: ""
}
PropertyChanges {
target: message
text: ""
}
},
State {
name: Constants.keycardSharedState.pinVerified
when: root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.pinVerified
PropertyChanges {
target: image
pattern: "keycard/strong_success/img-%1"
source: ""
startImgIndexForTheFirstLoop: 0
startImgIndexForOtherLoops: 0
endImgIndex: 20
duration: 1300
loops: 1
}
PropertyChanges {
target: title
text: qsTr("Keycard PIN verified!")
font.pixelSize: Constants.keycard.general.fontSize1
color: Theme.palette.directColor1
}
PropertyChanges {
target: info
text: ""
}
PropertyChanges {
target: message
text: ""
}
}
]
}

View File

@ -0,0 +1,125 @@
import QtQuick 2.14
import QtQuick.Layouts 1.14
import QtQuick.Controls 2.14
import QtGraphicalEffects 1.14
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Components 0.1
import StatusQ.Controls 0.1
import utils 1.0
Item {
id: root
property var sharedKeycardModule
property bool hideSeed: true
signal seedPhraseRevealed()
QtObject {
id: d
readonly property var seedPhrase: root.sharedKeycardModule.getMnemonic().split(" ")
}
ColumnLayout {
anchors.fill: parent
anchors.topMargin: Style.current.xlPadding
anchors.bottomMargin: Style.current.halfPadding
anchors.leftMargin: Style.current.xlPadding
anchors.rightMargin: Style.current.xlPadding
spacing: Style.current.padding
clip: true
StatusBaseText {
id: title
Layout.preferredHeight: Constants.keycard.general.titleHeight
Layout.alignment: Qt.AlignCenter
wrapMode: Text.WordWrap
}
StatusBaseText {
id: message
Layout.preferredHeight: Constants.keycard.general.messageHeight
Layout.alignment: Qt.AlignCenter
Layout.fillWidth: true
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.WordWrap
}
Item {
Layout.preferredWidth: parent.width
Layout.fillHeight: true
StatusGridView {
id: grid
anchors.fill: parent
visible: !root.hideSeed
cellWidth: parent.width * 0.5
cellHeight: 48
interactive: false
model: 12
readonly property var wordIndex: ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"]
readonly property int spacing: 4
delegate: StatusSeedPhraseInput {
width: (grid.cellWidth - grid.spacing)
height: (grid.cellHeight - grid.spacing)
textEdit.input.edit.enabled: false
text: {
const idx = parseInt(leftComponentText) - 1;
if (!d.seedPhrase || idx < 0 || idx > d.seedPhrase.length - 1)
return "";
return d.seedPhrase[idx];
}
leftComponentText: grid.wordIndex[index]
}
}
GaussianBlur {
id: blur
anchors.fill: grid
visible: root.hideSeed
source: grid
radius: 16
samples: 16
transparentBorder: true
}
StatusButton {
anchors.centerIn: parent
visible: root.hideSeed
type: StatusBaseButton.Type.Primary
icon.name: "view"
text: qsTr("Reveal seed phrase")
onClicked: {
root.hideSeed = false;
root.seedPhraseRevealed()
}
}
}
}
states: [
State {
name: Constants.keycardSharedState.seedPhraseDisplay
when: root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.seedPhraseDisplay
PropertyChanges {
target: title
text: qsTr("Write down your seed phrase")
font.pixelSize: Constants.keycard.general.fontSize1
font.weight: Font.Bold
color: Theme.palette.directColor1
}
PropertyChanges {
target: message
text: qsTr("The next screen contains your seed phrase.<br/><b>Anyone</b> who sees it can use it to access to your funds.")
font.pixelSize: Constants.keycard.general.fontSize2
wrapMode: Text.WordWrap
textFormat: Text.RichText
color: Theme.palette.dangerColor1
}
}
]
}

View File

@ -0,0 +1,109 @@
import QtQuick 2.14
import QtQuick.Layouts 1.14
import QtQuick.Controls 2.14
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Core.Utils 0.1
import StatusQ.Components 0.1
import StatusQ.Controls 0.1
import utils 1.0
import shared.status 1.0
import "../helpers"
Item {
id: root
property var sharedKeycardModule
ColumnLayout {
anchors.fill: parent
anchors.topMargin: Style.current.xlPadding
anchors.bottomMargin: Style.current.halfPadding
anchors.leftMargin: Style.current.xlPadding
anchors.rightMargin: Style.current.xlPadding
spacing: Style.current.padding
clip: true
ButtonGroup {
id: keyPairsButtonGroup
}
StatusBaseText {
id: title
Layout.alignment: Qt.AlignHCenter
text: qsTr("Select a key pair")
font.pixelSize: Constants.keycard.general.fontSize1
font.weight: Font.Bold
color: Theme.palette.directColor1
wrapMode: Text.WordWrap
}
StatusBaseText {
id: subTitle
Layout.alignment: Qt.AlignHCenter
text: qsTr("Select which key pair youd like to move to this Keycard")
font.pixelSize: Constants.keycard.general.fontSize2
color: Theme.palette.baseColor1
wrapMode: Text.WordWrap
}
Item {
Layout.fillWidth: true
Layout.preferredHeight: Style.current.halfPadding
Layout.fillHeight: root.sharedKeycardModule.keyPairModel.count === 0
}
StatusBaseText {
visible: !root.sharedKeycardModule.isProfileKeyPairMigrated()
Layout.preferredWidth: parent.width - 2 * Style.current.padding
Layout.leftMargin: Style.current.padding
Layout.alignment: Qt.AlignLeft
text: qsTr("Profile key pair")
font.pixelSize: Constants.keycard.general.fontSize2
color: Theme.palette.baseColor1
wrapMode: Text.WordWrap
}
KeyPairList {
visible: !root.sharedKeycardModule.isProfileKeyPairMigrated()
Layout.fillWidth: true
Layout.preferredHeight: 100
Layout.fillHeight: visible && root.sharedKeycardModule.keyPairModel.count === 1
Layout.alignment: Qt.AlignLeft
Layout.preferredWidth: parent.width
sharedKeycardModule: root.sharedKeycardModule
filterProfilePair: true
keyPairModel: root.sharedKeycardModule.keyPairModel
buttonGroup: keyPairsButtonGroup
}
StatusBaseText {
visible: root.sharedKeycardModule.isProfileKeyPairMigrated() && root.sharedKeycardModule.keyPairModel.count > 0 ||
!root.sharedKeycardModule.isProfileKeyPairMigrated() && root.sharedKeycardModule.keyPairModel.count > 1
Layout.preferredWidth: parent.width - 2 * Style.current.padding
Layout.leftMargin: Style.current.padding
Layout.alignment: Qt.AlignLeft
text: qsTr("Other key pairs")
font.pixelSize: Constants.keycard.general.fontSize2
color: Theme.palette.baseColor1
wrapMode: Text.WordWrap
}
KeyPairList {
visible: root.sharedKeycardModule.isProfileKeyPairMigrated() && root.sharedKeycardModule.keyPairModel.count > 0 ||
!root.sharedKeycardModule.isProfileKeyPairMigrated() && root.sharedKeycardModule.keyPairModel.count > 1
Layout.fillWidth: true
Layout.fillHeight: true
Layout.alignment: Qt.AlignLeft
Layout.preferredWidth: parent.width
sharedKeycardModule: root.sharedKeycardModule
keyPairModel: root.sharedKeycardModule.keyPairModel
buttonGroup: keyPairsButtonGroup
}
}
}

View File

@ -1,15 +1,19 @@
import QtQuick 2.13 import QtQuick 2.13
ListModel { ListModel {
id: root
property var words: []
Component.onCompleted: { Component.onCompleted: {
var xhr = new XMLHttpRequest(); var xhr = new XMLHttpRequest();
xhr.open("GET", "english.txt"); xhr.open("GET", "english.txt");
xhr.onreadystatechange = function () { xhr.onreadystatechange = function () {
if (xhr.readyState === XMLHttpRequest.DONE) { if (xhr.readyState === XMLHttpRequest.DONE) {
var words = xhr.responseText.split('\n'); root.words = xhr.responseText.split('\n');
for (var i = 0; i < words.length; i++) { for (var i = 0; i < root.words.length; i++) {
if (words[i] !== "") { if (root.words[i] !== "") {
insert(count, {"seedWord": words[i]}); insert(count, {"seedWord": root.words[i]});
} }
} }
} }

View File

@ -67,9 +67,16 @@ QtObject {
readonly property string loginKeycardEmpty: "LoginKeycardEmpty" readonly property string loginKeycardEmpty: "LoginKeycardEmpty"
} }
readonly property QtObject predefinedKeycardData: QtObject {
readonly property int wronglyInsertedCard: 1
readonly property int hideKeyPair: 2
readonly property int wrongSeedPhrase: 4
}
readonly property QtObject keycardSharedFlow: QtObject { readonly property QtObject keycardSharedFlow: QtObject {
readonly property string general: "General" readonly property string general: "General"
readonly property string factoryReset: "FactoryReset" readonly property string factoryReset: "FactoryReset"
readonly property string setupNewKeycard: "SetupNewKeycard"
} }
readonly property QtObject keycardSharedState: QtObject { readonly property QtObject keycardSharedState: QtObject {
@ -77,12 +84,31 @@ QtObject {
readonly property string pluginReader: "PluginReader" readonly property string pluginReader: "PluginReader"
readonly property string readingKeycard: "ReadingKeycard" readonly property string readingKeycard: "ReadingKeycard"
readonly property string insertKeycard: "InsertKeycard" readonly property string insertKeycard: "InsertKeycard"
readonly property string keycardInserted: "KeycardInserted"
readonly property string createPin: "CreatePin"
readonly property string repeatPin: "RepeatPin"
readonly property string pinSet: "PinSet"
readonly property string pinVerified: "PinVerified"
readonly property string enterPin: "EnterPin" readonly property string enterPin: "EnterPin"
readonly property string wrongPin: "WrongPin"
readonly property string maxPinRetriesReached: "MaxPinRetriesReached"
readonly property string factoryResetConfirmation: "FactoryResetConfirmation" readonly property string factoryResetConfirmation: "FactoryResetConfirmation"
readonly property string factoryResetConfirmationDisplayMetadata: "FactoryResetConfirmationDisplayMetadata"
readonly property string factoryResetSuccess: "FactoryResetSuccess" readonly property string factoryResetSuccess: "FactoryResetSuccess"
readonly property string keycardEmptyMetadata: "KeycardEmptyMetadata"
readonly property string keycardMetadataDisplay: "KeycardMetadataDisplay"
readonly property string keycardEmpty: "KeycardEmpty" readonly property string keycardEmpty: "KeycardEmpty"
readonly property string keycardNotEmpty: "KeycardNotEmpty"
readonly property string notKeycard: "NotKeycard" readonly property string notKeycard: "NotKeycard"
readonly property string recognizedKeycard: "RecognizedKeycard" readonly property string recognizedKeycard: "RecognizedKeycard"
readonly property string selectExistingKeyPair: "SelectExistingKeyPair"
readonly property string enterSeedPhrase: "EnterSeedPhrase"
readonly property string wrongSeedPhrase: "WrongSeedPhrase"
readonly property string seedPhraseDisplay: "SeedPhraseDisplay"
readonly property string seedPhraseEnterWords: "SeedPhraseEnterWords"
readonly property string keyPairMigrateSuccess: "KeyPairMigrateSuccess"
readonly property string keyPairMigrateFailure: "KeyPairMigrateFailure"
readonly property string migratingKeyPair: "MigratingKeyPair"
} }
readonly property QtObject keychain: QtObject { readonly property QtObject keychain: QtObject {
@ -339,6 +365,17 @@ QtObject {
readonly property int pukCellHeight: 60 readonly property int pukCellHeight: 60
readonly property int sharedFlowImageWidth: 240 readonly property int sharedFlowImageWidth: 240
readonly property int sharedFlowImageHeight: 240 readonly property int sharedFlowImageHeight: 240
readonly property int popupWidth: 640
readonly property int popupHeight: 640
readonly property int popupBiggerHeight: 766
readonly property int titleHeight: 44
readonly property int messageHeight: 48
}
readonly property QtObject keyPairType: QtObject {
readonly property int profile: 0
readonly property int seedImport: 1
readonly property int privateKeyImport: 2
} }
readonly property QtObject shared: QtObject { readonly property QtObject shared: QtObject {