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:
Sale Djenic 2022-10-11 14:15:33 +02:00 committed by saledjenic
parent 86a2d963ad
commit c1f4874e18
25 changed files with 1074 additions and 176 deletions

View File

@ -5,6 +5,8 @@ import io_interface
import ../../../../core/eventemitter import ../../../../core/eventemitter
import ../../../shared_modules/keycard_popup/io_interface as keycard_shared_module 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: logScope:
topics = "profile-section-keycard-module-controller" topics = "profile-section-keycard-module-controller"
@ -15,13 +17,16 @@ type
Controller* = ref object of RootObj Controller* = ref object of RootObj
delegate: io_interface.AccessInterface delegate: io_interface.AccessInterface
events: EventEmitter events: EventEmitter
walletAccountService: wallet_account_service.Service
proc newController*(delegate: io_interface.AccessInterface, proc newController*(delegate: io_interface.AccessInterface,
events: EventEmitter): events: EventEmitter,
walletAccountService: wallet_account_service.Service):
Controller = Controller =
result = Controller() result = Controller()
result.delegate = delegate result.delegate = delegate
result.events = events result.events = events
result.walletAccountService = walletAccountService
proc delete*(self: Controller) = proc delete*(self: Controller) =
discard discard
@ -38,3 +43,24 @@ proc init*(self: Controller) =
if args.uniqueIdentifier != UNIQUE_SETTING_KEYCARD_MODULE_IDENTIFIER: if args.uniqueIdentifier != UNIQUE_SETTING_KEYCARD_MODULE_IDENTIFIER:
return return
self.delegate.onDisplayKeycardSharedModuleFlow() 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()

View File

