fix(@desktop/wallet): importing keypairs at the account level (navigating into an account page in Wallet) (point 9)

Point 9 of #11968
This commit is contained in:
Sale Djenic 2023-08-23 13:46:04 +02:00 committed by saledjenic
parent fadad5f4f6
commit aec5dc62c9
12 changed files with 249 additions and 57 deletions

View File

@ -197,7 +197,7 @@ proc newModule*[T](
result, events, tokenService, currencyService,
transactionService, walletAccountService,
settingsService, savedAddressService, networkService, accountsService,
keycardService, nodeService, networkConnectionService
keycardService, nodeService, networkConnectionService, devicesService
)
result.browserSectionModule = browser_section_module.newModule(
result, events, bookmarkService, settingsService, networkService,
@ -1355,6 +1355,6 @@ method communityMembersRevealedAccountsLoaded*[T](self: Module[T], communityId:
if revealedAccount.isAirdropAddress:
communityMembersAirdropAddress[pubkey] = revealedAccount.address
discard
self.view.model.setMembersAirdropAddress(communityId, communityMembersAirdropAddress)

View File

@ -68,3 +68,9 @@ proc toggleIncludeWatchOnlyAccount*(self: Controller) =
proc isIncludeWatchOnlyAccount*(self: Controller): bool =
return self.walletAccountService.isIncludeWatchOnlyAccount()
proc getKeypairByAccountAddress*(self: Controller, address: string): KeypairDto =
return self.walletAccountService.getKeypairByAccountAddress(address)
proc hasPairedDevices*(self: Controller): bool =
return self.walletAccountService.hasPairedDevices()

View File

@ -99,3 +99,18 @@ method getLatestBlockNumber*(self: AccessInterface, chainId: int): string {.base
method fetchDecodedTxData*(self: AccessInterface, txHash: string, data: string) {.base.} =
raise newException(ValueError, "No implementation available")
method runKeypairImportPopup*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method getKeypairImportModule*(self: AccessInterface): QVariant {.base.} =
raise newException(ValueError, "No implementation available")
method onKeypairImportModuleLoaded*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method destroyKeypairImportPopup*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method hasPairedDevices*(self: AccessInterface): bool {.base.} =
raise newException(ValueError, "No implementation available")

View File

@ -1,4 +1,4 @@
import NimQml, chronicles, sequtils, sugar
import NimQml, chronicles, sequtils, strutils, sugar
import ./controller, ./view, ./filter
import ./io_interface as io_interface
@ -12,25 +12,27 @@ import ./buy_sell_crypto/module as buy_sell_crypto_module
import ./networks/module as networks_module
import ./overview/module as overview_module
import ./send/module as send_module
import ../../shared_modules/add_account/module as add_account_module
import ./activity/controller as activityc
import app/modules/shared_modules/collectibles/controller as collectiblesc
import app/modules/shared_modules/collectible_details/controller as collectible_detailsc
import ../../../global/global_singleton
import ../../../core/eventemitter
import ../../../../app_service/service/keycard/service as keycard_service
import ../../../../app_service/service/token/service as token_service
import ../../../../app_service/service/currency/service as currency_service
import ../../../../app_service/service/transaction/service as transaction_service
import ../../../../app_service/service/wallet_account/service as wallet_account_service
import ../../../../app_service/service/settings/service as settings_service
import ../../../../app_service/service/saved_address/service as saved_address_service
import ../../../../app_service/service/network/service as network_service
import ../../../../app_service/service/accounts/service as accounts_service
import ../../../../app_service/service/node/service as node_service
import ../../../../app_service/service/network_connection/service as network_connection_service
import app/global/global_singleton
import app/core/eventemitter
import app/modules/shared_modules/add_account/module as add_account_module
import app/modules/shared_modules/keypair_import/module as keypair_import_module
import app_service/service/keycard/service as keycard_service
import app_service/service/token/service as token_service
import app_service/service/currency/service as currency_service
import app_service/service/transaction/service as transaction_service
import app_service/service/wallet_account/service as wallet_account_service
import app_service/service/settings/service as settings_service
import app_service/service/saved_address/service as saved_address_service
import app_service/service/network/service as network_service
import app_service/service/accounts/service as accounts_service
import app_service/service/node/service as node_service
import app_service/service/network_connection/service as network_connection_service
import app_service/service/devices/service as devices_service
import backend/collectibles as backend_collectibles
@ -52,13 +54,16 @@ type
view: View
filter: Filter
# shared modules
addAccountModule: add_account_module.AccessInterface
keypairImportModule: keypair_import_module.AccessInterface
# modules
accountsModule: accounts_module.AccessInterface
allTokensModule: all_tokens_module.AccessInterface
assetsModule: assets_module.AccessInterface
sendModule: send_module.AccessInterface
savedAddressesModule: saved_addresses_module.AccessInterface
buySellCryptoModule: buy_sell_crypto_module.AccessInterface
addAccountModule: add_account_module.AccessInterface
overviewModule: overview_module.AccessInterface
networksModule: networks_module.AccessInterface
networksService: network_service.Service
@ -66,6 +71,7 @@ type
keycardService: keycard_service.Service
accountsService: accounts_service.Service
walletAccountService: wallet_account_service.Service
devicesService: devices_service.Service
activityController: activityc.Controller
collectiblesController: collectiblesc.Controller
@ -73,6 +79,10 @@ type
# instance to be used in temporary, short-lived, workflows (e.g. send popup)
tmpActivityController: activityc.Controller
## Forward declaration
proc onUpdatedKeypairsOperability*(self: Module, updatedKeypairs: seq[KeypairDto])
proc onLocalPairingStatusUpdate*(self: Module, data: LocalPairingStatus)
proc newModule*(
delegate: delegate_interface.AccessInterface,
events: EventEmitter,
@ -86,7 +96,8 @@ proc newModule*(
accountsService: accounts_service.Service,
keycardService: keycard_service.Service,
nodeService: node_service.Service,
networkConnectionService: network_connection_service.Service
networkConnectionService: network_connection_service.Service,
devicesService: devices_service.Service
): Module =
result = Module()
result.delegate = delegate
@ -94,6 +105,7 @@ proc newModule*(
result.keycardService = keycardService
result.accountsService = accountsService
result.walletAccountService = walletAccountService
result.devicesService = devicesService
result.moduleLoaded = false
result.controller = newController(result, settingsService, walletAccountService, currencyService, networkService)
@ -132,6 +144,8 @@ method delete*(self: Module) =
if not self.addAccountModule.isNil:
self.addAccountModule.delete
if not self.keypairImportModule.isNil:
self.keypairImportModule.delete
method updateCurrency*(self: Module, currency: string) =
self.controller.updateCurrency(currency)
@ -167,10 +181,16 @@ method toggleWatchOnlyAccounts*(self: Module) =
self.filter.toggleWatchOnlyAccounts()
method setFilterAddress*(self: Module, address: string) =
let keypair = self.controller.getKeypairByAccountAddress(address)
if keypair.isNil:
self.view.setKeypairOperabilityForObservedAccount("")
else:
self.view.setKeypairOperabilityForObservedAccount(keypair.getOperability())
self.filter.setAddress(address)
self.notifyFilterChanged()
method setFillterAllAddresses*(self: Module) =
self.view.setKeypairOperabilityForObservedAccount("")
self.filter.setFillterAllAddresses()
self.notifyFilterChanged()
@ -189,8 +209,7 @@ method load*(self: Module) =
self.events.on(SIGNAL_WALLET_ACCOUNT_SAVED) do(e:Args):
let args = AccountArgs(e)
self.setTotalCurrencyBalance()
self.filter.setAddress(args.account.address)
self.notifyFilterChanged()
self.setFilterAddress(args.account.address)
self.events.on(SIGNAL_WALLET_ACCOUNT_DELETED) do(e:Args):
let args = AccountArgs(e)
self.setTotalCurrencyBalance()
@ -223,6 +242,14 @@ method load*(self: Module) =
self.events.on(SIGNAL_TRANSACTION_DECODED) do(e: Args):
let args = TransactionDecodedArgs(e)
self.view.txDecoded(args.txHash, args.dataDecoded)
self.events.on(SIGNAL_IMPORTED_KEYPAIRS) do(e:Args):
let args = KeypairsArgs(e)
if args.error.len != 0:
return
self.onUpdatedKeypairsOperability(args.keypairs)
self.events.on(SIGNAL_LOCAL_PAIRING_STATUS_UPDATE) do(e:Args):
let data = LocalPairingStatus(e)
self.onLocalPairingStatusUpdate(data)
self.controller.init()
self.view.load()
@ -344,3 +371,44 @@ method getLatestBlockNumber*(self: Module, chainId: int): string =
method fetchDecodedTxData*(self: Module, txHash: string, data: string) =
self.transactionService.fetchDecodedTxData(txHash, data)
proc onUpdatedKeypairsOperability*(self: Module, updatedKeypairs: seq[KeypairDto]) =
if self.filter.addresses.len != 1:
return
for kp in updatedKeypairs:
for acc in kp.accounts:
if cmpIgnoreCase(acc.address, self.filter.addresses[0]) == 0:
self.view.setKeypairOperabilityForObservedAccount(kp.getOperability())
return
method destroyKeypairImportPopup*(self: Module) =
if self.keypairImportModule.isNil:
return
self.view.emitDestroyKeypairImportPopup()
self.keypairImportModule.delete
self.keypairImportModule = nil
method runKeypairImportPopup*(self: Module) =
if self.filter.addresses.len != 1:
return
let keypair = self.controller.getKeypairByAccountAddress(self.filter.addresses[0])
if keypair.isNil:
return
self.keypairImportModule = keypair_import_module.newModule(self, self.events, self.accountsService,
self.walletAccountService, self.devicesService)
self.keypairImportModule.load(keypair.keyUid, ImportKeypairModuleMode.SelectImportMethod)
method getKeypairImportModule*(self: Module): QVariant =
if self.keypairImportModule.isNil:
return newQVariant()
return self.keypairImportModule.getModuleAsVariant()
method onKeypairImportModuleLoaded*(self: Module) =
self.view.emitDisplayKeypairImportPopup()
method hasPairedDevices*(self: Module): bool =
return self.controller.hasPairedDevices()
proc onLocalPairingStatusUpdate*(self: Module, data: LocalPairingStatus) =
if data.state == LocalPairingState.Finished:
self.view.emitHasPairedDevicesChangedSignal()

View File

@ -20,6 +20,7 @@ QtObject:
collectiblesController: collectiblesc.Controller
collectibleDetailsController: collectible_detailsc.Controller
isNonArchivalNode: bool
keypairOperabilityForObservedAccount: string
proc setup(self: View) =
self.QObject.setup
@ -167,3 +168,38 @@ QtObject:
notify = isNonArchivalNodeChanged
proc txDecoded*(self: View, txHash: string, dataDecoded: string) {.signal.}
proc hasPairedDevicesChanged*(self: View) {.signal.}
proc emitHasPairedDevicesChangedSignal*(self: View) =
self.hasPairedDevicesChanged()
proc getHasPairedDevices(self: View): bool {.slot.} =
return self.delegate.hasPairedDevices()
QtProperty[bool] hasPairedDevices:
read = getHasPairedDevices
notify = hasPairedDevicesChanged
proc keypairOperabilityForObservedAccountChanged(self: View) {.signal.}
proc getKeypairOperabilityForObservedAccount(self: View): string {.slot.} =
return self.keypairOperabilityForObservedAccount
QtProperty[string] keypairOperabilityForObservedAccount:
read = getKeypairOperabilityForObservedAccount
notify = keypairOperabilityForObservedAccountChanged
proc setKeypairOperabilityForObservedAccount*(self: View, value: string) =
self.keypairOperabilityForObservedAccount = value
self.keypairOperabilityForObservedAccountChanged()
proc runKeypairImportPopup*(self: View) {.slot.} =
self.delegate.runKeypairImportPopup()
proc getKeypairImportModule(self: View): QVariant {.slot.} =
return self.delegate.getKeypairImportModule()
QtProperty[QVariant] keypairImportModule:
read = getKeypairImportModule
proc displayKeypairImportPopup*(self: View) {.signal.}
proc emitDisplayKeypairImportPopup*(self: View) =
self.displayKeypairImportPopup()
proc destroyKeypairImportPopup*(self: View) {.signal.}
proc emitDestroyKeypairImportPopup*(self: View) =
self.destroyKeypairImportPopup()

View File

@ -30,6 +30,12 @@ proc getKeypairByKeyUid*(self: Service, keyUid: string): KeypairDto =
return
return self.keypairs[keyUid]
proc getKeypairByAccountAddress*(self: Service, address: string): KeypairDto =
for _, kp in self.keypairs:
for acc in kp.accounts:
if cmpIgnoreCase(acc.address, address) == 0:
return kp
proc getWatchOnlyAccounts*(self: Service): seq[WalletAccountDto] =
return toSeq(self.watchOnlyAccounts.values)

View File

@ -11,6 +11,7 @@ import AppLayouts.Wallet 1.0
import AppLayouts.Wallet.controls 1.0
import AppLayouts.Profile.popups 1.0
import shared.controls 1.0
import shared.popups 1.0
import shared.panels 1.0
import utils 1.0
@ -79,50 +80,17 @@ ColumnLayout {
}
}
Rectangle {
ImportKeypairInfo {
Layout.fillWidth: true
Layout.topMargin: Style.current.bigPadding
Layout.preferredHeight: childrenRect.height
visible: !!root.keyPair && root.keyPair.operability === Constants.keypair.operability.nonOperable
radius: Style.current.radius
border.width: 1
border.color: Theme.palette.directColor8
color: Theme.palette.transparent
ColumnLayout {
anchors.left: parent.left
anchors.right: parent.right
anchors.leftMargin: Style.current.padding
anchors.rightMargin: Style.current.padding
spacing: Style.current.halfPadding
StatusBaseText {
Layout.fillWidth: true
Layout.topMargin: Style.current.padding
text: qsTr("Import keypair to use this account")
color: Theme.palette.warningColor1
}
StatusBaseText {
Layout.fillWidth: true
wrapMode: Text.WordWrap
text: qsTr("This account was added to one of your synced devices. To use this account you will first need import the associated keypair to this device.")
}
StatusButton {
Layout.alignment: Qt.AlignLeft
Layout.bottomMargin: Style.current.padding
text: qsTr("Import keypair")
type: StatusBaseButton.Type.Warning
icon.name: "download"
onClicked: {
root.runImportMissingKeypairFlow()
}
}
onRunImport: {
root.runImportMissingKeypairFlow()
}
}
StatusBaseText {
Layout.topMargin: Style.current.bigPadding
text: qsTr("Account details")

View File

@ -6,6 +6,7 @@ import StatusQ.Layout 0.1
import utils 1.0
import shared.controls 1.0
import shared.popups.keypairimport 1.0
import "popups"
import "panels"
@ -32,6 +33,14 @@ Item {
function onFilterChanged(address, includeWatchOnly, allAddresses) {
root.showAllAccounts = allAddresses
}
function onDisplayKeypairImportPopup() {
keypairImport.active = true
}
function onDestroyKeypairImportPopup() {
keypairImport.active = false
}
}
function showSigningPhrasePopup(){
@ -150,4 +159,18 @@ Item {
anchors.centerIn: parent
}
}
Loader {
id: keypairImport
active: false
asynchronous: true
sourceComponent: KeypairImportPopup {
store.keypairImportModule: root.store.walletSectionInst.keypairImportModule
}
onLoaded: {
keypairImport.item.open()
}
}
}

View File

@ -5,6 +5,7 @@ import StatusQ.Core 0.1
import StatusQ.Controls 0.1
import utils 1.0
import shared.controls 1.0
import shared.views 1.0
import shared.stores 1.0
@ -76,6 +77,18 @@ Item {
onLaunchShareAddressModal: root.launchShareAddressModal()
onSwitchHideWatchOnlyAccounts: RootStore.toggleWatchOnlyAccounts()
}
ImportKeypairInfo {
Layout.fillWidth: true
Layout.topMargin: Style.current.bigPadding
Layout.preferredHeight: childrenRect.height
visible: root.store.walletSectionInst.hasPairedDevices && root.store.walletSectionInst.keypairOperabilityForObservedAccount === Constants.keypair.operability.nonOperable
onRunImport: {
root.store.walletSectionInst.runKeypairImportPopup()
}
}
StatusTabBar {
id: walletTabBar
objectName: "rightSideWalletTabBar"

View File

@ -11,6 +11,7 @@ QtObject {
id: root
property var mainModuleInst: mainModule
property var walletSectionInst: walletSection
property var aboutModuleInst: aboutModule
property var communitiesModuleInst: communitiesModule
property bool newVersionAvailable: false

View File

@ -0,0 +1,55 @@
import QtQuick 2.15
import QtQuick.Layouts 1.15
import StatusQ.Core 0.1
import StatusQ.Controls 0.1
import StatusQ.Core.Theme 0.1
import utils 1.0
Rectangle {
id: root
property string title: qsTr("Import keypair to use this account")
property string info: qsTr("This account was added to one of your synced devices. To use this account you will first need import the associated keypair to this device.")
property string buttonName: qsTr("Import keypair")
signal runImport()
radius: Style.current.radius
border.width: 1
border.color: Theme.palette.directColor8
color: Theme.palette.transparent
ColumnLayout {
anchors.left: parent.left
anchors.right: parent.right
anchors.leftMargin: Style.current.padding
anchors.rightMargin: Style.current.padding
spacing: Style.current.halfPadding
StatusBaseText {
Layout.fillWidth: true
Layout.topMargin: Style.current.padding
text: root.title
color: Theme.palette.warningColor1
}
StatusBaseText {
Layout.fillWidth: true
wrapMode: Text.WordWrap
text: root.info
}
StatusButton {
Layout.alignment: Qt.AlignLeft
Layout.bottomMargin: Style.current.padding
text: root.buttonName
type: StatusBaseButton.Type.Warning
icon.name: "download"
onClicked: {
root.runImport()
}
}
}
}

View File

@ -45,3 +45,4 @@ ErrorDetails 1.0 ErrorDetails.qml
CopyButton 1.0 CopyButton.qml
DisabledTooltipButton 1.0 DisabledTooltipButton.qml
WalletAccountListItem 1.0 WalletAccountListItem.qml
ImportKeypairInfo 1.0 ImportKeypairInfo.qml