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"
# Local Account Settings values:
const LS_VALUE_STORE* = "store"
const LS_VALUE_NOTNOW* = "notNow"
const LS_VALUE_NOT_NOW* = "notNow"
const LS_VALUE_NEVER* = "never"
QtObject:

View File

@ -4,6 +4,8 @@ import io_interface
import ../../../../core/eventemitter
import ../../../shared_modules/keycard_popup/io_interface as keycard_shared_module
logScope:
topics = "profile-section-keycard-module-controller"
@ -23,4 +25,9 @@ proc delete*(self: Controller) =
discard
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.} =
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
# Delegate for the view must be declared here due to use of QtObject and multi
# inheritance, which is not well supported in Nim.

View File

@ -6,8 +6,12 @@ import ../io_interface as delegate_interface
import ../../../../core/eventemitter
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 ../../../shared_modules/keycard_popup/module as keycard_shared_module
export io_interface
logScope:
@ -22,16 +26,23 @@ type
moduleLoaded: bool
events: EventEmitter
keycardService: keycard_service.Service
privacyService: privacy_service.Service
accountsService: accounts_service.Service
walletAccountService: wallet_account_service.Service
keycardSharedModule: keycard_shared_module.AccessInterface
proc newModule*(delegate: delegate_interface.AccessInterface,
events: EventEmitter,
keycardService: keycard_service.Service,
privacyService: privacy_service.Service,
accountsService: accounts_service.Service,
walletAccountService: wallet_account_service.Service): Module =
result = Module()
result.delegate = delegate
result.events = events
result.keycardService = keycardService
result.privacyService = privacyService
result.accountsService = accountsService
result.walletAccountService = walletAccountService
result.view = view.newView(result)
result.viewVariant = newQVariant(result.view)
@ -42,6 +53,8 @@ method delete*(self: Module) =
self.view.delete
self.viewVariant.delete
self.controller.delete
if not self.keycardSharedModule.isNil:
self.keycardSharedModule.delete
method load*(self: Module) =
self.controller.init()
@ -56,3 +69,28 @@ method viewDidLoad*(self: Module) =
method getModuleAsVariant*(self: Module): QVariant =
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

@ -18,3 +18,18 @@ QtObject:
proc load*(self: View) =
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.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)

View File

@ -6,6 +6,9 @@ import ../../../global/global_singleton
import ../../../core/signals/types
import ../../../core/eventemitter
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:
topics = "keycard-popup-controller"
@ -15,21 +18,46 @@ type
delegate: io_interface.AccessInterface
events: EventEmitter
keycardService: keycard_service.Service
privacyService: privacy_service.Service
accountsService: accounts_service.Service
walletAccountService: wallet_account_service.Service
connectionIds: seq[UUID]
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,
events: EventEmitter,
keycardService: keycard_service.Service):
keycardService: keycard_service.Service,
privacyService: privacy_service.Service,
accountsService: accounts_service.Service,
walletAccountService: wallet_account_service.Service):
Controller =
result = Controller()
result.delegate = delegate
result.events = events
result.keycardService = keycardService
result.privacyService = privacyService
result.accountsService = accountsService
result.walletAccountService = walletAccountService
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) =
discard
self.disconnect()
proc init*(self: Controller) =
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.connectionIds.add(handlerId)
proc disconnect*(self: Controller) =
for id in self.connectionIds:
self.events.disconnect(id)
proc getKeycardData*(self: Controller): string =
return self.delegate.getKeycardData()
proc setKeycardData*(self: Controller, value: string) =
self.delegate.setKeycardData(value)
@ -50,6 +77,83 @@ proc containsMetadata*(self: Controller): bool =
proc setContainsMetadata*(self: Controller, value: bool) =
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) =
self.keycardService.cancelCurrentFlow()
# in most cases we're running another flow after canceling the current one,
@ -64,9 +168,76 @@ proc runGetMetadataFlow*(self: Controller) =
self.cancelCurrentFlow()
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) =
self.keycardService.resumeCurrentFlowLater()
proc readyToDisplayPopup*(self: Controller) =
self.events.emit(SignalSharedKeycarModuleDisplayPopup, Args())
proc terminateCurrentFlow*(self: Controller, lastStepInTheCurrentFlow: bool) =
let data = SharedKeycarModuleFlowTerminatedArgs(lastStepInTheCurrentFlow: lastStepInTheCurrentFlow)
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) =
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) =
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: FactoryResetConfirmationState, controller: Controller) =
if self.flowType == FlowType.FactoryReset:
if self.flowType == FlowType.FactoryReset or
self.flowType == FlowType.SetupNewKeycard:
controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false)
method getNextPrimaryState*(self: FactoryResetConfirmationState, controller: Controller): State =
return createState(StateType.PluginReader, self.flowType, nil)
method resolveKeycardNextState*(self: FactoryResetConfirmationState, keycardFlowType: string, keycardEvent: KeycardEvent,
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) =
if self.flowType == FlowType.FactoryReset:
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
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)
method resolveKeycardNextState*(self: InsertKeycardState, keycardFlowType: string, keycardEvent: KeycardEvent,
controller: Controller): State =
let state = ensureReaderAndCardPresenceAndResolveNextState(self, keycardFlowType, keycardEvent, controller)
if not state.isNil:
return state
if keycardFlowType == ResponseTypeValueInsertCard and
keycardEvent.error.len > 0 and
keycardEvent.error == ErrorConnection:
controller.setKeycardData(ResponseTypeValueInsertCard)
controller.setKeycardData(getPredefinedKeycardData(controller.getKeycardData(), PredefinedKeycardData.WronglyInsertedCard, add = true))
return nil
if keycardFlowType == ResponseTypeValueCardInserted:
controller.setKeycardData("")
return createState(StateType.ReadingKeycard, self.flowType, nil)
controller.setKeycardData(getPredefinedKeycardData(controller.getKeycardData(), PredefinedKeycardData.WronglyInsertedCard, add = false))
if self.flowType == FlowType.SetupNewKeycard:
return createState(StateType.KeycardInserted, self.flowType, self.getBackState)
return createState(StateType.KeycardInserted, self.flowType, 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
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)

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
method executePrimaryCommand*(self: NotKeycardState, controller: Controller) =
if self.flowType == FlowType.FactoryReset:
if self.flowType == FlowType.FactoryReset or
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
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)
method resolveKeycardNextState*(self: PluginReaderState, keycardFlowType: string, keycardEvent: KeycardEvent,
controller: Controller): State =
if keycardFlowType == ResponseTypeValueKeycardFlowResult and
keycardEvent.error.len > 0 and
keycardEvent.error == ErrorConnection:
controller.resumeCurrentFlowLater()
return nil
if keycardFlowType == ResponseTypeValueInsertCard:
return createState(StateType.InsertKeycard, self.flowType, nil)
return ensureReaderAndCardPresenceAndResolveNextState(self, keycardFlowType, keycardEvent, controller)

View File

@ -9,21 +9,16 @@ proc delete*(self: ReadingKeycardState) =
self.State.delete
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)
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,
controller: Controller): State =
if self.flowType == FlowType.FactoryReset:
if keycardFlowType == ResponseTypeValueSwapCard and
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)
# this is used in case a keycard is inserted and we jump to the first meaningful screen
return ensureReaderAndCardPresenceAndResolveNextState(self, keycardFlowType, keycardEvent, controller)

View File

@ -9,11 +9,15 @@ proc delete*(self: RecognizedKeycardState) =
self.State.delete
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)
method getNextSecondaryState*(self: RecognizedKeycardState, controller: Controller): State =
if self.flowType == FlowType.FactoryReset:
if controller.containsMetadata():
discard # from here we will jump to enter pin view once we add that in keycard settings
return createState(StateType.EnterPin, 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
from ../../../../../app_service/service/keycard/service import KeycardEvent, KeyDetails
from ../io_interface import FlowType
export KeycardEvent, KeyDetails
type FlowType* {.pure.} = enum
General = "General"
FactoryReset = "FactoryReset"
export FlowType, KeycardEvent, KeyDetails
type StateType* {.pure.} = enum
NoState = "NoState"
PluginReader = "PluginReader"
ReadingKeycard = "ReadingKeycard"
InsertKeycard = "InsertKeycard"
KeycardInserted = "KeycardInserted"
CreatePin = "CreatePin"
RepeatPin = "RepeatPin"
PinSet = "PinSet"
PinVerified = "PinVerified"
EnterPin = "EnterPin"
WrongPin = "WrongPin"
MaxPinRetriesReached = "MaxPinRetriesReached"
FactoryResetConfirmation = "FactoryResetConfirmation"
FactoryResetConfirmationDisplayMetadata = "FactoryResetConfirmationDisplayMetadata"
FactoryResetSuccess = "FactoryResetSuccess"
KeycardEmptyMetadata = "KeycardEmptyMetadata"
KeycardMetadataDisplay = "KeycardMetadataDisplay"
KeycardEmpty = "KeycardEmpty"
KeycardNotEmpty = "KeycardNotEmpty"
NotKeycard = "NotKeycard"
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.
@ -70,6 +85,10 @@ method getNextPrimaryState*(self: State, controller: Controller): State {.inlin
method getNextSecondaryState*(self: State, controller: Controller): State {.inline base.} =
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
method executeBackCommand*(self: State, controller: Controller) {.inline base.} =
discard
@ -82,6 +101,10 @@ method executePrimaryCommand*(self: State, controller: Controller) {.inline base
method executeSecondaryCommand*(self: State, controller: Controller) {.inline base.} =
discard
## This method is executed in case "tertiary" action is triggered
method executeTertiaryCommand*(self: State, controller: Controller) {.inline base.} =
discard
## This method is used for handling aync responses for keycard related states
method resolveKeycardNextState*(self: State, keycardFlowType: string, keycardEvent: KeycardEvent,
controller: Controller): State {.inline base.} =

View File

@ -1,6 +1,7 @@
import chronicles
import parseutils, chronicles
import ../../../../../app_service/service/keycard/constants
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 PUKLengthForStatusApp
import state
@ -8,37 +9,217 @@ import state
logScope:
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
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 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_success_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_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 pin_set_state
include pin_verified_state
include plugin_reader_state
include reading_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 =
if stateToBeCreated == StateType.CreatePin:
return newCreatePinState(flowType, backState)
if stateToBeCreated == StateType.EnterPin:
return newEnterPinState(flowType, backState)
if stateToBeCreated == StateType.EnterSeedPhrase:
return newEnterSeedPhraseState(flowType, backState)
if stateToBeCreated == StateType.FactoryResetConfirmationDisplayMetadata:
return newFactoryResetConfirmationDisplayMetadataState(flowType, backState)
if stateToBeCreated == StateType.FactoryResetConfirmation:
return newFactoryResetConfirmationState(flowType, backState)
if stateToBeCreated == StateType.FactoryResetSuccess:
return newFactoryResetSuccessState(flowType, backState)
if stateToBeCreated == StateType.InsertKeycard:
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:
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:
return newNotKeycardState(flowType, backState)
if stateToBeCreated == StateType.PinSet:
return newPinSetState(flowType, backState)
if stateToBeCreated == StateType.PinVerified:
return newPinVerifiedState(flowType, backState)
if stateToBeCreated == StateType.PluginReader:
return newPluginReaderState(flowType, backState)
if stateToBeCreated == StateType.ReadingKeycard:
return newReadingKeycardState(flowType, backState)
if stateToBeCreated == StateType.RecognizedKeycard:
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
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

@ -56,3 +56,7 @@ QtObject:
proc secondaryActionClicked*(self: StateWrapper) {.signal.}
proc doSecondaryAction*(self: StateWrapper) {.slot.} =
self.secondaryActionClicked()
proc tertiaryActionClicked*(self: StateWrapper) {.signal.}
proc doTertiaryAction*(self: StateWrapper) {.slot.} =
self.tertiaryActionClicked()

View File

@ -0,0 +1,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 ../../../../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"
type
SharedKeycarModuleFlowTerminatedArgs* = ref object of Args
lastStepInTheCurrentFlow*: bool
type FlowType* {.pure.} = enum
General = "General"
FactoryReset = "FactoryReset"
SetupNewKeycard = "SetupNewKeycard"
type
AccessInterface* {.pure inheritable.} = ref object of RootObj
@ -17,6 +24,9 @@ method delete*(self: AccessInterface) {.base.} =
method getModuleAsVariant*(self: AccessInterface): QVariant {.base.} =
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.} =
raise newException(ValueError, "No implementation available")
@ -29,13 +39,51 @@ method onPrimaryActionClicked*(self: AccessInterface) {.base.} =
method onSecondaryActionClicked*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method onTertiaryActionClicked*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method onKeycardResponse*(self: AccessInterface, keycardFlowType: string, keycardEvent: KeycardEvent) {.base.} =
raise newException(ValueError, "No implementation available")
method runFactoryResetFlow*(self: AccessInterface) {.base.} =
method runFlow*(self: AccessInterface, flowToRun: FlowType) {.base.} =
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
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 view, controller
import internal/[state, state_factory]
import models/[key_pair_model, key_pair_item]
import ../../../global/global_singleton
import ../../../core/eventemitter
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
@ -19,20 +24,24 @@ type
viewVariant: QVariant
controller: Controller
initialized: bool
tmpLocalState: State # used when flow is run, until response arrives to determine next state appropriatelly
proc newModule*[T](delegate: T,
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] =
result = Module[T]()
result.delegate = delegate
result.view = view.newView(result)
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
method delete*[T](self: Module[T]) =
self.controller.disconnect()
self.view.delete
self.viewVariant.delete
self.controller.delete
@ -40,9 +49,54 @@ method delete*[T](self: Module[T]) =
method getModuleAsVariant*[T](self: Module[T]): QVariant =
return self.viewVariant
method getKeycardData*[T](self: Module[T]): string =
return self.view.getKeycardData()
method setKeycardData*[T](self: Module[T], value: string) =
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]) =
let currStateObj = self.view.currentStateObj()
if currStateObj.isNil:
@ -81,11 +135,32 @@ method onSecondaryActionClicked*[T](self: Module[T]) =
self.view.setCurrentState(nextState)
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()
if currStateObj.isNil:
error "sm_cannot resolve current state"
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()
let nextState = currStateObj.resolveKeycardNextState(keycardFlowType, keycardEvent, self.controller)
if nextState.isNil:
@ -93,8 +168,131 @@ method onKeycardResponse*[T](self: Module[T], keycardFlowType: string, keycardEv
self.view.setCurrentState(nextState)
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:
self.controller.init()
self.view.setCurrentState(newPluginReaderState(FlowType.FactoryReset, nil))
if flowToRun == FlowType.FactoryReset:
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 io_interface
import internal/[state, state_wrapper]
import models/[key_pair_model, key_pair_item, key_pair_selected_item]
QtObject:
type
@ -8,11 +9,30 @@ QtObject:
delegate: io_interface.AccessInterface
currentState: StateWrapper
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
proc delete*(self: View) =
self.currentStateVariant.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
proc newView*(delegate: io_interface.AccessInterface): View =
@ -25,6 +45,7 @@ QtObject:
signalConnect(result.currentState, "backActionClicked()", result, "onBackActionClicked()", 2)
signalConnect(result.currentState, "primaryActionClicked()", result, "onPrimaryActionClicked()", 2)
signalConnect(result.currentState, "secondaryActionClicked()", result, "onSecondaryActionClicked()", 2)
signalConnect(result.currentState, "tertiaryActionClicked()", result, "onTertiaryActionClicked()", 2)
proc currentStateObj*(self: View): State =
return self.currentState.getStateObj()
@ -57,5 +78,84 @@ QtObject:
proc onSecondaryActionClicked*(self: View) {.slot.} =
self.delegate.onSecondaryActionClicked()
proc runFactoryResetFlow*(self: View) {.slot.} =
self.delegate.runFactoryResetFlow()
proc onTertiaryActionClicked*(self: View) {.slot.} =
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
const WalletTypeDefaultStatusAccount* = ""
const WalletTypeGenerated* = "generated"
const WalletTypeSeed* = "seed"
const WalletTypeWatch* = "watch"
const WalletTypeKey* = "key"
type BalanceDto* = object
balance*: float64
currencyBalance*: float64

View File

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

View File

@ -6,4 +6,7 @@ QtObject {
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.controls 1.0
import shared.status 1.0
import shared.popups.keycard 1.0
import "../stores"
import "../controls"
import "../panels"
import "../popups"
SettingsContentBase {
id: root
@ -33,6 +35,29 @@ SettingsContentBase {
id: contentColumn
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 {
Layout.alignment: Qt.AlignCenter
Layout.preferredHeight: sourceSize.height
@ -70,8 +95,8 @@ SettingsContentBase {
color: Theme.palette.baseColor1
}
]
sensor.onClicked: {
console.warn("TODO: Run Set up Keycard flow...")
onClicked: {
root.keycardStore.runSetupKeycardPopup()
}
}
@ -92,7 +117,7 @@ SettingsContentBase {
color: Theme.palette.baseColor1
}
]
sensor.onClicked: {
onClicked: {
console.warn("TODO: Generate a seed phrase...")
}
}
@ -107,7 +132,7 @@ SettingsContentBase {
color: Theme.palette.baseColor1
}
]
sensor.onClicked: {
onClicked: {
console.warn("TODO: Import or restore via a seed phrase...")
}
}
@ -122,7 +147,7 @@ SettingsContentBase {
color: Theme.palette.baseColor1
}
]
sensor.onClicked: {
onClicked: {
console.warn("TODO: Import from Keycard to Status Desktop...")
}
}
@ -144,7 +169,7 @@ SettingsContentBase {
color: Theme.palette.baseColor1
}
]
sensor.onClicked: {
onClicked: {
console.warn("TODO: Check whats on a Keycard...")
}
}
@ -159,7 +184,7 @@ SettingsContentBase {
color: Theme.palette.baseColor1
}
]
sensor.onClicked: {
onClicked: {
console.warn("TODO: Factory reset a Keycard...")
}
}