@ -37,7 +37,7 @@ method runImportOrRestoreViaSeedPhrasePopup*(self: AccessInterface) {.base.} =
method runImportFromKeycardToAppPopup*(self: AccessInterface) {.base.} = method runImportFromKeycardToAppPopup*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
method runUnlockKeycardPopup*(self: AccessInterface) {.base.} = method runUnlockKeycardPopupForKeycardWithUid*(self: AccessInterface, keycardUid: string) {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
method runDisplayKeycardContentPopup*(self: AccessInterface) {.base.} = method runDisplayKeycardContentPopup*(self: AccessInterface) {.base.} =
@ -46,6 +46,36 @@ method runDisplayKeycardContentPopup*(self: AccessInterface) {.base.} =
method runFactoryResetPopup*(self: AccessInterface) {.base.} = method runFactoryResetPopup*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available") 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 # View Delegate Interface
# Delegate for the view must be declared here due to use of QtObject and multi # Delegate for the view must be declared here due to use of QtObject and multi

View File

@ -1,8 +1,9 @@
import NimQml, chronicles import NimQml, chronicles, json, marshal
import ./io_interface, ./view, ./controller import ./io_interface, ./view, ./controller
import ../io_interface as delegate_interface import ../io_interface as delegate_interface
import ../../../../global/global_singleton
import ../../../../core/eventemitter import ../../../../core/eventemitter
import ../../../../../app_service/service/keycard/service as keycard_service import ../../../../../app_service/service/keycard/service as keycard_service
@ -13,6 +14,7 @@ import ../../../../../app_service/service/wallet_account/service as wallet_accou
import ../../../../../app_service/service/keychain/service as keychain_service import ../../../../../app_service/service/keychain/service as keychain_service
import ../../../shared_modules/keycard_popup/module as keycard_shared_module import ../../../shared_modules/keycard_popup/module as keycard_shared_module
import ../../../shared_modules/keycard_popup/models/keycard_model
export io_interface export io_interface
@ -35,6 +37,9 @@ type
keychainService: keychain_service.Service keychainService: keychain_service.Service
keycardSharedModule: keycard_shared_module.AccessInterface keycardSharedModule: keycard_shared_module.AccessInterface
## Forward declarations
proc buildKeycardList(self: Module)
proc newModule*(delegate: delegate_interface.AccessInterface, proc newModule*(delegate: delegate_interface.AccessInterface,
events: EventEmitter, events: EventEmitter,
keycardService: keycard_service.Service, keycardService: keycard_service.Service,
@ -54,7 +59,7 @@ proc newModule*(delegate: delegate_interface.AccessInterface,
result.keychainService = keychainService result.keychainService = keychainService
result.view = view.newView(result) result.view = view.newView(result)
result.viewVariant = newQVariant(result.view) result.viewVariant = newQVariant(result.view)
result.controller = controller.newController(result, events) result.controller = controller.newController(result, events, walletAccountService)
result.moduleLoaded = false result.moduleLoaded = false
method delete*(self: Module) = method delete*(self: Module) =
@ -67,6 +72,7 @@ method delete*(self: Module) =
method load*(self: Module) = method load*(self: Module) =
self.controller.init() self.controller.init()
self.view.load() self.view.load()
self.buildKeycardList()
method isLoaded*(self: Module): bool = method isLoaded*(self: Module): bool =
return self.moduleLoaded return self.moduleLoaded
@ -113,10 +119,11 @@ method runImportOrRestoreViaSeedPhrasePopup*(self: Module) =
method runImportFromKeycardToAppPopup*(self: Module) = method runImportFromKeycardToAppPopup*(self: Module) =
info "TODO: Import from Keycard to Status Desktop..." info "TODO: Import from Keycard to Status Desktop..."
method runUnlockKeycardPopup*(self: Module) = method runUnlockKeycardPopupForKeycardWithUid*(self: Module, keycardUid: string) =
self.createSharedKeycardModule() self.createSharedKeycardModule()
if self.keycardSharedModule.isNil: if self.keycardSharedModule.isNil:
return return
self.keycardSharedModule.setUidOfAKeycardWhichNeedToBeUnlocked(keycardUid)
self.keycardSharedModule.runFlow(keycard_shared_module.FlowType.UnlockKeycard) self.keycardSharedModule.runFlow(keycard_shared_module.FlowType.UnlockKeycard)
method runDisplayKeycardContentPopup*(self: Module) = method runDisplayKeycardContentPopup*(self: Module) =
@ -130,3 +137,92 @@ method runFactoryResetPopup*(self: Module) =
if self.keycardSharedModule.isNil: if self.keycardSharedModule.isNil:
return 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

View File

@ -1,19 +1,31 @@
import NimQml import NimQml
import ../../../shared_modules/keycard_popup/models/keycard_model
import ./io_interface import ./io_interface
QtObject: QtObject:
type type
View* = ref object of QObject View* = ref object of QObject
delegate: io_interface.AccessInterface delegate: io_interface.AccessInterface
keycardModel: KeycardModel
keycardModelVariant: QVariant
proc delete*(self: View) = proc delete*(self: View) =
self.QObject.delete 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 = proc newView*(delegate: io_interface.AccessInterface): View =
new(result, delete) new(result, delete)
result.QObject.setup result.QObject.setup
result.delegate = delegate result.delegate = delegate
if result.keycardModel.isNil:
result.keycardModel = newKeycardModel()
if result.keycardModelVariant.isNil:
result.keycardModelVariant = newQVariant(result.keycardModel)
proc load*(self: View) = proc load*(self: View) =
self.delegate.viewDidLoad() self.delegate.viewDidLoad()
@ -43,11 +55,57 @@ QtObject:
proc runImportFromKeycardToAppPopup*(self: View) {.slot.} = proc runImportFromKeycardToAppPopup*(self: View) {.slot.} =
self.delegate.runImportFromKeycardToAppPopup() self.delegate.runImportFromKeycardToAppPopup()
proc runUnlockKeycardPopup*(self: View) {.slot.} = proc runUnlockKeycardPopupForKeycardWithUid*(self: View, keycardUid: string) {.slot.} =
self.delegate.runUnlockKeycardPopup() self.delegate.runUnlockKeycardPopupForKeycardWithUid(keycardUid)
proc runDisplayKeycardContentPopup*(self: View) {.slot.} = proc runDisplayKeycardContentPopup*(self: View) {.slot.} =
self.delegate.runDisplayKeycardContentPopup() self.delegate.runDisplayKeycardContentPopup()
proc runFactoryResetPopup*(self: View) {.slot.} = 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)

View File

@ -31,6 +31,7 @@ type
connectionKeycardResponse: UUID connectionKeycardResponse: UUID
tmpKeycardContainsMetadata: bool tmpKeycardContainsMetadata: bool
tmpCardMetadata: CardMetadata tmpCardMetadata: CardMetadata
tmpKeycardUidForUnlocking: string
tmpPin: string tmpPin: string
tmpPinMatch: bool tmpPinMatch: bool
tmpPuk: string tmpPuk: string
@ -141,6 +142,12 @@ proc containsMetadata*(self: Controller): bool =
proc setContainsMetadata*(self: Controller, value: bool) = proc setContainsMetadata*(self: Controller, value: bool) =
self.tmpKeycardContainsMetadata = value 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) = proc setPin*(self: Controller, value: string) =
self.tmpPin = value self.tmpPin = value

View File

@ -26,7 +26,7 @@ proc doMigration(self: MigratingKeyPairState, controller: Controller) =
self.migrationSuccess = self.migrationSuccess and controller.convertSelectedKeyPairToKeycardAccount(password) self.migrationSuccess = self.migrationSuccess and controller.convertSelectedKeyPairToKeycardAccount(password)
if not self.migrationSuccess: if not self.migrationSuccess:
return return
controller.runStoreMetadataFlow(selectedKeyPairDto.keypairName, controller.getPin(), controller.runStoreMetadataFlow(selectedKeyPairDto.keycardName, controller.getPin(),
controller.getSelectedKeyPairWalletPaths()) controller.getSelectedKeyPairWalletPaths())
method executePrimaryCommand*(self: MigratingKeyPairState, controller: Controller) = method executePrimaryCommand*(self: MigratingKeyPairState, controller: Controller) =

View File

@ -24,9 +24,15 @@ method executeTertiaryCommand*(self: ReadingKeycardState, controller: Controller
method getNextSecondaryState*(self: ReadingKeycardState, controller: Controller): State = method getNextSecondaryState*(self: ReadingKeycardState, controller: Controller): State =
let (flowType, flowEvent) = controller.getLastReceivedKeycardData() 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) # 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, method resolveKeycardNextState*(self: ReadingKeycardState, keycardFlowType: string, keycardEvent: KeycardEvent,
controller: Controller): State = 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 # this is used in case a keycard is inserted and we jump to the first meaningful screen
return ensureReaderAndCardPresenceAndResolveNextState(self, keycardFlowType, keycardEvent, controller) return ensureReaderAndCardPresenceAndResolveNextState(self, keycardFlowType, keycardEvent, controller)

View File

@ -8,6 +8,11 @@ proc newWrongKeycardState*(flowType: FlowType, backState: State): WrongKeycardSt
proc delete*(self: WrongKeycardState) = proc delete*(self: WrongKeycardState) =
self.State.delete self.State.delete
method executePrimaryCommand*(self: WrongKeycardState, controller: Controller) =
if self.flowType == FlowType.UnlockKeycard:
controller.terminateCurrentFlow(lastStepInTheCurrentFlow = true)
method executeTertiaryCommand*(self: WrongKeycardState, controller: Controller) = method executeTertiaryCommand*(self: WrongKeycardState, controller: Controller) =
if self.flowType == FlowType.Authentication: if self.flowType == FlowType.Authentication or
self.flowType == FlowType.UnlockKeycard:
controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false) controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false)

