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:
Sale Djenic 2023-08-09 12:41:55 +02:00 committed by saledjenic
parent 7d4df690c5
commit 4c6af4f1ad
22 changed files with 484 additions and 61 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -2,7 +2,9 @@ import ../controller
type StateType* {.pure.} = enum
NoState = "NoState"
Main = "Main"
SelectKeypair = "SelectKeypair"
SelectImportMethod = "SelectImportMethod"
ScanQr = "ScanQr"
ImportSeedPhrase = "ImportSeedPhrase"
ImportPrivateKey = "ImportPrivateKey"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

2
vendor/status-go vendored

@ -1 +1 @@
Subproject commit b4b0d26aa4c3f11ee66e3aeb404834cd8c5e48c8
Subproject commit 6ee70388093f674c9d98e8d03f452ec4542d5c2f