View File

@ -15,31 +15,92 @@ StatusModal {
property var sharedKeycardModule
width: 640
height: 640
margins: 8
width: Constants.keycard.general.popupWidth
height: {
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
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 {
id: d
property bool factoryResetConfirmed: false
property bool resetInProgress: d.factoryResetConfirmed && root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.readingKeycard
property bool primaryButtonEnabled: false
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: {
hasCloseButton = !resetInProgress
onDisablePopupCloseChanged: {
hasCloseButton = !disablePopupClose
}
}
onClosed: {
// for all states but the `factoryResetConfirmation` cancel the flow is primary action
if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.factoryResetConfirmation)
if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.setupNewKeycard) {
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.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()
}
@ -50,19 +111,53 @@ StatusModal {
sourceComponent: {
if (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.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.keycardEmptyMetadata ||
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.recognizedKeycard)
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.maxPinRetriesReached ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.recognizedKeycard ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardMetadataDisplay)
{
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
}
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
}
@ -80,8 +175,72 @@ StatusModal {
KeycardConfirmation {
sharedKeycardModule: root.sharedKeycardModule
Component.onCompleted: {
d.primaryButtonEnabled = false
}
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 {
id: backButton
visible: root.sharedKeycardModule.currentState.displayBackButton
height: primaryButton.height
width: primaryButton.height
onClicked: {
root.sharedKeycardModule.currentState.backAction()
}
@ -101,13 +262,62 @@ StatusModal {
StatusButton {
id: secondaryButton
text: {
if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.factoryResetConfirmation)
if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.setupNewKeycard) {
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 ""
}
visible: {
if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.factoryResetConfirmation)
if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.setupNewKeycard) {
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
}
highlighted: focus
@ -119,20 +329,130 @@ StatusModal {
StatusButton {
id: primaryButton
text: {
if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.factoryResetConfirmation)
if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.setupNewKeycard) {
if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.pluginReader ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.readingKeycard ||
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 ||
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 (d.resetInProgress ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.factoryResetSuccess)
}
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: {
if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.factoryResetConfirmation)
return d.factoryResetConfirmed
if (d.resetInProgress)
if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.readingKeycard ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.migratingKeyPair) {
if (d.disablePopupClose) {
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
}
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
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 "../helpers"
Item {
id: root
@ -15,42 +17,119 @@ Item {
signal confirmationUpdated(bool value)
ColumnLayout {
anchors.centerIn: parent
spacing: Style.current.padding
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
}
}
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
Layout.alignment: Qt.AlignHCenter
Layout.preferredHeight: Constants.keycard.shared.imageHeight
Layout.preferredWidth: Constants.keycard.shared.imageWidth
fillMode: Image.PreserveAspectFit
antialiasing: true
mipmap: true
source: Style.png("keycard/popup_card_red_sprayed@2x")
pattern: "keycard/strong_error/img-%1"
source: ""
startImgIndexForTheFirstLoop: 0
startImgIndexForOtherLoops: 18
endImgIndex: 29
duration: 1300
loops: -1
}
StatusBaseText {
id: title
Layout.alignment: Qt.AlignHCenter
horizontalAlignment: Text.AlignHCenter
Layout.preferredHeight: Constants.keycard.general.titleHeight
wrapMode: Text.WordWrap
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
}
StatusCheckBox {
id: confirmation
Layout.alignment: Qt.AlignHCenter
Layout.preferredHeight: Constants.keycard.general.messageHeight
Layout.alignment: Qt.AlignCenter
leftSide: false
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")
onCheckedChanged: {
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.Components 0.1
///////// Remove Later////////////
import StatusQ.Popups 0.1
import StatusQ.Controls 0.1
//////////////////////////////////
import utils 1.0
import "../helpers"
Item {
id: root
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 {
id: timer
interval: 1000
@ -22,23 +41,100 @@ Item {
}
}
ColumnLayout {
///////// Remove Later////////////
StatusModal {
id: passwordPopup
width: 300
height: 200
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
Layout.alignment: Qt.AlignHCenter
Layout.preferredHeight: Constants.keycard.shared.imageHeight
Layout.preferredWidth: Constants.keycard.shared.imageWidth
fillMode: Image.PreserveAspectFit
antialiasing: true
mipmap: true
onAnimationCompleted: {
if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardInserted ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.readingKeycard) {
root.sharedKeycardModule.currentState.doSecondaryAction()
}
}
}
Row {
spacing: Style.current.halfPadding
Layout.alignment: Qt.AlignCenter
Layout.preferredHeight: Constants.keycard.general.titleHeight
StatusIcon {
id: icon
@ -50,7 +146,8 @@ Item {
}
StatusLoadingIndicator {
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 {
id: title
@ -60,10 +157,71 @@ Item {
StatusBaseText {
id: message
Layout.alignment: Qt.AlignHCenter
Layout.alignment: Qt.AlignCenter
Layout.preferredWidth: parent.width
Layout.preferredHeight: Constants.keycard.general.messageHeight
horizontalAlignment: Text.AlignHCenter
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: [
@ -79,12 +237,12 @@ Item {
}
PropertyChanges {
target: image
source: Style.png("keycard/popup_card_reader@2x")
source: Style.png("keycard/empty-reader")
pattern: ""
}
PropertyChanges {
target: message
text: ""
visible: false
}
},
State {
@ -99,16 +257,48 @@ Item {
}
PropertyChanges {
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 {
target: message
visible: root.sharedKeycardModule.keycardData !== ""
text: qsTr("Check the card, it might be wrongly inserted")
font.pixelSize: Constants.keycard.general.fontSize3
text: root.sharedKeycardModule.keycardData & Constants.predefinedKeycardData.wronglyInsertedCard?
qsTr("Check the card, it might be wrongly inserted") :
""
font.pixelSize: Constants.keycard.general.fontSize2
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 {
name: Constants.keycardSharedState.readingKeycard
when: root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.readingKeycard
@ -121,12 +311,17 @@ Item {
}
PropertyChanges {
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 {
target: message
text: ""
visible: false
}
},
State {
@ -135,20 +330,74 @@ Item {
PropertyChanges {
target: title
text: qsTr("This is not a Keycard")
font.pixelSize: Constants.keycard.general.fontSize2
font.pixelSize: Constants.keycard.general.fontSize1
font.weight: Font.Bold
color: Theme.palette.dangerColor1
}
PropertyChanges {
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 {
target: message
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
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 {
@ -157,20 +406,45 @@ Item {
PropertyChanges {
target: title
text: qsTr("Keycard is empty")
font.pixelSize: Constants.keycard.general.fontSize2
font.pixelSize: Constants.keycard.general.fontSize1
font.weight: Font.Bold
color: Theme.palette.directColor1
}
PropertyChanges {
target: image
source: Style.png("keycard/popup_card_dark@2x")
source: Style.png("keycard/card-empty")
pattern: ""
}
PropertyChanges {
target: message
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
visible: true
}
},
State {
@ -185,12 +459,17 @@ Item {
}
PropertyChanges {
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 {
target: message
text: ""
visible: false
}
},
State {
@ -198,21 +477,127 @@ Item {
when: root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.factoryResetSuccess
PropertyChanges {
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.weight: Font.Bold
color: Theme.palette.directColor1
}
PropertyChanges {
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 {
target: message
text: qsTr("You can now use this Keycard as if it\nwas a brand new empty Keycard")
font.pixelSize: Constants.keycard.general.fontSize3
text: root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.setupNewKeycard?
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
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
ListModel {
id: root
property var words: []
Component.onCompleted: {
var xhr = new XMLHttpRequest();
xhr.open("GET", "english.txt");
xhr.onreadystatechange = function () {
if (xhr.readyState === XMLHttpRequest.DONE) {
var words = xhr.responseText.split('\n');
for (var i = 0; i < words.length; i++) {
if (words[i] !== "") {
insert(count, {"seedWord": words[i]});
root.words = xhr.responseText.split('\n');
for (var i = 0; i < root.words.length; i++) {
if (root.words[i] !== "") {
insert(count, {"seedWord": root.words[i]});
}
}
}

View File

@ -67,9 +67,16 @@ QtObject {
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 string general: "General"
readonly property string factoryReset: "FactoryReset"
readonly property string setupNewKeycard: "SetupNewKeycard"
}
readonly property QtObject keycardSharedState: QtObject {
@ -77,12 +84,31 @@ QtObject {
readonly property string pluginReader: "PluginReader"
readonly property string readingKeycard: "ReadingKeycard"
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 wrongPin: "WrongPin"
readonly property string maxPinRetriesReached: "MaxPinRetriesReached"
readonly property string factoryResetConfirmation: "FactoryResetConfirmation"
readonly property string factoryResetConfirmationDisplayMetadata: "FactoryResetConfirmationDisplayMetadata"
readonly property string factoryResetSuccess: "FactoryResetSuccess"
readonly property string keycardEmptyMetadata: "KeycardEmptyMetadata"
readonly property string keycardMetadataDisplay: "KeycardMetadataDisplay"
readonly property string keycardEmpty: "KeycardEmpty"
readonly property string keycardNotEmpty: "KeycardNotEmpty"
readonly property string notKeycard: "NotKeycard"
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 {
@ -339,6 +365,17 @@ QtObject {
readonly property int pukCellHeight: 60
readonly property int sharedFlowImageWidth: 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 {