View File

@ -91,6 +91,9 @@ method onKeycardResponse*(self: AccessInterface, keycardFlowType: string, keycar
method runFlow*(self: AccessInterface, flowToRun: FlowType, keyUid = "", bip44Path = "", txHash = "") {.base.} = method runFlow*(self: AccessInterface, flowToRun: FlowType, keyUid = "", bip44Path = "", txHash = "") {.base.} =
raise newException(ValueError, "No implementation available") 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.} = method setPin*(self: AccessInterface, value: string) {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")

View File

@ -29,6 +29,25 @@ type
pairType: KeyPairType pairType: KeyPairType
accounts: seq[WalletAccountDetails] 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*( proc initKeyPairItem*(
pubKey = "", pubKey = "",
keyUid = "", keyUid = "",
@ -40,19 +59,14 @@ proc initKeyPairItem*(
derivedFrom = "" derivedFrom = ""
): KeyPairItem = ): KeyPairItem =
result = KeyPairItem() result = KeyPairItem()
result.pubKey = pubKey result.setup(pubKey, keyUid, locked, name, image, icon, pairType, derivedFrom)
result.keyUid = keyUid
result.name = name
result.image = image
result.icon = icon
result.pairType = pairType
result.derivedFrom = derivedFrom
proc `$`*(self: KeyPairItem): string = proc `$`*(self: KeyPairItem): string =
result = fmt"""KeyPairItem[ result = fmt"""KeyPairItem[
pubKey: {self.pubkey}, pubKey: {self.pubkey},
keyUid: {self.keyUid}, keyUid: {self.keyUid},
name: {self.name}, name: {self.name},
locked: {self.locked},
image: {self.image}, image: {self.image},
icon: {self.icon}, icon: {self.icon},
pairType: {$self.pairType}, pairType: {$self.pairType},

View File

@ -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

View File

@ -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])

View File

@ -66,6 +66,9 @@ method getKeycardData*[T](self: Module[T]): string =
method setKeycardData*[T](self: Module[T], value: string) = method setKeycardData*[T](self: Module[T], value: string) =
self.view.setKeycardData(value) self.view.setKeycardData(value)
method setUidOfAKeycardWhichNeedToBeUnlocked*[T](self: Module[T], value: string) =
self.controller.setUidOfAKeycardWhichNeedToBeUnlocked(value)
method setPin*[T](self: Module[T], value: string) = method setPin*[T](self: Module[T], value: string) =
self.controller.setPin(value) 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) = method setSelectedKeyPair*[T](self: Module[T], item: KeyPairItem) =
var paths: seq[string] var paths: seq[string]
var keyPairDto = KeyPairDto(keycardUid: "", # will be set during migration var keyPairDto = KeyPairDto(keycardUid: "", # will be set during migration
keypairName: item.name, keycardName: item.name,
keycardLocked: item.locked, keycardLocked: item.locked,
keyUid: item.keyUid) keyUid: item.keyUid)
for a in item.accountsAsArr(): for a in item.accountsAsArr():

