feat(@desktop/keycard): UI for the keycard settings in case user has set up a keycard
Keycard settings view - UI - developed in a way that list of keycards is displayed if there is at least one keycard set up. If the a keycard is locked or gets locked it will be correctly marked in red. Selecting keycard from the list, its details may be seen and additional flows may be run for it (so far only unlock flow is developed). Fixes: #7025
This commit is contained in:
parent
86a2d963ad
commit
c1f4874e18
|
@ -5,6 +5,8 @@ import io_interface
|
|||
import ../../../../core/eventemitter
|
||||
|
||||
import ../../../shared_modules/keycard_popup/io_interface as keycard_shared_module
|
||||
import ../../../../../app_service/service/contacts/service as contact_service
|
||||
import ../../../../../app_service/service/wallet_account/service as wallet_account_service
|
||||
|
||||
logScope:
|
||||
topics = "profile-section-keycard-module-controller"
|
||||
|
@ -15,13 +17,16 @@ type
|
|||
Controller* = ref object of RootObj
|
||||
delegate: io_interface.AccessInterface
|
||||
events: EventEmitter
|
||||
walletAccountService: wallet_account_service.Service
|
||||
|
||||
proc newController*(delegate: io_interface.AccessInterface,
|
||||
events: EventEmitter):
|
||||
events: EventEmitter,
|
||||
walletAccountService: wallet_account_service.Service):
|
||||
Controller =
|
||||
result = Controller()
|
||||
result.delegate = delegate
|
||||
result.events = events
|
||||
result.walletAccountService = walletAccountService
|
||||
|
||||
proc delete*(self: Controller) =
|
||||
discard
|
||||
|
@ -38,3 +43,24 @@ proc init*(self: Controller) =
|
|||
if args.uniqueIdentifier != UNIQUE_SETTING_KEYCARD_MODULE_IDENTIFIER:
|
||||
return
|
||||
self.delegate.onDisplayKeycardSharedModuleFlow()
|
||||
|
||||
self.events.on(SIGNAL_LOGGEDIN_USER_IMAGE_CHANGED) do(e: Args):
|
||||
self.delegate.onLoggedInUserImageChanged()
|
||||
|
||||
self.events.on(SIGNAL_KEYCARD_LOCKED) do(e: Args):
|
||||
let args = KeycardActivityArgs(e)
|
||||
self.delegate.onKeycardLocked(args.keycardUid)
|
||||
|
||||
self.events.on(SIGNAL_KEYCARD_UNLOCKED) do(e: Args):
|
||||
let args = KeycardActivityArgs(e)
|
||||
self.delegate.onKeycardUnlocked(args.keycardUid)
|
||||
|
||||
self.events.on(SIGNAL_KEYCARD_UID_UPDATED) do(e: Args):
|
||||
let args = KeycardActivityArgs(e)
|
||||
self.delegate.onKeycardUidUpdated(args.keycardUid, args.keycardNewUid)
|
||||
|
||||
proc getAllMigratedKeyPairs*(self: Controller): seq[KeyPairDto] =
|
||||
return self.walletAccountService.getAllMigratedKeyPairs()
|
||||
|
||||
proc getWalletAccounts*(self: Controller): seq[wallet_account_service.WalletAccountDto] =
|
||||
return self.walletAccountService.fetchAccounts()
|
|
@ -37,7 +37,7 @@ method runImportOrRestoreViaSeedPhrasePopup*(self: AccessInterface) {.base.} =
|
|||
method runImportFromKeycardToAppPopup*(self: AccessInterface) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method runUnlockKeycardPopup*(self: AccessInterface) {.base.} =
|
||||
method runUnlockKeycardPopupForKeycardWithUid*(self: AccessInterface, keycardUid: string) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method runDisplayKeycardContentPopup*(self: AccessInterface) {.base.} =
|
||||
|
@ -46,6 +46,36 @@ method runDisplayKeycardContentPopup*(self: AccessInterface) {.base.} =
|
|||
method runFactoryResetPopup*(self: AccessInterface) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method runRenameKeycardPopup*(self: AccessInterface) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method runChangePinPopup*(self: AccessInterface) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method runCreateBackupCopyOfAKeycardPopup*(self: AccessInterface) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method runCreatePukPopup*(self: AccessInterface) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method runCreateNewPairingCodePopup*(self: AccessInterface) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method onLoggedInUserImageChanged*(self: AccessInterface) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method onKeycardLocked*(self: AccessInterface, keycardUid: string) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method onKeycardUnlocked*(self: AccessInterface, keycardUid: string) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method onKeycardUidUpdated*(self: AccessInterface, keycardUid: string, keycardNewUid: string) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method getKeycardDetailsAsJson*(self: AccessInterface, keycardUid: string): string {.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
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import NimQml, chronicles
|
||||
import NimQml, chronicles, json, marshal
|
||||
|
||||
import ./io_interface, ./view, ./controller
|
||||
import ../io_interface as delegate_interface
|
||||
|
||||
import ../../../../global/global_singleton
|
||||
import ../../../../core/eventemitter
|
||||
|
||||
import ../../../../../app_service/service/keycard/service as keycard_service
|
||||
|
@ -13,6 +14,7 @@ import ../../../../../app_service/service/wallet_account/service as wallet_accou
|
|||
import ../../../../../app_service/service/keychain/service as keychain_service
|
||||
|
||||
import ../../../shared_modules/keycard_popup/module as keycard_shared_module
|
||||
import ../../../shared_modules/keycard_popup/models/keycard_model
|
||||
|
||||
export io_interface
|
||||
|
||||
|
@ -35,6 +37,9 @@ type
|
|||
keychainService: keychain_service.Service
|
||||
keycardSharedModule: keycard_shared_module.AccessInterface
|
||||
|
||||
## Forward declarations
|
||||
proc buildKeycardList(self: Module)
|
||||
|
||||
proc newModule*(delegate: delegate_interface.AccessInterface,
|
||||
events: EventEmitter,
|
||||
keycardService: keycard_service.Service,
|
||||
|
@ -54,7 +59,7 @@ proc newModule*(delegate: delegate_interface.AccessInterface,
|
|||
result.keychainService = keychainService
|
||||
result.view = view.newView(result)
|
||||
result.viewVariant = newQVariant(result.view)
|
||||
result.controller = controller.newController(result, events)
|
||||
result.controller = controller.newController(result, events, walletAccountService)
|
||||
result.moduleLoaded = false
|
||||
|
||||
method delete*(self: Module) =
|
||||
|
@ -67,6 +72,7 @@ method delete*(self: Module) =
|
|||
method load*(self: Module) =
|
||||
self.controller.init()
|
||||
self.view.load()
|
||||
self.buildKeycardList()
|
||||
|
||||
method isLoaded*(self: Module): bool =
|
||||
return self.moduleLoaded
|
||||
|
@ -113,10 +119,11 @@ method runImportOrRestoreViaSeedPhrasePopup*(self: Module) =
|
|||
method runImportFromKeycardToAppPopup*(self: Module) =
|
||||
info "TODO: Import from Keycard to Status Desktop..."
|
||||
|
||||
method runUnlockKeycardPopup*(self: Module) =
|
||||
method runUnlockKeycardPopupForKeycardWithUid*(self: Module, keycardUid: string) =
|
||||
self.createSharedKeycardModule()
|
||||
if self.keycardSharedModule.isNil:
|
||||
return
|
||||
self.keycardSharedModule.setUidOfAKeycardWhichNeedToBeUnlocked(keycardUid)
|
||||
self.keycardSharedModule.runFlow(keycard_shared_module.FlowType.UnlockKeycard)
|
||||
|
||||
method runDisplayKeycardContentPopup*(self: Module) =
|
||||
|
@ -129,4 +136,93 @@ method runFactoryResetPopup*(self: Module) =
|
|||
self.createSharedKeycardModule()
|
||||
if self.keycardSharedModule.isNil:
|
||||
return
|
||||
self.keycardSharedModule.runFlow(keycard_shared_module.FlowType.FactoryReset)
|
||||
self.keycardSharedModule.runFlow(keycard_shared_module.FlowType.FactoryReset)
|
||||
|
||||
method runRenameKeycardPopup*(self: Module) =
|
||||
info "TODO: Rename Keycard..."
|
||||
|
||||
method runChangePinPopup*(self: Module) =
|
||||
info "TODO: Change PIN for a Keycard..."
|
||||
|
||||
method runCreateBackupCopyOfAKeycardPopup*(self: Module) =
|
||||
info "TODO: Create a Backup Copy of a Keycard..."
|
||||
|
||||
method runCreatePukPopup*(self: Module) =
|
||||
info "TODO: Create PUK for a Keycard..."
|
||||
|
||||
method runCreateNewPairingCodePopup*(self: Module) =
|
||||
info "TODO: Create New Pairing Code for a Keycard..."
|
||||
|
||||
proc buildKeycardList(self: Module) =
|
||||
let findAccountByAccountAddress = proc(accounts: seq[WalletAccountDto], address: string): WalletAccountDto =
|
||||
for i in 0 ..< accounts.len:
|
||||
if(accounts[i].address == address):
|
||||
return accounts[i]
|
||||
return nil
|
||||
|
||||
let accounts = self.controller.getWalletAccounts()
|
||||
var items: seq[KeycardItem]
|
||||
let migratedKeyPairs = self.controller.getAllMigratedKeyPairs()
|
||||
for kp in migratedKeyPairs:
|
||||
var knownAccounts: seq[WalletAccountDto]
|
||||
for accAddr in kp.accountsAddresses:
|
||||
let account = findAccountByAccountAddress(accounts, accAddr)
|
||||
if account.isNil:
|
||||
## we should never be here cause we need to remove deleted accounts from the `keypairs` table and sync
|
||||
## that state accross different app instances
|
||||
continue
|
||||
knownAccounts.add(account)
|
||||
if knownAccounts.len == 0:
|
||||
continue
|
||||
var item = initKeycardItem(keycardUid = kp.keycardUid,
|
||||
pubKey = knownAccounts[0].publicKey,
|
||||
keyUid = kp.keyUid,
|
||||
locked = kp.keycardLocked,
|
||||
name = kp.keycardName,
|
||||
derivedFrom = knownAccounts[0].derivedfrom)
|
||||
for ka in knownAccounts:
|
||||
if ka.walletType == WalletTypeDefaultStatusAccount:
|
||||
item.setPairType(KeyPairType.Profile)
|
||||
item.setPubKey(singletonInstance.userProfile.getPubKey())
|
||||
item.setImage(singletonInstance.userProfile.getIcon())
|
||||
if ka.walletType == WalletTypeSeed:
|
||||
item.setPairType(KeyPairType.SeedImport)
|
||||
item.setIcon("wallet")
|
||||
if ka.walletType == WalletTypeKey:
|
||||
item.setPairType(KeyPairType.PrivateKeyImport)
|
||||
item.setIcon("wallet")
|
||||
item.addAccount(ka.name, ka.path, ka.address, ka.emoji, ka.color, icon = "", balance = 0.0)
|
||||
items.add(item)
|
||||
self.view.setKeycardItems(items)
|
||||
|
||||
method onLoggedInUserImageChanged*(self: Module) =
|
||||
self.view.keycardModel().setImage(singletonInstance.userProfile.getPubKey(), singletonInstance.userProfile.getIcon())
|
||||
self.view.emitKeycardProfileChangedSignal()
|
||||
|
||||
method onKeycardLocked*(self: Module, keycardUid: string) =
|
||||
self.view.keycardModel().setLocked(keycardUid, true)
|
||||
self.view.emitKeycardDetailsChangedSignal(keycardUid)
|
||||
|
||||
method onKeycardUnlocked*(self: Module, keycardUid: string) =
|
||||
self.view.keycardModel().setLocked(keycardUid, false)
|
||||
self.view.emitKeycardDetailsChangedSignal(keycardUid)
|
||||
|
||||
method onKeycardUidUpdated*(self: Module, keycardUid: string, keycardNewUid: string) =
|
||||
self.view.keycardModel().setKeycardUid(keycardUid, keycardNewUid)
|
||||
self.view.emitKeycardUidChangedSignal(keycardUid, keycardNewUid)
|
||||
|
||||
method getKeycardDetailsAsJson*(self: Module, keycardUid: string): string =
|
||||
let item = self.view.keycardModel().getItemByKeycardUid(keycardUid)
|
||||
let jsonObj = %* {
|
||||
"keycardUid": item.keycardUid,
|
||||
"pubKey": item.pubkey,
|
||||
"keyUid": item.keyUid,
|
||||
"locked": item.locked,
|
||||
"name": item.name,
|
||||
"image": item.image,
|
||||
"icon": item.icon,
|
||||
"pairType": $item.pairType.int,
|
||||
"derivedFrom": item.derivedFrom,
|
||||
"accounts": $item.accounts
|
||||
}
|
||||
return $jsonObj
|
|
@ -1,19 +1,31 @@
|
|||
import NimQml
|
||||
|
||||
import ../../../shared_modules/keycard_popup/models/keycard_model
|
||||
|
||||
import ./io_interface
|
||||
|
||||
QtObject:
|
||||
type
|
||||
View* = ref object of QObject
|
||||
delegate: io_interface.AccessInterface
|
||||
keycardModel: KeycardModel
|
||||
keycardModelVariant: QVariant
|
||||
|
||||
proc delete*(self: View) =
|
||||
self.QObject.delete
|
||||
if not self.keycardModel.isNil:
|
||||
self.keycardModel.delete
|
||||
if not self.keycardModelVariant.isNil:
|
||||
self.keycardModelVariant.delete
|
||||
|
||||
proc newView*(delegate: io_interface.AccessInterface): View =
|
||||
new(result, delete)
|
||||
result.QObject.setup
|
||||
result.delegate = delegate
|
||||
if result.keycardModel.isNil:
|
||||
result.keycardModel = newKeycardModel()
|
||||
if result.keycardModelVariant.isNil:
|
||||
result.keycardModelVariant = newQVariant(result.keycardModel)
|
||||
|
||||
proc load*(self: View) =
|
||||
self.delegate.viewDidLoad()
|
||||
|
@ -43,11 +55,57 @@ QtObject:
|
|||
proc runImportFromKeycardToAppPopup*(self: View) {.slot.} =
|
||||
self.delegate.runImportFromKeycardToAppPopup()
|
||||
|
||||
proc runUnlockKeycardPopup*(self: View) {.slot.} =
|
||||
self.delegate.runUnlockKeycardPopup()
|
||||
proc runUnlockKeycardPopupForKeycardWithUid*(self: View, keycardUid: string) {.slot.} =
|
||||
self.delegate.runUnlockKeycardPopupForKeycardWithUid(keycardUid)
|
||||
|
||||
proc runDisplayKeycardContentPopup*(self: View) {.slot.} =
|
||||
self.delegate.runDisplayKeycardContentPopup()
|
||||
|
||||
proc runFactoryResetPopup*(self: View) {.slot.} =
|
||||
self.delegate.runFactoryResetPopup()
|
||||
self.delegate.runFactoryResetPopup()
|
||||
|
||||
proc runRenameKeycardPopup*(self: View) {.slot.} =
|
||||
self.delegate.runRenameKeycardPopup()
|
||||
|
||||
proc runChangePinPopup*(self: View) {.slot.} =
|
||||
self.delegate.runChangePinPopup()
|
||||
|
||||
proc runCreateBackupCopyOfAKeycardPopup*(self: View) {.slot.} =
|
||||
self.delegate.runCreateBackupCopyOfAKeycardPopup()
|
||||
|
||||
proc runCreatePukPopup*(self: View) {.slot.} =
|
||||
self.delegate.runCreatePukPopup()
|
||||
|
||||
proc runCreateNewPairingCodePopup*(self: View) {.slot.} =
|
||||
self.delegate.runCreateNewPairingCodePopup()
|
||||
|
||||
proc keycardModel*(self: View): KeycardModel =
|
||||
return self.keycardModel
|
||||
|
||||
proc keycardModelChanged(self: View) {.signal.}
|
||||
proc getKeycardModel(self: View): QVariant {.slot.} =
|
||||
if self.keycardModelVariant.isNil:
|
||||
return newQVariant()
|
||||
return self.keycardModelVariant
|
||||
QtProperty[QVariant] keycardModel:
|
||||
read = getKeycardModel
|
||||
notify = keycardModelChanged
|
||||
|
||||
proc setKeycardItems*(self: View, items: seq[KeycardItem]) =
|
||||
self.keycardModel.setItems(items)
|
||||
self.keycardModelChanged()
|
||||
|
||||
proc getKeycardDetailsAsJson*(self: View, keycardUid: string): string {.slot.} =
|
||||
return self.delegate.getKeycardDetailsAsJson(keycardUid)
|
||||
|
||||
proc keycardProfileChanged(self: View) {.signal.}
|
||||
proc emitKeycardProfileChangedSignal*(self: View) =
|
||||
self.keycardProfileChanged()
|
||||
|
||||
proc keycardUidChanged(self: View, oldKcUid: string, newKcUid: string) {.signal.}
|
||||
proc emitKeycardUidChangedSignal*(self: View, oldKcUid: string, newKcUid: string) =
|
||||
self.keycardUidChanged(oldKcUid, newKcUid)
|
||||
|
||||
proc keycardDetailsChanged(self: View, kcUid: string) {.signal.}
|
||||
proc emitKeycardDetailsChangedSignal*(self: View, kcUid: string) =
|
||||
self.keycardDetailsChanged(kcUid)
|
|
@ -31,6 +31,7 @@ type
|
|||
connectionKeycardResponse: UUID
|
||||
tmpKeycardContainsMetadata: bool
|
||||
tmpCardMetadata: CardMetadata
|
||||
tmpKeycardUidForUnlocking: string
|
||||
tmpPin: string
|
||||
tmpPinMatch: bool
|
||||
tmpPuk: string
|
||||
|
@ -141,6 +142,12 @@ proc containsMetadata*(self: Controller): bool =
|
|||
proc setContainsMetadata*(self: Controller, value: bool) =
|
||||
self.tmpKeycardContainsMetadata = value
|
||||
|
||||
proc setUidOfAKeycardWhichNeedToBeUnlocked*(self: Controller, value: string) =
|
||||
self.tmpKeycardUidForUnlocking = value
|
||||
|
||||
proc getUidOfAKeycardWhichNeedToBeUnlocked*(self: Controller): string =
|
||||
return self.tmpKeycardUidForUnlocking
|
||||
|
||||
proc setPin*(self: Controller, value: string) =
|
||||
self.tmpPin = value
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ proc doMigration(self: MigratingKeyPairState, controller: Controller) =
|
|||
self.migrationSuccess = self.migrationSuccess and controller.convertSelectedKeyPairToKeycardAccount(password)
|
||||
if not self.migrationSuccess:
|
||||
return
|
||||
controller.runStoreMetadataFlow(selectedKeyPairDto.keypairName, controller.getPin(),
|
||||
controller.runStoreMetadataFlow(selectedKeyPairDto.keycardName, controller.getPin(),
|
||||
controller.getSelectedKeyPairWalletPaths())
|
||||
|
||||
method executePrimaryCommand*(self: MigratingKeyPairState, controller: Controller) =
|
||||
|
|
|
@ -24,9 +24,15 @@ method executeTertiaryCommand*(self: ReadingKeycardState, controller: Controller
|
|||
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)
|
||||
return self.resolveKeycardNextState(flowType, flowEvent, controller)
|
||||
|
||||
method resolveKeycardNextState*(self: ReadingKeycardState, keycardFlowType: string, keycardEvent: KeycardEvent,
|
||||
controller: Controller): State =
|
||||
if self.flowType == FlowType.UnlockKeycard:
|
||||
let ensureKeycardPresenceState = ensureReaderAndCardPresence(self, keycardFlowType, keycardEvent, controller)
|
||||
if ensureKeycardPresenceState.isNil: # means the keycard is inserted
|
||||
let kcUid = controller.getUidOfAKeycardWhichNeedToBeUnlocked()
|
||||
if kcUid.len > 0 and kcUid != keycardEvent.instanceUID:
|
||||
return createState(StateType.WrongKeycard, 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)
|
|
@ -8,6 +8,11 @@ proc newWrongKeycardState*(flowType: FlowType, backState: State): WrongKeycardSt
|
|||
proc delete*(self: WrongKeycardState) =
|
||||
self.State.delete
|
||||
|
||||
method executePrimaryCommand*(self: WrongKeycardState, controller: Controller) =
|
||||
if self.flowType == FlowType.UnlockKeycard:
|
||||
controller.terminateCurrentFlow(lastStepInTheCurrentFlow = true)
|
||||
|
||||
method executeTertiaryCommand*(self: WrongKeycardState, controller: Controller) =
|
||||
if self.flowType == FlowType.Authentication:
|
||||
controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false)
|
||||
if self.flowType == FlowType.Authentication or
|
||||
self.flowType == FlowType.UnlockKeycard:
|
||||
controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false)
|
|
@ -91,6 +91,9 @@ method onKeycardResponse*(self: AccessInterface, keycardFlowType: string, keycar
|
|||
method runFlow*(self: AccessInterface, flowToRun: FlowType, keyUid = "", bip44Path = "", txHash = "") {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method setUidOfAKeycardWhichNeedToBeUnlocked*(self: AccessInterface, value: string) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method setPin*(self: AccessInterface, value: string) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
|
|
|
@ -28,7 +28,26 @@ type
|
|||
derivedFrom: string
|
||||
pairType: KeyPairType
|
||||
accounts: seq[WalletAccountDetails]
|
||||
|
||||
|
||||
proc setup*(self: KeyPairItem,
|
||||
pubKey: string,
|
||||
keyUid: string,
|
||||
locked: bool,
|
||||
name: string,
|
||||
image: string,
|
||||
icon: string,
|
||||
pairType: KeyPairType,
|
||||
derivedFrom: string
|
||||
) =
|
||||
self.pubKey = pubKey
|
||||
self.keyUid = keyUid
|
||||
self.locked = locked
|
||||
self.name = name
|
||||
self.image = image
|
||||
self.icon = icon
|
||||
self.pairType = pairType
|
||||
self.derivedFrom = derivedFrom
|
||||
|
||||
proc initKeyPairItem*(
|
||||
pubKey = "",
|
||||
keyUid = "",
|
||||
|
@ -40,19 +59,14 @@ proc initKeyPairItem*(
|
|||
derivedFrom = ""
|
||||
): KeyPairItem =
|
||||
result = KeyPairItem()
|
||||
result.pubKey = pubKey
|
||||
result.keyUid = keyUid
|
||||
result.name = name
|
||||
result.image = image
|
||||
result.icon = icon
|
||||
result.pairType = pairType
|
||||
result.derivedFrom = derivedFrom
|
||||
result.setup(pubKey, keyUid, locked, name, image, icon, pairType, derivedFrom)
|
||||
|
||||
proc `$`*(self: KeyPairItem): string =
|
||||
result = fmt"""KeyPairItem[
|
||||
pubKey: {self.pubkey},
|
||||
keyUid: {self.keyUid},
|
||||
name: {self.name},
|
||||
locked: {self.locked},
|
||||
image: {self.image},
|
||||
icon: {self.icon},
|
||||
pairType: {$self.pairType},
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
import strformat
|
||||
import key_pair_item
|
||||
|
||||
export key_pair_item
|
||||
|
||||
type
|
||||
KeycardItem* = ref object of KeyPairItem
|
||||
keycardUid: string
|
||||
|
||||
proc initKeycardItem*(
|
||||
keycardUid = "",
|
||||
pubKey = "",
|
||||
keyUid = "",
|
||||
locked = false,
|
||||
name = "",
|
||||
image = "",
|
||||
icon = "",
|
||||
pairType = KeyPairType.Unknown,
|
||||
derivedFrom = ""
|
||||
): KeycardItem =
|
||||
result = KeycardItem()
|
||||
result.KeyPairItem.setup(pubKey, keyUid, locked, name, image, icon, pairType, derivedFrom)
|
||||
result.keycardUid = keycardUid
|
||||
|
||||
proc `$`*(self: KeycardItem): string =
|
||||
result = fmt"""KeycardItem[
|
||||
keycardUid: {self.keycardUid},
|
||||
pubKey: {self.pubkey},
|
||||
keyUid: {self.keyUid},
|
||||
locked: {self.locked},
|
||||
name: {self.name},
|
||||
image: {self.image},
|
||||
icon: {self.icon},
|
||||
pairType: {$self.pairType},
|
||||
derivedFrom: {self.derivedFrom},
|
||||
accounts: {self.accounts}
|
||||
]"""
|
||||
|
||||
proc keycardUid*(self: KeycardItem): string {.inline.} =
|
||||
self.keycardUid
|
||||
proc setKeycardUid*(self: KeycardItem, value: string) {.inline.} =
|
||||
self.keycardUid = value
|
|
@ -0,0 +1,128 @@
|
|||
import NimQml, Tables, strformat
|
||||
import keycard_item
|
||||
|
||||
export keycard_item
|
||||
|
||||
type
|
||||
ModelRole {.pure.} = enum
|
||||
PubKey = UserRole + 1
|
||||
KeycardUid
|
||||
Locked
|
||||
Name
|
||||
Image
|
||||
Icon
|
||||
PairType
|
||||
Accounts
|
||||
DerivedFrom
|
||||
|
||||
QtObject:
|
||||
type
|
||||
KeycardModel* = ref object of QAbstractListModel
|
||||
items: seq[KeycardItem]
|
||||
|
||||
proc delete(self: KeycardModel) =
|
||||
self.items = @[]
|
||||
self.QAbstractListModel.delete
|
||||
|
||||
proc setup(self: KeycardModel) =
|
||||
self.QAbstractListModel.setup
|
||||
|
||||
proc newKeycardModel*(): KeycardModel =
|
||||
new(result, delete)
|
||||
result.setup
|
||||
|
||||
proc countChanged(self: KeycardModel) {.signal.}
|
||||
proc getCount*(self: KeycardModel): int {.slot.} =
|
||||
self.items.len
|
||||
QtProperty[int]count:
|
||||
read = getCount
|
||||
notify = countChanged
|
||||
|
||||
proc setItems*(self: KeycardModel, items: seq[KeycardItem]) =
|
||||
self.beginResetModel()
|
||||
self.items = items
|
||||
self.endResetModel()
|
||||
self.countChanged()
|
||||
|
||||
proc `$`*(self: KeycardModel): string =
|
||||
for i in 0 ..< self.items.len:
|
||||
result &= fmt"""KeycardModel:
|
||||
[{i}]:({$self.items[i]})
|
||||
"""
|
||||
|
||||
method rowCount(self: KeycardModel, index: QModelIndex = nil): int =
|
||||
return self.items.len
|
||||
|
||||
method roleNames(self: KeycardModel): Table[int, string] =
|
||||
{
|
||||
ModelRole.PubKey.int: "pubKey",
|
||||
ModelRole.KeycardUid.int: "keycardUid",
|
||||
ModelRole.Locked.int: "locked",
|
||||
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: KeycardModel, 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.KeycardUid:
|
||||
result = newQVariant(item.keycardUid)
|
||||
of ModelRole.Locked:
|
||||
result = newQVariant(item.locked)
|
||||
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 getItemByKeycardUid*(self: KeycardModel, keycardUid: string): KeycardItem =
|
||||
for i in 0 ..< self.items.len:
|
||||
if(self.items[i].keycardUid == keycardUid):
|
||||
return self.items[i]
|
||||
return nil
|
||||
|
||||
proc findIndexForMember(self: KeycardModel, pubKey: string): int =
|
||||
for i in 0 ..< self.items.len:
|
||||
if(self.items[i].pubKey == pubKey):
|
||||
return i
|
||||
return -1
|
||||
|
||||
proc setImage*(self: KeycardModel, pubKey: string, image: string) =
|
||||
let ind = self.findIndexForMember(pubKey)
|
||||
if(ind == -1):
|
||||
return
|
||||
self.items[ind].setImage(image)
|
||||
let index = self.createIndex(ind, 0, nil)
|
||||
self.dataChanged(index, index, @[ModelRole.Image.int])
|
||||
|
||||
proc setLocked*(self: KeycardModel, keycardUid: string, locked: bool) =
|
||||
for i in 0 ..< self.items.len:
|
||||
if(self.items[i].keycardUid == keycardUid):
|
||||
self.items[i].setLocked(locked)
|
||||
let index = self.createIndex(i, 0, nil)
|
||||
self.dataChanged(index, index, @[ModelRole.Locked.int])
|
||||
|
||||
proc setKeycardUid*(self: KeycardModel, keycardUid: string, keycardNewUid: string) =
|
||||
for i in 0 ..< self.items.len:
|
||||
if(self.items[i].keycardUid == keycardUid):
|
||||
self.items[i].setKeycardUid(keycardNewUid)
|
||||
let index = self.createIndex(i, 0, nil)
|
||||
self.dataChanged(index, index, @[ModelRole.KeycardUid.int])
|
|
@ -66,6 +66,9 @@ method getKeycardData*[T](self: Module[T]): string =
|
|||
method setKeycardData*[T](self: Module[T], value: string) =
|
||||
self.view.setKeycardData(value)
|
||||
|
||||
method setUidOfAKeycardWhichNeedToBeUnlocked*[T](self: Module[T], value: string) =
|
||||
self.controller.setUidOfAKeycardWhichNeedToBeUnlocked(value)
|
||||
|
||||
method setPin*[T](self: Module[T], value: string) =
|
||||
self.controller.setPin(value)
|
||||
|
||||
|
@ -349,7 +352,7 @@ method runFlow*[T](self: Module[T], flowToRun: FlowType, keyUid = "", bip44Path
|
|||
method setSelectedKeyPair*[T](self: Module[T], item: KeyPairItem) =
|
||||
var paths: seq[string]
|
||||
var keyPairDto = KeyPairDto(keycardUid: "", # will be set during migration
|
||||
keypairName: item.name,
|
||||
keycardName: item.name,
|
||||
keycardLocked: item.locked,
|
||||
keyUid: item.keyUid)
|
||||
for a in item.accountsAsArr():
|
||||
|
|
|
@ -4,7 +4,7 @@ include ../../common/json_utils
|
|||
|
||||
type KeyPairDto* = object
|
||||
keycardUid*: string
|
||||
keypairName*: string
|
||||
keycardName*: string
|
||||
keycardLocked*: bool
|
||||
accountsAddresses*: seq[string]
|
||||
keyUid*: string
|
||||
|
@ -12,7 +12,7 @@ type KeyPairDto* = object
|
|||
proc toKeyPairDto*(jsonObj: JsonNode): KeyPairDto =
|
||||
result = KeyPairDto()
|
||||
discard jsonObj.getProp("keycard-uid", result.keycardUid)
|
||||
discard jsonObj.getProp("keypair-name", result.keypairName)
|
||||
discard jsonObj.getProp("keycard-name", result.keycardName)
|
||||
discard jsonObj.getProp("keycard-locked", result.keycardLocked)
|
||||
discard jsonObj.getProp("key-uid", result.keyUid)
|
||||
|
||||
|
|
|
@ -33,6 +33,10 @@ const SIGNAL_WALLET_ACCOUNT_NETWORK_ENABLED_UPDATED* = "walletAccount/networkEna
|
|||
const SIGNAL_WALLET_ACCOUNT_DERIVED_ADDRESS_READY* = "walletAccount/derivedAddressesReady"
|
||||
const SIGNAL_WALLET_ACCOUNT_TOKENS_REBUILT* = "walletAccount/tokensRebuilt"
|
||||
|
||||
const SIGNAL_KEYCARD_LOCKED* = "keycardLocked"
|
||||
const SIGNAL_KEYCARD_UNLOCKED* = "keycardUnlocked"
|
||||
const SIGNAL_KEYCARD_UID_UPDATED* = "keycardUidUpdated"
|
||||
|
||||
var
|
||||
balanceCache {.threadvar.}: Table[string, float64]
|
||||
|
||||
|
@ -81,6 +85,10 @@ type DerivedAddressesArgs* = ref object of Args
|
|||
type TokensPerAccountArgs* = ref object of Args
|
||||
accountsTokens*: OrderedTable[string, seq[WalletTokenDto]] # [wallet address, list of tokens]
|
||||
|
||||
type KeycardActivityArgs* = ref object of Args
|
||||
keycardUid*: string
|
||||
keycardNewUid*: string
|
||||
|
||||
const CheckBalanceSlotExecuteIntervalInSeconds = 15 * 60 # 15 mins
|
||||
const CheckBalanceTimerIntervalInMilliseconds = 5000 # 5 sec
|
||||
|
||||
|
@ -487,7 +495,7 @@ QtObject:
|
|||
try:
|
||||
let response = backend.addMigratedKeyPair(
|
||||
keyPair.keycardUid,
|
||||
keyPair.keyPairName,
|
||||
keyPair.keycardName,
|
||||
keyPair.keyUid,
|
||||
keyPair.accountsAddresses)
|
||||
return self.responseHasNoErrors("addMigratedKeyPair", response)
|
||||
|
@ -522,7 +530,9 @@ QtObject:
|
|||
proc setKeycardLocked*(self: Service, keycardUid: string): bool =
|
||||
try:
|
||||
let response = backend.keycardLocked(keycardUid)
|
||||
return self.responseHasNoErrors("setKeycardLocked", response)
|
||||
result = self.responseHasNoErrors("setKeycardLocked", response)
|
||||
if result:
|
||||
self.events.emit(SIGNAL_KEYCARD_LOCKED, KeycardActivityArgs(keycardUid: keycardUid))
|
||||
except Exception as e:
|
||||
error "error: ", procName="setKeycardLocked", errName = e.name, errDesription = e.msg
|
||||
return false
|
||||
|
@ -530,7 +540,9 @@ QtObject:
|
|||
proc setKeycardUnlocked*(self: Service, keycardUid: string): bool =
|
||||
try:
|
||||
let response = backend.keycardUnlocked(keycardUid)
|
||||
return self.responseHasNoErrors("setKeycardUnlocked", response)
|
||||
result = self.responseHasNoErrors("setKeycardUnlocked", response)
|
||||
if result:
|
||||
self.events.emit(SIGNAL_KEYCARD_UNLOCKED, KeycardActivityArgs(keycardUid: keycardUid))
|
||||
except Exception as e:
|
||||
error "error: ", procName="setKeycardUnlocked", errName = e.name, errDesription = e.msg
|
||||
return false
|
||||
|
@ -538,7 +550,9 @@ QtObject:
|
|||
proc updateKeycardUid*(self: Service, oldKeycardUid: string, newKeycardUid: string): bool =
|
||||
try:
|
||||
let response = backend.updateKeycardUID(oldKeycardUid, newKeycardUid)
|
||||
return self.responseHasNoErrors("updateKeycardUid", response)
|
||||
result = self.responseHasNoErrors("updateKeycardUid", response)
|
||||
if result:
|
||||
self.events.emit(SIGNAL_KEYCARD_UID_UPDATED, KeycardActivityArgs(keycardUid: oldKeycardUid, keycardNewUid: newKeycardUid))
|
||||
except Exception as e:
|
||||
error "error: ", procName="updateKeycardUid", errName = e.name, errDesription = e.msg
|
||||
return false
|
||||
|
|
|
@ -32,6 +32,9 @@ StatusSectionLayout {
|
|||
case 4:
|
||||
walletView.resetStack();
|
||||
break;
|
||||
case Constants.settingsSubsection.keycard:
|
||||
keycardView.handleBackAction();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -87,7 +90,11 @@ StatusSectionLayout {
|
|||
}
|
||||
if (currentIndex === 1) {
|
||||
root.store.backButtonName = root.store.getNameForSubsection(Constants.settingsSubsection.messaging);
|
||||
} else {
|
||||
}
|
||||
else if (currentIndex === Constants.settingsSubsection.keycard) {
|
||||
keycardView.handleBackAction();
|
||||
}
|
||||
else {
|
||||
root.store.backButtonName = "";
|
||||
}
|
||||
}
|
||||
|
@ -226,11 +233,14 @@ StatusSectionLayout {
|
|||
}
|
||||
|
||||
KeycardView {
|
||||
id: keycardView
|
||||
implicitWidth: parent.width
|
||||
implicitHeight: parent.height
|
||||
|
||||
profileSectionStore: root.store
|
||||
keycardStore: root.store.keycardStore
|
||||
sectionTitle: root.store.getNameForSubsection(Constants.settingsSubsection.keycard)
|
||||
mainSectionTitle: root.store.getNameForSubsection(Constants.settingsSubsection.keycard)
|
||||
contentWidth: d.contentWidth
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,8 +22,8 @@ QtObject {
|
|||
root.keycardModule.runImportFromKeycardToAppPopup()
|
||||
}
|
||||
|
||||
function runUnlockKeycardPopup() {
|
||||
root.keycardModule.runUnlockKeycardPopup()
|
||||
function runUnlockKeycardPopupForKeycardWithUid(keycardUid) {
|
||||
root.keycardModule.runUnlockKeycardPopupForKeycardWithUid(keycardUid)
|
||||
}
|
||||
|
||||
function runDisplayKeycardContentPopup() {
|
||||
|
@ -33,4 +33,47 @@ QtObject {
|
|||
function runFactoryResetPopup() {
|
||||
root.keycardModule.runFactoryResetPopup()
|
||||
}
|
||||
|
||||
function runRenameKeycardPopup() {
|
||||
root.keycardModule.runRenameKeycardPopup()
|
||||
}
|
||||
|
||||
function runChangePinPopup() {
|
||||
root.keycardModule.runChangePinPopup()
|
||||
}
|
||||
|
||||
function runCreateBackupCopyOfAKeycardPopup() {
|
||||
root.keycardModule.runCreateBackupCopyOfAKeycardPopup()
|
||||
}
|
||||
|
||||
function runCreatePukPopup() {
|
||||
root.keycardModule.runCreatePukPopup()
|
||||
}
|
||||
|
||||
function runCreateNewPairingCodePopup() {
|
||||
root.keycardModule.runCreateNewPairingCodePopup()
|
||||
}
|
||||
|
||||
function getKeycardDetailsAsJson(keycardUid) {
|
||||
let jsonObj = root.keycardModule.getKeycardDetailsAsJson(keycardUid)
|
||||
try {
|
||||
let obj = JSON.parse(jsonObj)
|
||||
return obj
|
||||
}
|
||||
catch (e) {
|
||||
console.debug("error parsing keycard details for keycard uid: ", keycardUid, " error: ", e.message)
|
||||
return {
|
||||
keycardUid: keycardUid,
|
||||
pubKey: "",
|
||||
keyUid: "",
|
||||
locked: false,
|
||||
name: "",
|
||||
image: "",
|
||||
icon: "",
|
||||
pairType: Constants.keycard.keyPairType.unknown,
|
||||
derivedFrom: "",
|
||||
accounts: [],
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,25 +1,21 @@
|
|||
import QtQuick 2.13
|
||||
import QtQuick.Controls 2.13
|
||||
import QtQuick.Layouts 1.13
|
||||
import QtQuick 2.14
|
||||
import QtQuick.Controls 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.Components 0.1
|
||||
import StatusQ.Popups 0.1
|
||||
|
||||
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 "./keycard"
|
||||
|
||||
SettingsContentBase {
|
||||
id: root
|
||||
|
||||
property ProfileSectionStore profileSectionStore
|
||||
property KeycardStore keycardStore
|
||||
property string mainSectionTitle: ""
|
||||
|
||||
titleRowComponentLoader.sourceComponent: StatusButton {
|
||||
text: qsTr("Get Keycard")
|
||||
|
@ -28,9 +24,44 @@ SettingsContentBase {
|
|||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: contentColumn
|
||||
spacing: Constants.settingsSection.itemSpacing
|
||||
function handleBackAction() {
|
||||
if (stackLayout.currentIndex === d.detailsViewIndex) {
|
||||
root.profileSectionStore.backButtonName = ""
|
||||
root.sectionTitle = root.mainSectionTitle
|
||||
stackLayout.currentIndex = d.mainViewIndex
|
||||
}
|
||||
}
|
||||
|
||||
StackLayout {
|
||||
id: stackLayout
|
||||
|
||||
currentIndex: d.mainViewIndex
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
readonly property int mainViewIndex: 0
|
||||
readonly property int detailsViewIndex: 1
|
||||
|
||||
property string observedKeycardUid: ""
|
||||
}
|
||||
|
||||
MainView {
|
||||
Layout.preferredWidth: root.contentWidth
|
||||
keycardStore: root.keycardStore
|
||||
|
||||
onDisplayKeycardDetails: {
|
||||
d.observedKeycardUid = keycardUid
|
||||
root.profileSectionStore.backButtonName = root.mainSectionTitle
|
||||
root.sectionTitle = keycardName
|
||||
stackLayout.currentIndex = d.detailsViewIndex
|
||||
}
|
||||
}
|
||||
|
||||
DetailsView {
|
||||
Layout.preferredWidth: root.contentWidth
|
||||
keycardStore: root.keycardStore
|
||||
keycardUid: d.observedKeycardUid
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: root.keycardStore.keycardModule
|
||||
|
@ -41,6 +72,12 @@ SettingsContentBase {
|
|||
onDestroyKeycardSharedModuleFlow: {
|
||||
keycardPopup.active = false
|
||||
}
|
||||
|
||||
onKeycardUidChanged: {
|
||||
if (d.observedKeycardUid === oldKcUid) {
|
||||
d.observedKeycardUid = newKcUid
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
|
@ -54,136 +91,5 @@ SettingsContentBase {
|
|||
keycardPopup.item.open()
|
||||
}
|
||||
}
|
||||
|
||||
Image {
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
Layout.preferredHeight: sourceSize.height
|
||||
Layout.preferredWidth: sourceSize.width
|
||||
fillMode: Image.PreserveAspectFit
|
||||
antialiasing: true
|
||||
source: Style.png("keycard/security-keycard@2x")
|
||||
mipmap: true
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Style.current.halfPadding
|
||||
}
|
||||
|
||||
StyledText {
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
font.pixelSize: Constants.settingsSection.importantInfoFontSize
|
||||
color: Style.current.directColor1
|
||||
text: qsTr("Secure your funds. Keep your profile safe.")
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Style.current.halfPadding
|
||||
}
|
||||
|
||||
StatusListItem {
|
||||
Layout.preferredWidth: root.contentWidth
|
||||
title: qsTr("Setup a new Keycard with an existing account")
|
||||
components: [
|
||||
StatusIcon {
|
||||
icon: "chevron-down"
|
||||
rotation: 270
|
||||
color: Theme.palette.baseColor1
|
||||
}
|
||||
]
|
||||
onClicked: {
|
||||
root.keycardStore.runSetupKeycardPopup()
|
||||
}
|
||||
}
|
||||
|
||||
StatusSectionHeadline {
|
||||
Layout.preferredWidth: root.contentWidth
|
||||
Layout.leftMargin: Style.current.padding
|
||||
Layout.rightMargin: Style.current.padding
|
||||
text: qsTr("Create, import or restore a Keycard account")
|
||||
}
|
||||
|
||||
StatusListItem {
|
||||
Layout.preferredWidth: root.contentWidth
|
||||
title: qsTr("Generate a seed phrase")
|
||||
components: [
|
||||
StatusIcon {
|
||||
icon: "chevron-down"
|
||||
rotation: 270
|
||||
color: Theme.palette.baseColor1
|
||||
}
|
||||
]
|
||||
onClicked: {
|
||||
root.keycardStore.runGenerateSeedPhrasePopup()
|
||||
}
|
||||
}
|
||||
|
||||
StatusListItem {
|
||||
Layout.preferredWidth: root.contentWidth
|
||||
title: qsTr("Import or restore via a seed phrase")
|
||||
components: [
|
||||
StatusIcon {
|
||||
icon: "chevron-down"
|
||||
rotation: 270
|
||||
color: Theme.palette.baseColor1
|
||||
}
|
||||
]
|
||||
onClicked: {
|
||||
root.keycardStore.runImportOrRestoreViaSeedPhrasePopup()
|
||||
}
|
||||
}
|
||||
|
||||
StatusListItem {
|
||||
Layout.preferredWidth: root.contentWidth
|
||||
title: qsTr("Import from Keycard to Status Desktop")
|
||||
components: [
|
||||
StatusIcon {
|
||||
icon: "chevron-down"
|
||||
rotation: 270
|
||||
color: Theme.palette.baseColor1
|
||||
}
|
||||
]
|
||||
onClicked: {
|
||||
root.keycardStore.runImportFromKeycardToAppPopup()
|
||||
}
|
||||
}
|
||||
|
||||
StatusSectionHeadline {
|
||||
Layout.preferredWidth: root.contentWidth
|
||||
Layout.leftMargin: Style.current.padding
|
||||
Layout.rightMargin: Style.current.padding
|
||||
text: qsTr("Other")
|
||||
}
|
||||
|
||||
StatusListItem {
|
||||
Layout.preferredWidth: root.contentWidth
|
||||
title: qsTr("Check what’s on a Keycard")
|
||||
components: [
|
||||
StatusIcon {
|
||||
icon: "chevron-down"
|
||||
rotation: 270
|
||||
color: Theme.palette.baseColor1
|
||||
}
|
||||
]
|
||||
onClicked: {
|
||||
root.keycardStore.runDisplayKeycardContentPopup()
|
||||
}
|
||||
}
|
||||
|
||||
StatusListItem {
|
||||
Layout.preferredWidth: root.contentWidth
|
||||
title: qsTr("Factory reset a Keycard")
|
||||
components: [
|
||||
StatusIcon {
|
||||
icon: "chevron-down"
|
||||
rotation: 270
|
||||
color: Theme.palette.baseColor1
|
||||
}
|
||||
]
|
||||
onClicked: {
|
||||
root.keycardStore.runFactoryResetPopup()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,183 @@
|
|||
import QtQuick 2.14
|
||||
import QtQuick.Controls 2.14
|
||||
import QtQuick.Layouts 1.14
|
||||
|
||||
import StatusQ.Core 0.1
|
||||
import StatusQ.Core.Theme 0.1
|
||||
import StatusQ.Components 0.1
|
||||
|
||||
import utils 1.0
|
||||
import shared.status 1.0
|
||||
import shared.popups.keycard.helpers 1.0
|
||||
|
||||
import "../../stores"
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
property KeycardStore keycardStore
|
||||
property string keycardUid: ""
|
||||
|
||||
spacing: Constants.settingsSection.itemSpacing
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
property bool collapsed: true
|
||||
|
||||
function resetKeycardDetails() {
|
||||
let kcItem = root.keycardStore.getKeycardDetailsAsJson(root.keycardUid)
|
||||
keycardDetails.keycardName = kcItem.name
|
||||
keycardDetails.keycardLocked = kcItem.locked
|
||||
keycardDetails.keyPairType = kcItem.pairType
|
||||
keycardDetails.keyPairIcon = kcItem.icon
|
||||
keycardDetails.keyPairImage = kcItem.image
|
||||
keycardDetails.keyPairAccounts = kcItem.accounts
|
||||
}
|
||||
}
|
||||
|
||||
onKeycardUidChanged: {
|
||||
d.resetKeycardDetails()
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: root.keycardStore.keycardModule
|
||||
|
||||
onKeycardProfileChanged: {
|
||||
if (keycardDetails.keyPairType === Constants.keycard.keyPairType.profile) {
|
||||
d.resetKeycardDetails()
|
||||
}
|
||||
}
|
||||
|
||||
onKeycardDetailsChanged: {
|
||||
if (kcUid === root.keycardUid) {
|
||||
d.resetKeycardDetails()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
KeycardItem {
|
||||
id: keycardDetails
|
||||
Layout.fillWidth: true
|
||||
displayChevronComponent: false
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Style.current.halfPadding
|
||||
}
|
||||
|
||||
StatusSectionHeadline {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: Style.current.padding
|
||||
Layout.rightMargin: Style.current.padding
|
||||
text: qsTr("Configure your Keycard")
|
||||
}
|
||||
|
||||
StatusListItem {
|
||||
Layout.fillWidth: true
|
||||
title: qsTr("Rename Keycard")
|
||||
components: [
|
||||
StatusIcon {
|
||||
icon: "tiny/chevron-right"
|
||||
color: Theme.palette.baseColor1
|
||||
}
|
||||
]
|
||||
onClicked: {
|
||||
root.keycardStore.runRenameKeycardPopup()
|
||||
}
|
||||
}
|
||||
|
||||
StatusListItem {
|
||||
Layout.fillWidth: true
|
||||
title: qsTr("Change PIN")
|
||||
components: [
|
||||
StatusIcon {
|
||||
icon: "tiny/chevron-right"
|
||||
color: Theme.palette.baseColor1
|
||||
}
|
||||
]
|
||||
onClicked: {
|
||||
root.keycardStore.runChangePinPopup()
|
||||
}
|
||||
}
|
||||
|
||||
StatusListItem {
|
||||
Layout.fillWidth: true
|
||||
title: qsTr("Create a backup copy of this Keycard")
|
||||
components: [
|
||||
StatusIcon {
|
||||
icon: "tiny/chevron-right"
|
||||
color: Theme.palette.baseColor1
|
||||
}
|
||||
]
|
||||
onClicked: {
|
||||
root.keycardStore.runCreateBackupCopyOfAKeycardPopup()
|
||||
}
|
||||
}
|
||||
|
||||
StatusListItem {
|
||||
visible: keycardDetails.keycardLocked
|
||||
Layout.fillWidth: true
|
||||
title: qsTr("Unlock Keycard")
|
||||
components: [
|
||||
StatusBadge {
|
||||
value: 1 //always set to 1 if keycard is locked
|
||||
border.width: 4
|
||||
border.color: Theme.palette.dangerColor1
|
||||
color: Theme.palette.dangerColor1
|
||||
},
|
||||
StatusIcon {
|
||||
icon: "tiny/chevron-right"
|
||||
color: Theme.palette.baseColor1
|
||||
}
|
||||
]
|
||||
onClicked: {
|
||||
root.keycardStore.runUnlockKeycardPopupForKeycardWithUid(root.keycardUid)
|
||||
}
|
||||
}
|
||||
|
||||
StatusListItem {
|
||||
Layout.fillWidth: true
|
||||
title: qsTr("Advanced")
|
||||
statusListItemTitle.color: Style.current.secondaryText
|
||||
components: [
|
||||
StatusIcon {
|
||||
icon: d.collapsed? "tiny/chevron-down" : "tiny/chevron-up"
|
||||
color: Theme.palette.baseColor1
|
||||
}
|
||||
]
|
||||
onClicked: {
|
||||
d.collapsed = !d.collapsed
|
||||
}
|
||||
}
|
||||
|
||||
StatusListItem {
|
||||
visible: !d.collapsed
|
||||
Layout.fillWidth: true
|
||||
title: qsTr("Create a 12-digit personal unblocking key (PUK)")
|
||||
components: [
|
||||
StatusIcon {
|
||||
icon: "tiny/chevron-right"
|
||||
color: Theme.palette.baseColor1
|
||||
}
|
||||
]
|
||||
onClicked: {
|
||||
root.keycardStore.runCreatePukPopup()
|
||||
}
|
||||
}
|
||||
|
||||
StatusListItem {
|
||||
visible: !d.collapsed
|
||||
Layout.fillWidth: true
|
||||
title: qsTr("Create a new pairing code")
|
||||
components: [
|
||||
StatusIcon {
|
||||
icon: "tiny/chevron-right"
|
||||
color: Theme.palette.baseColor1
|
||||
}
|
||||
]
|
||||
onClicked: {
|
||||
root.keycardStore.runCreateNewPairingCodePopup()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,194 @@
|
|||
import QtQuick 2.14
|
||||
import QtQuick.Controls 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.Components 0.1
|
||||
import StatusQ.Popups 0.1
|
||||
|
||||
import utils 1.0
|
||||
import shared.panels 1.0
|
||||
import shared.controls 1.0
|
||||
import shared.status 1.0
|
||||
import shared.popups.keycard.helpers 1.0
|
||||
|
||||
import "../../stores"
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
property KeycardStore keycardStore
|
||||
|
||||
signal displayKeycardDetails(string keycardUid, string keycardName)
|
||||
|
||||
spacing: Constants.settingsSection.itemSpacing
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
|
||||
readonly property bool noKeycardsSet: root.keycardStore.keycardModule.keycardModel.count === 0
|
||||
}
|
||||
|
||||
Image {
|
||||
visible: d.noKeycardsSet
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
Layout.preferredHeight: sourceSize.height
|
||||
Layout.preferredWidth: sourceSize.width
|
||||
fillMode: Image.PreserveAspectFit
|
||||
antialiasing: true
|
||||
source: Style.png("keycard/security-keycard@2x")
|
||||
mipmap: true
|
||||
}
|
||||
|
||||
Item {
|
||||
visible: d.noKeycardsSet
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Style.current.halfPadding
|
||||
}
|
||||
|
||||
StyledText {
|
||||
visible: d.noKeycardsSet
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
font.pixelSize: Constants.settingsSection.importantInfoFontSize
|
||||
color: Theme.palette.directColor1
|
||||
text: qsTr("Secure your funds. Keep your profile safe.")
|
||||
}
|
||||
|
||||
Item {
|
||||
visible: d.noKeycardsSet
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Style.current.halfPadding
|
||||
}
|
||||
|
||||
StatusSectionHeadline {
|
||||
visible: !d.noKeycardsSet
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: Style.current.padding
|
||||
Layout.rightMargin: Style.current.padding
|
||||
text: qsTr("Your Keycard(s)")
|
||||
}
|
||||
|
||||
ListView {
|
||||
visible: !d.noKeycardsSet
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 200
|
||||
spacing: Style.current.padding
|
||||
model: root.keycardStore.keycardModule.keycardModel
|
||||
|
||||
delegate: KeycardItem {
|
||||
width: ListView.view.width
|
||||
|
||||
keycardName: model.name
|
||||
keycardLocked: model.locked
|
||||
keyPairType: model.pairType
|
||||
keyPairIcon: model.icon
|
||||
keyPairImage: model.image
|
||||
keyPairAccounts: model.accounts
|
||||
|
||||
onKeycardSelected: {
|
||||
root.displayKeycardDetails(model.keycardUid, model.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StatusListItem {
|
||||
Layout.fillWidth: true
|
||||
title: d.noKeycardsSet? qsTr("Setup a new Keycard with an existing account")
|
||||
: qsTr("Migrate an existing account from Status Desktop to Keycard")
|
||||
components: [
|
||||
StatusIcon {
|
||||
icon: "tiny/chevron-right"
|
||||
color: Theme.palette.baseColor1
|
||||
}
|
||||
]
|
||||
onClicked: {
|
||||
root.keycardStore.runSetupKeycardPopup()
|
||||
}
|
||||
}
|
||||
|
||||
StatusSectionHeadline {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: Style.current.padding
|
||||
Layout.rightMargin: Style.current.padding
|
||||
text: qsTr("Create, import or restore a Keycard account")
|
||||
}
|
||||
|
||||
StatusListItem {
|
||||
Layout.fillWidth: true
|
||||
title: qsTr("Generate a seed phrase")
|
||||
components: [
|
||||
StatusIcon {
|
||||
icon: "tiny/chevron-right"
|
||||
color: Theme.palette.baseColor1
|
||||
}
|
||||
]
|
||||
onClicked: {
|
||||
root.keycardStore.runGenerateSeedPhrasePopup()
|
||||
}
|
||||
}
|
||||
|
||||
StatusListItem {
|
||||
Layout.fillWidth: true
|
||||
title: qsTr("Import or restore via a seed phrase")
|
||||
components: [
|
||||
StatusIcon {
|
||||
icon: "tiny/chevron-right"
|
||||
color: Theme.palette.baseColor1
|
||||
}
|
||||
]
|
||||
onClicked: {
|
||||
root.keycardStore.runImportOrRestoreViaSeedPhrasePopup()
|
||||
}
|
||||
}
|
||||
|
||||
StatusListItem {
|
||||
Layout.fillWidth: true
|
||||
title: qsTr("Import from Keycard to Status Desktop")
|
||||
components: [
|
||||
StatusIcon {
|
||||
icon: "tiny/chevron-right"
|
||||
color: Theme.palette.baseColor1
|
||||
}
|
||||
]
|
||||
onClicked: {
|
||||
root.keycardStore.runImportFromKeycardToAppPopup()
|
||||
}
|
||||
}
|
||||
|
||||
StatusSectionHeadline {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: Style.current.padding
|
||||
Layout.rightMargin: Style.current.padding
|
||||
text: qsTr("Other")
|
||||
}
|
||||
|
||||
StatusListItem {
|
||||
Layout.fillWidth: true
|
||||
title: qsTr("Check what’s on a Keycard")
|
||||
components: [
|
||||
StatusIcon {
|
||||
icon: "tiny/chevron-right"
|
||||
color: Theme.palette.baseColor1
|
||||
}
|
||||
]
|
||||
onClicked: {
|
||||
root.keycardStore.runDisplayKeycardContentPopup()
|
||||
}
|
||||
}
|
||||
|
||||
StatusListItem {
|
||||
Layout.fillWidth: true
|
||||
title: qsTr("Factory reset a Keycard")
|
||||
components: [
|
||||
StatusIcon {
|
||||
icon: "tiny/chevron-right"
|
||||
color: Theme.palette.baseColor1
|
||||
}
|
||||
]
|
||||
onClicked: {
|
||||
root.keycardStore.runFactoryResetPopup()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -608,6 +608,7 @@ StatusModal {
|
|||
if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.unlockKeycard) {
|
||||
if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardEmpty ||
|
||||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardAlreadyUnlocked ||
|
||||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongKeycard ||
|
||||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.unlockKeycardSuccess)
|
||||
return qsTr("Done")
|
||||
if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.unlockKeycardOptions)
|
||||
|
|
|
@ -36,6 +36,7 @@ StatusListItem {
|
|||
width: root.keyPairIcon? 24 : 40
|
||||
height: root.keyPairIcon? 24 : 40
|
||||
name: root.keyPairImage? root.keyPairImage : root.keyPairIcon
|
||||
isImage: !!root.keyPairImage
|
||||
color: root.keyPairType === Constants.keycard.keyPairType.profile?
|
||||
Utils.colorForPubkey(d.myPublicKey) :
|
||||
root.keyPairCardLocked? Theme.palette.dangerColor1 : Theme.palette.primaryColor1
|
||||
|
|
|
@ -0,0 +1,116 @@
|
|||
import QtQuick 2.14
|
||||
import QtQml.Models 2.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
|
||||
|
||||
StatusListItem {
|
||||
id: root
|
||||
|
||||
property bool displayChevronComponent: true
|
||||
property string keycardName: ""
|
||||
property bool keycardLocked: false
|
||||
property int keyPairType: Constants.keycard.keyPairType.unknown
|
||||
property string keyPairIcon: ""
|
||||
property string keyPairImage: ""
|
||||
property string keyPairAccounts: ""
|
||||
|
||||
signal keycardSelected()
|
||||
|
||||
color: root.keycardLocked? Theme.palette.dangerColor3 : Style.current.grey
|
||||
title: root.keycardName
|
||||
statusListItemTitleAside.textFormat: Text.RichText
|
||||
statusListItemTitleAside.visible: true
|
||||
statusListItemTitleAside.text: {
|
||||
let t = ""
|
||||
if (root.keyPairType === Constants.keycard.keyPairType.profile) {
|
||||
t = Utils.getElidedCompressedPk(d.myPublicKey)
|
||||
}
|
||||
if (root.keycardLocked) {
|
||||
let label = qsTr("Keycard Locked")
|
||||
t += `<font color="${Theme.palette.dangerColor1}" size="5">${label}</font>`
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
asset {
|
||||
width: root.keyPairIcon? 24 : 40
|
||||
height: root.keyPairIcon? 24 : 40
|
||||
name: root.keyPairImage? root.keyPairImage : root.keyPairIcon
|
||||
isImage: !!root.keyPairImage
|
||||
color: root.keyPairType === Constants.keycard.keyPairType.profile?
|
||||
Utils.colorForPubkey(d.myPublicKey) : Theme.palette.primaryColor1
|
||||
letterSize: Math.max(4, asset.width / 2.4)
|
||||
charactersLen: 2
|
||||
isLetterIdenticon: !root.keyPairIcon && !asset.name.toString()
|
||||
bgColor: root.keycardLocked? Theme.palette.dangerColor2 : Theme.palette.primaryColor3
|
||||
}
|
||||
|
||||
ringSettings {
|
||||
ringSpecModel: root.keyPairType === Constants.keycard.keyPairType.profile?
|
||||
Utils.getColorHashAsJson(d.myPublicKey) : []
|
||||
ringPxSize: Math.max(asset.width / 24.0)
|
||||
}
|
||||
|
||||
tagsModel: ListModel{}
|
||||
|
||||
tagsDelegate: StatusListItemTag {
|
||||
color: model.color
|
||||
height: Style.current.bigPadding
|
||||
radius: 6
|
||||
closeButtonVisible: false
|
||||
asset {
|
||||
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: [
|
||||
StatusIcon {
|
||||
visible: root.displayChevronComponent
|
||||
icon: "tiny/chevron-right"
|
||||
color: Theme.palette.baseColor1
|
||||
}
|
||||
]
|
||||
|
||||
onClicked: {
|
||||
root.keycardSelected()
|
||||
}
|
||||
|
||||
onKeyPairAccountsChanged: {
|
||||
tagsModel.clear()
|
||||
if (root.keyPairAccounts === "") {
|
||||
// should never be here, as it's not possible to have keypair item without at least a single account
|
||||
console.warning("accounts list is empty for selecting keycard pair")
|
||||
return
|
||||
}
|
||||
let obj = JSON.parse(root.keyPairAccounts)
|
||||
if (obj.error) {
|
||||
console.warning("error parsing accounts for selecting keycard pair, error: ", obj.error)
|
||||
return
|
||||
}
|
||||
|
||||
for (var i=0; i<obj.length; i++) {
|
||||
tagsModel.append({"name": obj[i].Field0, "color": obj[i].Field4, "emoji": obj[i].Field3, "icon": obj[i].Field5})
|
||||
}
|
||||
}
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
property string myPublicKey: userProfile.pubKey
|
||||
}
|
||||
}
|
|
@ -1 +1,5 @@
|
|||
KeycardImage 1.0 KeycardImage.qml
|
||||
KeyPairItem 1.0 KeyPairItem.qml
|
||||
KeyPairUnknownItem 1.0 KeyPairUnknownItem.qml
|
||||
KeycardItem 1.0 KeycardItem.qml
|
||||
KeyPairList 1.0 KeyPairList.qml
|
||||
|
|
|
@ -425,7 +425,15 @@ Item {
|
|||
}
|
||||
PropertyChanges {
|
||||
target: message
|
||||
text: qsTr("Keycard inserted does not match the Keycard below")
|
||||
text: {
|
||||
if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.authentication) {
|
||||
return qsTr("Keycard inserted does not match the Keycard below")
|
||||
}
|
||||
if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.unlockKeycard) {
|
||||
return qsTr("Keycard inserted does not match the Keycard you're trying to unlock")
|
||||
}
|
||||
return ""
|
||||
}
|
||||
font.pixelSize: Constants.keycard.general.fontSize2
|
||||
color: Theme.palette.dangerColor1
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue