feat(@desktop/syncing): make a not operable account fully operable, part 2
- handles import keypairs (without syncing via qr) Closes the second part of #11779
This commit is contained in:
parent
7d4df690c5
commit
4c6af4f1ad
|
@ -5,6 +5,7 @@ import app/core/eventemitter
|
|||
import app_service/service/accounts/service as accounts_service
|
||||
import app_service/service/wallet_account/service as wallet_account_service
|
||||
|
||||
import app/modules/shared_models/[keypair_item]
|
||||
import app/modules/shared_modules/keycard_popup/io_interface as keycard_shared_module
|
||||
|
||||
logScope:
|
||||
|
@ -93,4 +94,10 @@ proc makePrivateKeyKeypairFullyOperable*(self: Controller, keyUid, privateKey, p
|
|||
return self.walletAccountService.makePrivateKeyKeypairFullyOperable(keyUid, privateKey, password, doPasswordHashing)
|
||||
|
||||
proc makeSeedPhraseKeypairFullyOperable*(self: Controller, keyUid, mnemonic, password: string, doPasswordHashing: bool): string =
|
||||
return self.walletAccountService.makeSeedPhraseKeypairFullyOperable(keyUid, mnemonic, password, doPasswordHashing)
|
||||
return self.walletAccountService.makeSeedPhraseKeypairFullyOperable(keyUid, mnemonic, password, doPasswordHashing)
|
||||
|
||||
proc getKeypairs*(self: Controller): seq[wallet_account_service.KeypairDto] =
|
||||
return self.walletAccountService.getKeypairs()
|
||||
|
||||
proc getSelectedKeypair*(self: Controller): KeyPairItem =
|
||||
return self.delegate.getSelectedKeypair()
|
|
@ -0,0 +1,12 @@
|
|||
type
|
||||
ScanQrState* = ref object of State
|
||||
|
||||
proc newScanQrState*(backState: State): ScanQrState =
|
||||
result = ScanQrState()
|
||||
result.setup(StateType.ScanQr, backState)
|
||||
|
||||
proc delete*(self: ScanQrState) =
|
||||
self.State.delete
|
||||
|
||||
method getNextPrimaryState*(self: ScanQrState, controller: Controller): State =
|
||||
discard
|
|
@ -0,0 +1,20 @@
|
|||
type
|
||||
SelectImportMethodState* = ref object of State
|
||||
|
||||
proc newSelectImportMethodState*(backState: State): SelectImportMethodState =
|
||||
result = SelectImportMethodState()
|
||||
result.setup(StateType.SelectImportMethod, backState)
|
||||
|
||||
proc delete*(self: SelectImportMethodState) =
|
||||
self.State.delete
|
||||
|
||||
method getNextPrimaryState*(self: SelectImportMethodState, controller: Controller): State =
|
||||
return createState(StateType.ScanQr, self)
|
||||
|
||||
method getNextSecondaryState*(self: SelectImportMethodState, controller: Controller): State =
|
||||
let kp = controller.getSelectedKeypair()
|
||||
if kp.getPairType() == KeyPairType.SeedImport.int:
|
||||
return createState(StateType.ImportSeedPhrase, self)
|
||||
if kp.getPairType() == KeyPairType.PrivateKeyImport.int:
|
||||
return createState(StateType.ImportPrivateKey, self)
|
||||
error "ki_unsupported keypair type"
|
|
@ -0,0 +1,12 @@
|
|||
type
|
||||
SelectKeypairState* = ref object of State
|
||||
|
||||
proc newSelectKeypairState*(backState: State): SelectKeypairState =
|
||||
result = SelectKeypairState()
|
||||
result.setup(StateType.SelectKeypair, backState)
|
||||
|
||||
proc delete*(self: SelectKeypairState) =
|
||||
self.State.delete
|
||||
|
||||
method getNextPrimaryState*(self: SelectKeypairState, controller: Controller): State =
|
||||
return createState(StateType.SelectImportMethod, self)
|
|
@ -2,7 +2,9 @@ import ../controller
|
|||
|
||||
type StateType* {.pure.} = enum
|
||||
NoState = "NoState"
|
||||
Main = "Main"
|
||||
SelectKeypair = "SelectKeypair"
|
||||
SelectImportMethod = "SelectImportMethod"
|
||||
ScanQr = "ScanQr"
|
||||
ImportSeedPhrase = "ImportSeedPhrase"
|
||||
ImportPrivateKey = "ImportPrivateKey"
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import chronicles
|
||||
import ../controller
|
||||
import app/modules/shared_models/[keypair_item]
|
||||
|
||||
import state
|
||||
|
||||
|
@ -9,10 +10,19 @@ logScope:
|
|||
# Forward declaration
|
||||
proc createState*(stateToBeCreated: StateType, backState: State): State
|
||||
|
||||
include select_keypair_state
|
||||
include select_import_method_state
|
||||
include scan_qr_state
|
||||
include import_private_key_state
|
||||
include import_seed_phrase_state
|
||||
|
||||
proc createState*(stateToBeCreated: StateType, backState: State): State =
|
||||
if stateToBeCreated == StateType.SelectKeypair:
|
||||
return newSelectKeypairState(backState)
|
||||
if stateToBeCreated == StateType.SelectImportMethod:
|
||||
return newSelectImportMethodState(backState)
|
||||
if stateToBeCreated == StateType.ScanQr:
|
||||
return newScanQrState(backState)
|
||||
if stateToBeCreated == StateType.ImportPrivateKey:
|
||||
return newImportPrivateKeyState(backState)
|
||||
if stateToBeCreated == StateType.ImportSeedPhrase:
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
import Tables, NimQml
|
||||
|
||||
import app/modules/shared_models/[keypair_item]
|
||||
import app_service/service/wallet_account/dto/derived_address_dto
|
||||
|
||||
type ImportOption* {.pure.}= enum
|
||||
SeedPhrase = 1,
|
||||
PrivateKey = 2
|
||||
SelectKeypair = 1
|
||||
SeedPhrase
|
||||
PrivateKey
|
||||
QrCode
|
||||
|
||||
type
|
||||
AccessInterface* {.pure inheritable.} = ref object of RootObj
|
||||
|
@ -27,6 +30,9 @@ method onBackActionClicked*(self: AccessInterface) {.base.} =
|
|||
method onPrimaryActionClicked*(self: AccessInterface) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method onSecondaryActionClicked*(self: AccessInterface) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method onCancelActionClicked*(self: AccessInterface) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
|
@ -45,6 +51,12 @@ method validSeedPhrase*(self: AccessInterface, seedPhrase: string): bool {.base.
|
|||
method onAddressDetailsFetched*(self: AccessInterface, derivedAddresses: seq[DerivedAddressDto], error: string) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method getSelectedKeypair*(self: AccessInterface): KeyPairItem {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method setSelectedKeyPairByKeyUid*(self: AccessInterface, keyUid: string) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
type
|
||||
DelegateInterface* = concept c
|
||||
c.onKeypairImportModuleLoaded()
|
||||
|
|
|
@ -7,7 +7,7 @@ import internal/[state, state_factory]
|
|||
import app/global/global_singleton
|
||||
import app/core/eventemitter
|
||||
import app/modules/shared/keypairs
|
||||
import app/modules/shared_models/[derived_address_model]
|
||||
import app/modules/shared_models/[keypair_model, derived_address_model]
|
||||
import app_service/service/accounts/service as accounts_service
|
||||
import app_service/service/wallet_account/service as wallet_account_service
|
||||
|
||||
|
@ -48,21 +48,27 @@ method getModuleAsVariant*[T](self: Module[T]): QVariant =
|
|||
|
||||
method load*[T](self: Module[T], keyUid: string, importOption: ImportOption) =
|
||||
self.controller.init()
|
||||
let keypair = self.controller.getKeypairByKeyUid(keyUid)
|
||||
if keypair.isNil:
|
||||
error "trying to import an unknown keypair"
|
||||
self.closeKeypairImportPopup()
|
||||
return
|
||||
let keypairItem = buildKeypairItem(keypair, areTestNetworksEnabled = false) # testnetworks are irrelevant in this context
|
||||
if keypairItem.isNil:
|
||||
error "cannot generate keypair item for provided keypair"
|
||||
self.closeKeypairImportPopup()
|
||||
return
|
||||
self.view.setSelectedKeypair(keypairItem)
|
||||
if importOption == ImportOption.PrivateKey:
|
||||
self.view.setCurrentState(newImportPrivateKeyState(nil))
|
||||
elif importOption == ImportOption.SeedPhrase:
|
||||
self.view.setCurrentState(newImportSeedPhraseState(nil))
|
||||
if importOption == ImportOption.SelectKeypair:
|
||||
let items = keypairs.buildKeyPairsList(self.controller.getKeypairs(), excludeAlreadyMigratedPairs = true,
|
||||
excludePrivateKeyKeypairs = false)
|
||||
self.view.createKeypairModel(items)
|
||||
self.view.setCurrentState(newSelectKeypairState(nil))
|
||||
else:
|
||||
let keypair = self.controller.getKeypairByKeyUid(keyUid)
|
||||
if keypair.isNil:
|
||||
error "ki_trying to import an unknown keypair"
|
||||
self.closeKeypairImportPopup()
|
||||
return
|
||||
let keypairItem = buildKeypairItem(keypair, areTestNetworksEnabled = false) # testnetworks are irrelevant in this context
|
||||
if keypairItem.isNil:
|
||||
error "ki_cannot generate keypair item for provided keypair"
|
||||
self.closeKeypairImportPopup()
|
||||
return
|
||||
self.view.setSelectedKeypairItem(keypairItem)
|
||||
if importOption == ImportOption.PrivateKey:
|
||||
self.view.setCurrentState(newImportPrivateKeyState(nil))
|
||||
elif importOption == ImportOption.SeedPhrase:
|
||||
self.view.setCurrentState(newImportSeedPhraseState(nil))
|
||||
self.delegate.onKeypairImportModuleLoaded()
|
||||
|
||||
method onBackActionClicked*[T](self: Module[T]) =
|
||||
|
@ -99,16 +105,40 @@ method onPrimaryActionClicked*[T](self: Module[T]) =
|
|||
self.view.setCurrentState(nextState)
|
||||
debug "ki_primary_action - set state", setCurrState=nextState.stateType()
|
||||
|
||||
method onSecondaryActionClicked*[T](self: Module[T]) =
|
||||
let currStateObj = self.view.currentStateObj()
|
||||
if currStateObj.isNil:
|
||||
error "ki_cannot resolve current state"
|
||||
return
|
||||
debug "ki_secondary_action", currState=currStateObj.stateType()
|
||||
currStateObj.executePreSecondaryStateCommand(self.controller)
|
||||
let nextState = currStateObj.getNextSecondaryState(self.controller)
|
||||
if nextState.isNil:
|
||||
return
|
||||
self.view.setCurrentState(nextState)
|
||||
debug "ki_secondary_action - set state", setCurrState=nextState.stateType()
|
||||
|
||||
proc authenticateLoggedInUser[T](self: Module[T]) =
|
||||
self.controller.authenticateLoggedInUser()
|
||||
|
||||
method getSelectedKeypair*[T](self: Module[T]): KeyPairItem =
|
||||
return self.view.getSelectedKeypair()
|
||||
|
||||
method setSelectedKeyPairByKeyUid*[T](self: Module[T], keyUid: string) =
|
||||
let item = self.view.keypairModel().findItemByKeyUid(keyUid)
|
||||
if item.isNil:
|
||||
error "ki_cannot generate keypair item for provided keypair"
|
||||
self.closeKeypairImportPopup()
|
||||
return
|
||||
self.view.setSelectedKeypairItem(item)
|
||||
|
||||
method changePrivateKey*[T](self: Module[T], privateKey: string) =
|
||||
self.view.setPrivateKeyAccAddress(newDerivedAddressItem())
|
||||
if privateKey.len == 0:
|
||||
return
|
||||
let genAccDto = self.controller.createAccountFromPrivateKey(privateKey)
|
||||
if genAccDto.address.len == 0:
|
||||
error "unable to resolve an address from the provided private key"
|
||||
error "ki_unable to resolve an address from the provided private key"
|
||||
return
|
||||
let kp = self.view.getSelectedKeypair()
|
||||
if kp.isNil:
|
||||
|
@ -116,7 +146,7 @@ method changePrivateKey*[T](self: Module[T], privateKey: string) =
|
|||
return
|
||||
if kp.getKeyUid() != genAccDto.keyUid:
|
||||
self.view.setEnteredPrivateKeyMatchTheKeypair(false)
|
||||
error "entered private key doesn't refer to a keyapir being imported"
|
||||
error "ki_entered private key doesn't refer to a keyapir being imported"
|
||||
return
|
||||
self.view.setEnteredPrivateKeyMatchTheKeypair(true)
|
||||
self.view.setPrivateKeyAccAddress(newDerivedAddressItem(order = 0, address = genAccDto.address, publicKey = genAccDto.publicKey))
|
||||
|
@ -127,7 +157,7 @@ method changeSeedPhrase*[T](self: Module[T], seedPhrase: string) =
|
|||
return
|
||||
let genAccDto = self.controller.createAccountFromSeedPhrase(seedPhrase)
|
||||
if seedPhrase.len > 0 and genAccDto.address.len == 0:
|
||||
error "unable to create an account from the provided seed phrase"
|
||||
error "ki_unable to create an account from the provided seed phrase"
|
||||
return
|
||||
|
||||
method validSeedPhrase*[T](self: Module[T], seedPhrase: string): bool =
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import NimQml
|
||||
import io_interface
|
||||
import internal/[state, state_wrapper]
|
||||
import app/modules/shared_models/[keypair_item, derived_address_model]
|
||||
import app/modules/shared_models/[keypair_model, derived_address_model]
|
||||
|
||||
QtObject:
|
||||
type
|
||||
|
@ -9,6 +9,8 @@ QtObject:
|
|||
delegate: io_interface.AccessInterface
|
||||
currentState: StateWrapper
|
||||
currentStateVariant: QVariant
|
||||
keypairModel: KeyPairModel
|
||||
keypairModelVariant: QVariant
|
||||
selectedKeypair: KeyPairItem
|
||||
selectedKeypairVariant: QVariant
|
||||
privateKeyAccAddress: DerivedAddressItem
|
||||
|
@ -20,6 +22,10 @@ QtObject:
|
|||
self.currentState.delete
|
||||
self.selectedKeypair.delete
|
||||
self.selectedKeypairVariant.delete
|
||||
if not self.keypairModel.isNil:
|
||||
self.keypairModel.delete
|
||||
if not self.keypairModelVariant.isNil:
|
||||
self.keypairModelVariant.delete
|
||||
self.QObject.delete
|
||||
|
||||
proc newView*(delegate: io_interface.AccessInterface): View =
|
||||
|
@ -37,6 +43,7 @@ QtObject:
|
|||
signalConnect(result.currentState, "backActionClicked()", result, "onBackActionClicked()", 2)
|
||||
signalConnect(result.currentState, "cancelActionClicked()", result, "onCancelActionClicked()", 2)
|
||||
signalConnect(result.currentState, "primaryActionClicked()", result, "onPrimaryActionClicked()", 2)
|
||||
signalConnect(result.currentState, "secondaryActionClicked()", result, "onSecondaryActionClicked()", 2)
|
||||
|
||||
proc currentStateObj*(self: View): State =
|
||||
return self.currentState.getStateObj()
|
||||
|
@ -57,6 +64,9 @@ QtObject:
|
|||
proc onPrimaryActionClicked*(self: View) {.slot.} =
|
||||
self.delegate.onPrimaryActionClicked()
|
||||
|
||||
proc onSecondaryActionClicked*(self: View) {.slot.} =
|
||||
self.delegate.onSecondaryActionClicked()
|
||||
|
||||
proc getSelectedKeypair*(self: View): KeyPairItem =
|
||||
return self.selectedKeypair
|
||||
proc getSelectedKeypairAsVariant*(self: View): QVariant {.slot.} =
|
||||
|
@ -64,9 +74,12 @@ QtObject:
|
|||
QtProperty[QVariant] selectedKeypair:
|
||||
read = getSelectedKeypairAsVariant
|
||||
|
||||
proc setSelectedKeypair*(self: View, item: KeyPairItem) =
|
||||
proc setSelectedKeypairItem*(self: View, item: KeyPairItem) =
|
||||
self.selectedKeypair.setItem(item)
|
||||
|
||||
proc setSelectedKeyPair*(self: View, keyUid: string) {.slot.} =
|
||||
self.delegate.setSelectedKeyPairByKeyUid(keyUid)
|
||||
|
||||
proc getPrivateKeyAccAddress*(self: View): DerivedAddressItem =
|
||||
return self.privateKeyAccAddress
|
||||
|
||||
|
@ -100,3 +113,23 @@ QtObject:
|
|||
proc setEnteredPrivateKeyMatchTheKeypair*(self: View, value: bool) =
|
||||
self.enteredPrivateKeyMatchTheKeypair = value
|
||||
self.enteredPrivateKeyMatchTheKeypairChanged()
|
||||
|
||||
proc keypairModel*(self: View): KeyPairModel =
|
||||
return self.keypairModel
|
||||
|
||||
proc keypairModelChanged(self: View) {.signal.}
|
||||
proc getKeypairModel(self: View): QVariant {.slot.} =
|
||||
if self.keypairModelVariant.isNil:
|
||||
return newQVariant()
|
||||
return self.keypairModelVariant
|
||||
QtProperty[QVariant] keypairModel:
|
||||
read = getKeypairModel
|
||||
notify = keypairModelChanged
|
||||
|
||||
proc createKeypairModel*(self: View, items: seq[KeyPairItem]) =
|
||||
if self.keypairModel.isNil:
|
||||
self.keypairModel = newKeyPairModel()
|
||||
if self.keypairModelVariant.isNil:
|
||||
self.keypairModelVariant = newQVariant(self.keypairModel)
|
||||
self.keypairModel.setItems(items)
|
||||
self.keypairModelChanged()
|
|
@ -10,8 +10,7 @@ const KeypairTypeProfile* = "profile"
|
|||
const KeypairTypeSeed* = "seed"
|
||||
const KeypairTypeKey* = "key"
|
||||
|
||||
const SyncedFromBackup* = "backup" # means a account is coming from backed up data
|
||||
const SyncedFromLocalPairing* = "local-pairing" # means a account is coming from another device when user is reocovering Status account
|
||||
const SyncedFromBackup* = "backup" # means a keypair is coming from backed up data
|
||||
|
||||
type
|
||||
KeypairDto* = ref object of RootObj
|
||||
|
|
|
@ -591,6 +591,10 @@ proc handleWalletAccount(self: Service, account: WalletAccountDto, notify: bool
|
|||
proc handleKeypair(self: Service, keypair: KeypairDto) =
|
||||
let localKp = self.getKeypairByKeyUid(keypair.keyUid)
|
||||
if not localKp.isNil:
|
||||
# sotore only keypair fields which may change
|
||||
localKp.name = keypair.name
|
||||
localKp.lastUsedDerivationIndex = keypair.lastUsedDerivationIndex
|
||||
localKp.syncedFrom = keypair.syncedFrom
|
||||
# - first remove removed accounts from the UI
|
||||
for localAcc in localKp.accounts:
|
||||
let accAddress = localAcc.address
|
||||
|
|
|
@ -20,10 +20,12 @@ StatusMenu {
|
|||
enabled: !!root.keyPair &&
|
||||
root.keyPair.pairType !== Constants.keypair.type.profile &&
|
||||
!root.keyPair.migratedToKeycard &&
|
||||
root.keyPair.operability === Constants.keypair.operability.fullyOperable
|
||||
root.keyPair.operability !== Constants.keypair.operability.nonOperable
|
||||
icon.name: "qr"
|
||||
icon.color: Theme.palette.primaryColor1
|
||||
onTriggered: {
|
||||
// in this case we need to check if any account of a keypair is partially operable and not migrated to a keycard
|
||||
// and if so we need to create a keystore for them first and then proceed with qr code
|
||||
console.warn("TODO: show encrypted QR")
|
||||
}
|
||||
}
|
||||
|
@ -46,6 +48,7 @@ StatusMenu {
|
|||
text: enabled? qsTr("Import keypair from device via encrypted QR") : ""
|
||||
enabled: !!root.keyPair &&
|
||||
root.keyPair.pairType !== Constants.keypair.type.profile &&
|
||||
!root.keyPair.migratedToKeycard &&
|
||||
root.keyPair.operability === Constants.keypair.operability.nonOperable &&
|
||||
root.keyPair.syncedFrom !== Constants.keypair.syncedFrom.backup
|
||||
icon.name: "qr-scan"
|
||||
|
@ -58,6 +61,7 @@ StatusMenu {
|
|||
StatusAction {
|
||||
text: enabled? root.keyPair.pairType === Constants.keypair.type.privateKeyImport? qsTr("Import via entering private key") : qsTr("Import via entering seed phrase") : ""
|
||||
enabled: !!root.keyPair &&
|
||||
!root.keyPair.migratedToKeycard &&
|
||||
root.keyPair.operability === Constants.keypair.operability.nonOperable &&
|
||||
(root.keyPair.pairType === Constants.keypair.type.seedImport ||
|
||||
root.keyPair.pairType === Constants.keypair.type.privateKeyImport)
|
||||
|
|
|
@ -130,6 +130,51 @@ Column {
|
|||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: importMissingKeypairs
|
||||
visible: importMissingKeypairs.unimportedKeypairs > 0
|
||||
height: 102
|
||||
width: parent.width
|
||||
color: Theme.palette.transparent
|
||||
radius: 8
|
||||
border.width: 1
|
||||
border.color: Theme.palette.baseColor5
|
||||
|
||||
readonly property int unimportedKeypairs: {
|
||||
let total = 0
|
||||
for (var i = 0; i < keypairsRepeater.count; i++) {
|
||||
let kp = keypairsRepeater.itemAt(i).keyPair
|
||||
if (kp.migratedToKeycard ||
|
||||
kp.operability === Constants.keypair.operability.fullyOperable ||
|
||||
kp.operability === Constants.keypair.operability.partiallyOperable) {
|
||||
continue
|
||||
}
|
||||
total++
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
padding: 16
|
||||
spacing: 8
|
||||
|
||||
StatusBaseText {
|
||||
text: qsTr("%n keypair(s) require import to use on this device", "", importMissingKeypairs.unimportedKeypairs)
|
||||
font.pixelSize: 15
|
||||
}
|
||||
|
||||
StatusButton {
|
||||
text: qsTr("Import missing keypairs")
|
||||
type: StatusBaseButton.Type.Warning
|
||||
icon.name: "download"
|
||||
onClicked: {
|
||||
root.walletStore.runKeypairImportPopup("", Constants.keypairImportPopup.importOption.selectKeypair)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer {
|
||||
width: parent.width
|
||||
}
|
||||
|
@ -138,6 +183,7 @@ Column {
|
|||
width: parent.width
|
||||
spacing: 24
|
||||
Repeater {
|
||||
id: keypairsRepeater
|
||||
objectName: "generatedAccounts"
|
||||
model: walletStore.originModel
|
||||
delegate: WalletKeyPairDelegate {
|
||||
|
|
|
@ -2,6 +2,7 @@ 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
|
||||
|
@ -12,12 +13,12 @@ import utils 1.0
|
|||
StatusListItem {
|
||||
id: root
|
||||
|
||||
property var sharedKeycardModule
|
||||
property ButtonGroup buttonGroup
|
||||
property bool usedAsSelectOption: false
|
||||
property bool tagClickable: false
|
||||
property bool tagDisplayRemoveAccountButton: false
|
||||
property bool canBeSelected: true
|
||||
property bool displayRadioButtonForSelection: true
|
||||
|
||||
property int keyPairType: Constants.keycard.keyPairType.unknown
|
||||
property string keyPairKeyUid: ""
|
||||
|
@ -115,23 +116,38 @@ StatusListItem {
|
|||
property list<Item> components: [
|
||||
StatusRadioButton {
|
||||
id: radioButton
|
||||
visible: root.usedAsSelectOption
|
||||
visible: root.usedAsSelectOption && root.displayRadioButtonForSelection
|
||||
ButtonGroup.group: root.buttonGroup
|
||||
onCheckedChanged: {
|
||||
if (!root.usedAsSelectOption || !root.canBeSelected)
|
||||
return
|
||||
if (checked) {
|
||||
root.sharedKeycardModule.setSelectedKeyPair(root.keyPairKeyUid)
|
||||
root.keyPairSelected()
|
||||
d.doAction(checked)
|
||||
}
|
||||
},
|
||||
StatusIcon {
|
||||
visible: root.usedAsSelectOption && !root.displayRadioButtonForSelection
|
||||
icon: "next"
|
||||
color: Theme.palette.baseColor1
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
d.doAction(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
function doAction(checked){
|
||||
if (!root.usedAsSelectOption || !root.canBeSelected)
|
||||
return
|
||||
if (checked) {
|
||||
root.keyPairSelected()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
if (!root.usedAsSelectOption || !root.canBeSelected)
|
||||
return
|
||||
radioButton.checked = true
|
||||
d.doAction(!root.displayRadioButtonForSelection || radioButton.checked)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,25 +8,18 @@ import SortFilterProxyModel 0.2
|
|||
Item {
|
||||
id: root
|
||||
|
||||
property var sharedKeycardModule
|
||||
property bool filterProfilePair: false
|
||||
property var keyPairModel
|
||||
property ButtonGroup buttonGroup
|
||||
property bool disableSelectionForKeypairsWithNonDefaultDerivationPath: true
|
||||
property bool displayRadioButtonForSelection: true
|
||||
property string optionLabel: ""
|
||||
property alias modelFilters: proxyModel.filters
|
||||
|
||||
signal keyPairSelected()
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
readonly property string profilePairTypeValue: Constants.keycard.keyPairType.profile
|
||||
}
|
||||
signal keyPairSelected(string keyUid)
|
||||
|
||||
SortFilterProxyModel {
|
||||
id: proxyModel
|
||||
sourceModel: root.keyPairModel
|
||||
filters: ExpressionFilter {
|
||||
expression: model.keyPair.pairType == d.profilePairTypeValue
|
||||
inverted: !root.filterProfilePair
|
||||
}
|
||||
}
|
||||
|
||||
ListView {
|
||||
|
@ -37,10 +30,12 @@ Item {
|
|||
delegate: KeyPairItem {
|
||||
width: ListView.view.width
|
||||
|
||||
sharedKeycardModule: root.sharedKeycardModule
|
||||
label: root.optionLabel
|
||||
buttonGroup: root.buttonGroup
|
||||
usedAsSelectOption: true
|
||||
canBeSelected: !model.keyPair.containsPathOutOfTheDefaultStatusDerivationTree()
|
||||
canBeSelected: !root.disableSelectionForKeypairsWithNonDefaultDerivationPath ||
|
||||
!model.keyPair.containsPathOutOfTheDefaultStatusDerivationTree()
|
||||
displayRadioButtonForSelection: root.displayRadioButtonForSelection
|
||||
|
||||
keyPairType: model.keyPair.pairType
|
||||
keyPairKeyUid: model.keyPair.keyUid
|
||||
|
@ -51,7 +46,7 @@ Item {
|
|||
keyPairAccounts: model.keyPair.accounts
|
||||
|
||||
onKeyPairSelected: {
|
||||
root.keyPairSelected()
|
||||
root.keyPairSelected(model.keyPair.keyUid)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,8 @@ import StatusQ.Controls 0.1
|
|||
import utils 1.0
|
||||
import shared.status 1.0
|
||||
|
||||
import SortFilterProxyModel 0.2
|
||||
|
||||
import "../helpers"
|
||||
|
||||
Item {
|
||||
|
@ -20,6 +22,11 @@ Item {
|
|||
|
||||
signal keyPairSelected()
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
readonly property string profilePairTypeValue: Constants.keycard.keyPairType.profile
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: Style.current.xlPadding
|
||||
|
@ -77,12 +84,14 @@ Item {
|
|||
Layout.alignment: Qt.AlignLeft
|
||||
Layout.preferredWidth: parent.width
|
||||
|
||||
sharedKeycardModule: root.sharedKeycardModule
|
||||
filterProfilePair: true
|
||||
modelFilters: ExpressionFilter {
|
||||
expression: model.keyPair.pairType == d.profilePairTypeValue
|
||||
}
|
||||
keyPairModel: root.sharedKeycardModule.keyPairModel
|
||||
buttonGroup: keyPairsButtonGroup
|
||||
|
||||
onKeyPairSelected: {
|
||||
root.sharedKeycardModule.setSelectedKeyPair(keyUid)
|
||||
root.keyPairSelected()
|
||||
}
|
||||
}
|
||||
|
@ -107,11 +116,15 @@ Item {
|
|||
Layout.alignment: Qt.AlignLeft
|
||||
Layout.preferredWidth: parent.width
|
||||
|
||||
sharedKeycardModule: root.sharedKeycardModule
|
||||
modelFilters: ExpressionFilter {
|
||||
expression: model.keyPair.pairType == d.profilePairTypeValue
|
||||
inverted: true
|
||||
}
|
||||
keyPairModel: root.sharedKeycardModule.keyPairModel
|
||||
buttonGroup: keyPairsButtonGroup
|
||||
|
||||
onKeyPairSelected: {
|
||||
root.sharedKeycardModule.setSelectedKeyPair(keyUid)
|
||||
root.keyPairSelected()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import StatusQ.Controls 0.1
|
|||
import utils 1.0
|
||||
|
||||
import "./stores"
|
||||
import "./states"
|
||||
import "../common"
|
||||
|
||||
StatusModal {
|
||||
|
@ -20,6 +21,15 @@ StatusModal {
|
|||
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
|
||||
|
||||
headerSettings.title: {
|
||||
switch (root.store.currentState.stateType) {
|
||||
case Constants.keypairImportPopup.state.selectKeypair:
|
||||
return qsTr("Import missing keypairs")
|
||||
case Constants.keypairImportPopup.state.selectImportMethod:
|
||||
return qsTr("Import %1 keypair").arg(root.store.selectedKeypair.name)
|
||||
case Constants.keypairImportPopup.state.scanQr:
|
||||
return qsTr("Scan encrypted QR")
|
||||
}
|
||||
|
||||
return qsTr("Import %1 keypair").arg(root.store.selectedKeypair.name)
|
||||
}
|
||||
|
||||
|
@ -46,6 +56,12 @@ StatusModal {
|
|||
width: parent.width
|
||||
sourceComponent: {
|
||||
switch (root.store.currentState.stateType) {
|
||||
case Constants.keypairImportPopup.state.selectKeypair:
|
||||
return selectKeypairComponent
|
||||
case Constants.keypairImportPopup.state.selectImportMethod:
|
||||
return selectImportMethodComponent
|
||||
case Constants.keypairImportPopup.state.scanQr:
|
||||
return scanQrComponent
|
||||
case Constants.keypairImportPopup.state.importPrivateKey:
|
||||
return keypairImportPrivateKeyComponent
|
||||
case Constants.keypairImportPopup.state.importSeedPhrase:
|
||||
|
@ -60,6 +76,28 @@ StatusModal {
|
|||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: selectKeypairComponent
|
||||
SelectKeypair {
|
||||
height: Constants.keypairImportPopup.contentHeight
|
||||
store: root.store
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: selectImportMethodComponent
|
||||
SelectImportMethod {
|
||||
height: Constants.keypairImportPopup.contentHeight
|
||||
store: root.store
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: scanQrComponent
|
||||
Item {
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: keypairImportPrivateKeyComponent
|
||||
EnterPrivateKey {
|
||||
|
@ -97,6 +135,8 @@ StatusModal {
|
|||
text: {
|
||||
switch (root.store.currentState.stateType) {
|
||||
|
||||
case Constants.keypairImportPopup.state.scanQr:
|
||||
return qsTr("Done")
|
||||
case Constants.keypairImportPopup.state.importPrivateKey:
|
||||
case Constants.keypairImportPopup.state.importSeedPhrase:
|
||||
return qsTr("Import %1 keypair").arg(root.store.selectedKeypair.name)
|
||||
|
|
|
@ -0,0 +1,100 @@
|
|||
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 SortFilterProxyModel 0.2
|
||||
|
||||
import "../stores"
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property KeypairImportStore store
|
||||
|
||||
implicitHeight: layout.implicitHeight
|
||||
|
||||
ColumnLayout {
|
||||
id: layout
|
||||
anchors.fill: parent
|
||||
anchors.margins: 16
|
||||
spacing: 16
|
||||
|
||||
StatusBaseText {
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Import method")
|
||||
font.pixelSize: Constants.keypairImportPopup.labelFontSize1
|
||||
color: Theme.palette.baseColor1
|
||||
}
|
||||
|
||||
StatusListItem {
|
||||
title: qsTr("Import via scanning encrypted QR")
|
||||
|
||||
asset {
|
||||
width: 24
|
||||
height: 24
|
||||
name: "qr"
|
||||
}
|
||||
|
||||
components: [
|
||||
StatusIcon {
|
||||
icon: "next"
|
||||
color: Theme.palette.baseColor1
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
root.store.currentState.doPrimaryAction()
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
onClicked: {
|
||||
root.store.currentState.doPrimaryAction()
|
||||
}
|
||||
}
|
||||
|
||||
StatusListItem {
|
||||
title: root.store.selectedKeypair.pairType === Constants.keypair.type.seedImport?
|
||||
qsTr("Import via entering seed phrase") :
|
||||
qsTr("Import via entering private key")
|
||||
|
||||
asset {
|
||||
width: 24
|
||||
height: 24
|
||||
name: root.store.selectedKeypair.pairType === Constants.keypair.type.seedImport?
|
||||
"key_pair_seed_phrase" :
|
||||
"objects"
|
||||
}
|
||||
|
||||
components: [
|
||||
StatusIcon {
|
||||
icon: "next"
|
||||
color: Theme.palette.baseColor1
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
root.store.currentState.doSecondaryAction()
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
onClicked: {
|
||||
root.store.currentState.doSecondaryAction()
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
import QtQuick 2.14
|
||||
import QtQuick.Controls 2.14
|
||||
import QtQuick.Layouts 1.14
|
||||
|
||||
import StatusQ.Core 0.1
|
||||
|
||||
import shared.popups.keycard.helpers 1.0
|
||||
|
||||
import utils 1.0
|
||||
|
||||
import SortFilterProxyModel 0.2
|
||||
|
||||
import "../stores"
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property KeypairImportStore store
|
||||
|
||||
implicitHeight: layout.implicitHeight
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
|
||||
readonly property string fullyOperableValue: Constants.keypair.operability.fullyOperable
|
||||
readonly property string partiallyOperableValue: Constants.keypair.operability.partiallyOperable
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: layout
|
||||
anchors.fill: parent
|
||||
anchors.margins: 16
|
||||
spacing: 16
|
||||
|
||||
StatusBaseText {
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("To use the associated accounts on this device, you need to import their keypairs.")
|
||||
font.pixelSize: Constants.keypairImportPopup.labelFontSize1
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
KeyPairList {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
|
||||
optionLabel: qsTr("import")
|
||||
disableSelectionForKeypairsWithNonDefaultDerivationPath: false
|
||||
displayRadioButtonForSelection: false
|
||||
modelFilters: ExpressionFilter {
|
||||
expression: model.keyPair.migratedToKeycard ||
|
||||
model.keyPair.operability == d.fullyOperableValue ||
|
||||
model.keyPair.operability == d.partiallyOperableValue
|
||||
inverted: true
|
||||
}
|
||||
keyPairModel: root.store.keypairImportModule.keypairModel
|
||||
|
||||
onKeyPairSelected: {
|
||||
root.store.keypairImportModule.setSelectedKeyPair(keyUid)
|
||||
root.store.currentState.doPrimaryAction()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -487,8 +487,7 @@ QtObject {
|
|||
}
|
||||
|
||||
readonly property QtObject syncedFrom: QtObject {
|
||||
readonly property string backup: "backup" // means a account is coming from backed up data
|
||||
readonly property string localPairing: "local-pairing" // means a account is coming from another device when user is reocovering Status account
|
||||
readonly property string backup: "backup" // means an account is coming from backed up data
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -743,14 +742,20 @@ QtObject {
|
|||
readonly property int popupWidth: 480
|
||||
readonly property int contentHeight: 626
|
||||
readonly property int footerButtonsHeight: 44
|
||||
readonly property int labelFontSize1: 15
|
||||
|
||||
readonly property QtObject importOption: QtObject {
|
||||
readonly property int seedPhrase: 1
|
||||
readonly property int privateKey: 2
|
||||
readonly property int selectKeypair: 1
|
||||
readonly property int seedPhrase: 2
|
||||
readonly property int privateKey: 3
|
||||
readonly property int qrCode: 4
|
||||
}
|
||||
|
||||
readonly property QtObject state: QtObject {
|
||||
readonly property string noState: "NoState"
|
||||
readonly property string selectKeypair: "SelectKeypair"
|
||||
readonly property string selectImportMethod: "SelectImportMethod"
|
||||
readonly property string scanQr: "ScanQr"
|
||||
readonly property string importSeedPhrase: "ImportSeedPhrase"
|
||||
readonly property string importPrivateKey: "ImportPrivateKey"
|
||||
}
|
||||
|
|
|
@ -856,8 +856,7 @@ QtObject {
|
|||
return qsTr("Restored from backup. Re-enter private key to use.")
|
||||
}
|
||||
}
|
||||
if (keypair.syncedFrom !== "" &&
|
||||
keypair.syncedFrom !== Constants.keypair.syncedFrom.localPairing) {
|
||||
if (keypair.syncedFrom !== "") {
|
||||
return qsTr("Synced from %1").arg(keypair.syncedFrom)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit b4b0d26aa4c3f11ee66e3aeb404834cd8c5e48c8
|
||||
Subproject commit 6ee70388093f674c9d98e8d03f452ec4542d5c2f
|
Loading…
Reference in New Issue