View File

@ -4,7 +4,7 @@ include ../../common/json_utils
type KeyPairDto* = object type KeyPairDto* = object
keycardUid*: string keycardUid*: string
keypairName*: string keycardName*: string
keycardLocked*: bool keycardLocked*: bool
accountsAddresses*: seq[string] accountsAddresses*: seq[string]
keyUid*: string keyUid*: string
@ -12,7 +12,7 @@ type KeyPairDto* = object
proc toKeyPairDto*(jsonObj: JsonNode): KeyPairDto = proc toKeyPairDto*(jsonObj: JsonNode): KeyPairDto =
result = KeyPairDto() result = KeyPairDto()
discard jsonObj.getProp("keycard-uid", result.keycardUid) 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("keycard-locked", result.keycardLocked)
discard jsonObj.getProp("key-uid", result.keyUid) discard jsonObj.getProp("key-uid", result.keyUid)

View File

@ -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_DERIVED_ADDRESS_READY* = "walletAccount/derivedAddressesReady"
const SIGNAL_WALLET_ACCOUNT_TOKENS_REBUILT* = "walletAccount/tokensRebuilt" 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 var
balanceCache {.threadvar.}: Table[string, float64] balanceCache {.threadvar.}: Table[string, float64]
@ -81,6 +85,10 @@ type DerivedAddressesArgs* = ref object of Args
type TokensPerAccountArgs* = ref object of Args type TokensPerAccountArgs* = ref object of Args
accountsTokens*: OrderedTable[string, seq[WalletTokenDto]] # [wallet address, list of tokens] 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 CheckBalanceSlotExecuteIntervalInSeconds = 15 * 60 # 15 mins
const CheckBalanceTimerIntervalInMilliseconds = 5000 # 5 sec const CheckBalanceTimerIntervalInMilliseconds = 5000 # 5 sec
@ -487,7 +495,7 @@ QtObject:
try: try:
let response = backend.addMigratedKeyPair( let response = backend.addMigratedKeyPair(
keyPair.keycardUid, keyPair.keycardUid,
keyPair.keyPairName, keyPair.keycardName,
keyPair.keyUid, keyPair.keyUid,
keyPair.accountsAddresses) keyPair.accountsAddresses)
return self.responseHasNoErrors("addMigratedKeyPair", response) return self.responseHasNoErrors("addMigratedKeyPair", response)
@ -522,7 +530,9 @@ QtObject:
proc setKeycardLocked*(self: Service, keycardUid: string): bool = proc setKeycardLocked*(self: Service, keycardUid: string): bool =
try: try:
let response = backend.keycardLocked(keycardUid) 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: except Exception as e:
error "error: ", procName="setKeycardLocked", errName = e.name, errDesription = e.msg error "error: ", procName="setKeycardLocked", errName = e.name, errDesription = e.msg
return false return false
@ -530,7 +540,9 @@ QtObject:
proc setKeycardUnlocked*(self: Service, keycardUid: string): bool = proc setKeycardUnlocked*(self: Service, keycardUid: string): bool =
try: try:
let response = backend.keycardUnlocked(keycardUid) 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: except Exception as e:
error "error: ", procName="setKeycardUnlocked", errName = e.name, errDesription = e.msg error "error: ", procName="setKeycardUnlocked", errName = e.name, errDesription = e.msg
return false return false
@ -538,7 +550,9 @@ QtObject:
proc updateKeycardUid*(self: Service, oldKeycardUid: string, newKeycardUid: string): bool = proc updateKeycardUid*(self: Service, oldKeycardUid: string, newKeycardUid: string): bool =
try: try:
let response = backend.updateKeycardUID(oldKeycardUid, newKeycardUid) 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: except Exception as e:
error "error: ", procName="updateKeycardUid", errName = e.name, errDesription = e.msg error "error: ", procName="updateKeycardUid", errName = e.name, errDesription = e.msg
return false return false

View File

@ -32,6 +32,9 @@ StatusSectionLayout {
case 4: case 4:
walletView.resetStack(); walletView.resetStack();
break; break;
case Constants.settingsSubsection.keycard:
keycardView.handleBackAction();
break;
} }
} }
@ -87,7 +90,11 @@ StatusSectionLayout {
} }
if (currentIndex === 1) { if (currentIndex === 1) {
root.store.backButtonName = root.store.getNameForSubsection(Constants.settingsSubsection.messaging); root.store.backButtonName = root.store.getNameForSubsection(Constants.settingsSubsection.messaging);
} else { }
else if (currentIndex === Constants.settingsSubsection.keycard) {
keycardView.handleBackAction();
}
else {
root.store.backButtonName = ""; root.store.backButtonName = "";
} }
} }
@ -226,11 +233,14 @@ StatusSectionLayout {
} }
KeycardView { KeycardView {
id: keycardView
implicitWidth: parent.width implicitWidth: parent.width
implicitHeight: parent.height implicitHeight: parent.height
profileSectionStore: root.store
keycardStore: root.store.keycardStore keycardStore: root.store.keycardStore
sectionTitle: root.store.getNameForSubsection(Constants.settingsSubsection.keycard) sectionTitle: root.store.getNameForSubsection(Constants.settingsSubsection.keycard)
mainSectionTitle: root.store.getNameForSubsection(Constants.settingsSubsection.keycard)
contentWidth: d.contentWidth contentWidth: d.contentWidth
} }
} }

View File

@ -22,8 +22,8 @@ QtObject {
root.keycardModule.runImportFromKeycardToAppPopup() root.keycardModule.runImportFromKeycardToAppPopup()
} }
function runUnlockKeycardPopup() { function runUnlockKeycardPopupForKeycardWithUid(keycardUid) {
root.keycardModule.runUnlockKeycardPopup() root.keycardModule.runUnlockKeycardPopupForKeycardWithUid(keycardUid)
} }
function runDisplayKeycardContentPopup() { function runDisplayKeycardContentPopup() {
@ -33,4 +33,47 @@ QtObject {
function runFactoryResetPopup() { function runFactoryResetPopup() {
root.keycardModule.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: [],
}
}
}
} }

View File

@ -1,25 +1,21 @@
import QtQuick 2.13 import QtQuick 2.14
import QtQuick.Controls 2.13 import QtQuick.Controls 2.14
import QtQuick.Layouts 1.13 import QtQuick.Layouts 1.14
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Controls 0.1 import StatusQ.Controls 0.1
import StatusQ.Components 0.1
import StatusQ.Popups 0.1
import utils 1.0 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 shared.popups.keycard 1.0
import "../stores" import "../stores"
import "./keycard"
SettingsContentBase { SettingsContentBase {
id: root id: root
property ProfileSectionStore profileSectionStore
property KeycardStore keycardStore property KeycardStore keycardStore
property string mainSectionTitle: ""
titleRowComponentLoader.sourceComponent: StatusButton { titleRowComponentLoader.sourceComponent: StatusButton {
text: qsTr("Get Keycard") text: qsTr("Get Keycard")
@ -28,9 +24,44 @@ SettingsContentBase {
} }
} }
ColumnLayout { function handleBackAction() {
id: contentColumn if (stackLayout.currentIndex === d.detailsViewIndex) {
spacing: Constants.settingsSection.itemSpacing 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 { Connections {
target: root.keycardStore.keycardModule target: root.keycardStore.keycardModule
@ -41,6 +72,12 @@ SettingsContentBase {
onDestroyKeycardSharedModuleFlow: { onDestroyKeycardSharedModuleFlow: {
keycardPopup.active = false keycardPopup.active = false
} }
onKeycardUidChanged: {
if (d.observedKeycardUid === oldKcUid) {
d.observedKeycardUid = newKcUid
}
}
} }
Loader { Loader {
@ -54,136 +91,5 @@ SettingsContentBase {
keycardPopup.item.open() 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 whats 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()
}
}
} }
} }

View File

@ -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()
}
}
}

View File

@ -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 whats 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()
}
}
}

View File

@ -608,6 +608,7 @@ StatusModal {
if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.unlockKeycard) { if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.unlockKeycard) {
if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardEmpty || if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardEmpty ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardAlreadyUnlocked || root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardAlreadyUnlocked ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongKeycard ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.unlockKeycardSuccess) root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.unlockKeycardSuccess)
return qsTr("Done") return qsTr("Done")
if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.unlockKeycardOptions) if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.unlockKeycardOptions)

View File

@ -36,6 +36,7 @@ StatusListItem {
width: root.keyPairIcon? 24 : 40 width: root.keyPairIcon? 24 : 40
height: root.keyPairIcon? 24 : 40 height: root.keyPairIcon? 24 : 40
name: root.keyPairImage? root.keyPairImage : root.keyPairIcon name: root.keyPairImage? root.keyPairImage : root.keyPairIcon
isImage: !!root.keyPairImage
color: root.keyPairType === Constants.keycard.keyPairType.profile? color: root.keyPairType === Constants.keycard.keyPairType.profile?
Utils.colorForPubkey(d.myPublicKey) : Utils.colorForPubkey(d.myPublicKey) :
root.keyPairCardLocked? Theme.palette.dangerColor1 : Theme.palette.primaryColor1 root.keyPairCardLocked? Theme.palette.dangerColor1 : Theme.palette.primaryColor1

View File

@ -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
}
}

View File

@ -1 +1,5 @@
KeycardImage 1.0 KeycardImage.qml 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

View File

@ -425,7 +425,15 @@ Item {
} }
PropertyChanges { PropertyChanges {
target: message 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 font.pixelSize: Constants.keycard.general.fontSize2
color: Theme.palette.dangerColor1 color: Theme.palette.dangerColor1
} }