From 96d9acf1f036426e3882fe1578f2fd9f581b4d7f Mon Sep 17 00:00:00 2001 From: Dario Gabriel Lipicar Date: Thu, 29 Feb 2024 11:53:08 -0300 Subject: [PATCH] feat(@desktop/wallet): implement jump to activity details screen from collectible details activity tab Fixes #13721 --- .../wallet_section/activity/controller.nim | 37 ++--- .../activity/details_controller.nim | 75 +++++++++++ .../modules/main/wallet_section/module.nim | 33 ++++- src/app/modules/main/wallet_section/view.nim | 15 ++- storybook/pages/TransactionDetailViewPage.qml | 41 ++++-- ui/app/AppLayouts/Wallet/stores/RootStore.qml | 1 + .../AppLayouts/Wallet/views/RightTabView.qml | 20 ++- .../Wallet/views/TransactionDetailView.qml | 127 +++++++++--------- .../collectibles/CollectibleDetailView.qml | 8 +- .../shared/controls/TransactionDelegate.qml | 6 +- ui/imports/shared/stores/RootStore.qml | 7 +- ui/imports/shared/views/HistoryView.qml | 22 ++- 12 files changed, 264 insertions(+), 128 deletions(-) create mode 100644 src/app/modules/main/wallet_section/activity/details_controller.nim diff --git a/src/app/modules/main/wallet_section/activity/controller.nim b/src/app/modules/main/wallet_section/activity/controller.nim index 76e586f072..70ae6c70e7 100644 --- a/src/app/modules/main/wallet_section/activity/controller.nim +++ b/src/app/modules/main/wallet_section/activity/controller.nim @@ -3,7 +3,6 @@ import tables, stint, sets import model import entry -import entry_details import recipients_model import collectibles_model import collectibles_item @@ -11,6 +10,8 @@ import events_handler import status import utils +import details_controller as details_controller + import web3/conversions import app/core/eventemitter @@ -48,7 +49,8 @@ QtObject: currencyService: currency_service.Service tokenService: token_service.Service savedAddressService: saved_address_service.Service - activityDetails: ActivityDetails + + detailsController: details_controller.Controller eventsHandler: EventsHandler status: Status @@ -114,30 +116,17 @@ QtObject: ae = entry.newTransactionActivityEntry(backendEntry, self.addresses, extraData, amountToCurrencyConvertor) result.add(ae) - proc fetchTxDetails*(self: Controller, entryIndex: int) {.slot.} = - let amountToCurrencyConvertor = proc(amount: UInt256, symbol: string): CurrencyAmount = - return currencyAmountToItem(self.currencyService.parseCurrencyValue(symbol, amount), - self.currencyService.getCurrencyFormat(symbol)) - - self.activityDetails = nil - let entry = self.model.getEntry(entryIndex) + proc fetchTxDetails*(self: Controller, txID: string) {.slot.} = + let index = self.model.getIndex(txID) + if index == -1: + error "entry index not found" + return + let entry = self.model.getEntry(index) if entry == nil: - error "failed to find entry with index: ", entryIndex + error "entry not found" return - try: - self.activityDetails = newActivityDetails(entry.getMetadata(), amountToCurrencyConvertor) - except Exception as e: - error "error: ", e.msg - return - - proc getActivityDetails(self: Controller): QVariant {.slot.} = - if self.activityDetails == nil: - return newQVariant() - return newQVariant(self.activityDetails) - - QtProperty[QVariant] activityDetails: - read = getActivityDetails + self.detailsController.setActivityEntry(entry) proc processResponse(self: Controller, response: JsonNode) = defer: self.status.setLoadingData(false) @@ -307,6 +296,7 @@ QtObject: ) proc newController*(requestId: int32, + detailsController: details_controller.Controller, currencyService: currency_service.Service, tokenService: token_service.Service, savedAddressService: saved_address_service.Service, @@ -325,6 +315,7 @@ QtObject: result.status = newStatus() result.currencyService = currencyService + result.detailsController = detailsController result.filterTokenCodes = initHashSet[string]() diff --git a/src/app/modules/main/wallet_section/activity/details_controller.nim b/src/app/modules/main/wallet_section/activity/details_controller.nim new file mode 100644 index 0000000000..4699715dc8 --- /dev/null +++ b/src/app/modules/main/wallet_section/activity/details_controller.nim @@ -0,0 +1,75 @@ +import NimQml, logging, stint + +import entry +import entry_details + +import app_service/service/currency/service as currency_service + +import app/modules/shared/wallet_utils +import app/modules/shared_models/currency_amount + +QtObject: + type + Controller* = ref object of QObject + activityEntry: ActivityEntry + activityDetails: ActivityDetails + currencyService: currency_service.Service + + proc setup(self: Controller) = + self.QObject.setup + + proc delete*(self: Controller) = + self.QObject.delete + + proc newController*(currencyService: currency_service.Service): Controller = + new(result, delete) + + result.currencyService = currencyService + + result.setup() + + proc activityEntryChanged*(self: Controller) {.signal.} + proc getActivityEntry(self: Controller): QVariant {.slot.} = + if self.activityEntry == nil: + return newQVariant() + return newQVariant(self.activityEntry) + + QtProperty[QVariant] activityEntry: + read = getActivityEntry + notify = activityEntryChanged + + proc activityDetailsChanged*(self: Controller) {.signal.} + proc getActivityDetails(self: Controller): QVariant {.slot.} = + if self.activityDetails == nil: + return newQVariant() + return newQVariant(self.activityDetails) + + QtProperty[QVariant] activityDetails: + read = getActivityDetails + notify = activityDetailsChanged + + proc setActivityEntry*(self: Controller, entry: ActivityEntry) = + self.activityEntry = entry + self.activityEntryChanged() + + if self.activityDetails != nil: + self.activityDetails = nil + self.activityDetailsChanged() + + proc resetActivityEntry*(self: Controller) {.slot.} = + self.setActivityEntry(nil) + + proc fetchExtraTxDetails*(self: Controller) {.slot.} = + let amountToCurrencyConvertor = proc(amount: UInt256, symbol: string): CurrencyAmount = + return currencyAmountToItem(self.currencyService.parseCurrencyValue(symbol, amount), + self.currencyService.getCurrencyFormat(symbol)) + if self.activityEntry == nil: + error "activity entry is not set" + return + + try: + self.activityDetails = newActivityDetails(self.activityEntry.getMetadata(), amountToCurrencyConvertor) + self.activityDetailsChanged() + except Exception as e: + error "error: ", e.msg + return diff --git a/src/app/modules/main/wallet_section/module.nim b/src/app/modules/main/wallet_section/module.nim index d3a4599c68..9a9a97b57e 100644 --- a/src/app/modules/main/wallet_section/module.nim +++ b/src/app/modules/main/wallet_section/module.nim @@ -15,6 +15,7 @@ import ./overview/module as overview_module import ./send/module as send_module import ./activity/controller as activityc +import ./activity/details_controller as activity_detailsc import ./wallet_connect/controller as wcc import app/modules/shared_models/collectibles_model as collectiblesm @@ -90,6 +91,7 @@ type # We need one for each app "layer" that simultaneously needs to show a different list of activity # entries (e.g. send popup is one "layer" above the collectible details activity tab) tmpActivityControllers: ActivityControllerArray + activityDetailsController: activity_detailsc.Controller wcController: wcc.Controller @@ -141,20 +143,36 @@ proc newModule*( result.networksService = networkService result.transactionService = transactionService - result.activityController = activityc.newController(int32(ActivityID.History), currencyService, tokenService, - savedAddressService, events) + result.activityDetailsController = activity_detailsc.newController(currencyService) + result.activityController = activityc.newController( + int32(ActivityID.History), + result.activityDetailsController, + currencyService, + tokenService, + savedAddressService, + events) result.tmpActivityControllers = [ - activityc.newController(int32(ActivityID.Temporary0), currencyService, tokenService, - savedAddressService, events), - activityc.newController(int32(ActivityID.Temporary1), currencyService, tokenService, - savedAddressService, events) + activityc.newController( + int32(ActivityID.Temporary0), + result.activityDetailsController, + currencyService, + tokenService, + savedAddressService, + events), + activityc.newController( + int32(ActivityID.Temporary1), + result.activityDetailsController, + currencyService, + tokenService, + savedAddressService, + events) ] result.collectibleDetailsController = collectible_detailsc.newController(int32(backend_collectibles.CollectiblesRequestID.WalletAccount), networkService, events) result.filter = initFilter(result.controller) result.wcController = wcc.newController(events, walletAccountService) - result.view = newView(result, result.activityController, result.tmpActivityControllers, result.collectibleDetailsController, result.wcController) + result.view = newView(result, result.activityController, result.tmpActivityControllers, result.activityDetailsController, result.collectibleDetailsController, result.wcController) method delete*(self: Module) = self.accountsModule.delete @@ -169,6 +187,7 @@ method delete*(self: Module) = self.activityController.delete for i in 0..self.tmpActivityControllers.len-1: self.tmpActivityControllers[i].delete + self.activityDetailsController.delete self.collectibleDetailsController.delete self.wcController.delete diff --git a/src/app/modules/main/wallet_section/view.nim b/src/app/modules/main/wallet_section/view.nim index 07a88c7ee1..bbd6a7d848 100644 --- a/src/app/modules/main/wallet_section/view.nim +++ b/src/app/modules/main/wallet_section/view.nim @@ -1,6 +1,7 @@ import NimQml, json import ./activity/controller as activityc +import ./activity/details_controller as activity_detailsc import app/modules/shared_modules/collectible_details/controller as collectible_detailsc import ./io_interface import ../../shared_models/currency_amount @@ -20,6 +21,7 @@ QtObject: tmpSymbol: string # shouldn't be used anywhere except in prepare*/getPrepared* procs activityController: activityc.Controller tmpActivityControllers: ActivityControllerArray + activityDetailsController: activity_detailsc.Controller collectibleDetailsController: collectible_detailsc.Controller isNonArchivalNode: bool keypairOperabilityForObservedAccount: string @@ -34,11 +36,17 @@ QtObject: proc delete*(self: View) = self.QObject.delete - proc newView*(delegate: io_interface.AccessInterface, activityController: activityc.Controller, tmpActivityControllers: ActivityControllerArray, collectibleDetailsController: collectible_detailsc.Controller, wcController: wcc.Controller): View = + proc newView*(delegate: io_interface.AccessInterface, + activityController: activityc.Controller, + tmpActivityControllers: ActivityControllerArray, + activityDetailsController: activity_detailsc.Controller, + collectibleDetailsController: collectible_detailsc.Controller, + wcController: wcc.Controller): View = new(result, delete) result.delegate = delegate result.activityController = activityController result.tmpActivityControllers = tmpActivityControllers + result.activityDetailsController = activityDetailsController result.collectibleDetailsController = collectibleDetailsController result.wcController = wcController @@ -164,6 +172,11 @@ QtObject: QtProperty[QVariant] tmpActivityController1: read = getTmpActivityController1 + proc getActivityDetailsController(self: View): QVariant {.slot.} = + return newQVariant(self.activityDetailsController) + QtProperty[QVariant] activityDetailsController: + read = getActivityDetailsController + proc getLatestBlockNumber*(self: View, chainId: int): string {.slot.} = return self.delegate.getLatestBlockNumber(chainId) diff --git a/storybook/pages/TransactionDetailViewPage.qml b/storybook/pages/TransactionDetailViewPage.qml index f2e3341631..b5aa0418d8 100644 --- a/storybook/pages/TransactionDetailViewPage.qml +++ b/storybook/pages/TransactionDetailViewPage.qml @@ -137,21 +137,16 @@ SplitView { id: transactionData property int chainId: 1 - property string blockNumber: "0x124" property int timestamp: Date.now() / 1000 property int txStatus: 0 property string type: "eth" - property string nonce: "0x123" property string from: "0x29D7d1dd5B6f9C864d9db560D72a247c178aE86B" property string to: "0x4de3f6278C0DdFd3F29df9DcD979038F5c7bbc35" - property string contract: "0x4de3f6278C0DdFd3F29df9DcD979038F5c7bbc35" property bool isNFT: false - property string input: "0x40e8d703000000000000000000000000670dca62b3418bddd08cbc69cb4490a5a3382a9f0000000000000000000000000000000000000000000000000000000000000064ddd08cbc69cb4490a5a3382a9f0000000000" property string tokenID: "4981676894159712808201908443964193325271219637660871887967796332739046670337" property string nftName: "Happy Meow" property string nftImageUrl: Style.png("collectibles/HappyMeow") property string symbol: "ETH" - property string txHash: "0x4de3f6278C0DdFd3F29df9DcD979038F5c7bbc35" readonly property var value: QtObject { property real amount: amountSpinbox.realValue @@ -159,6 +154,17 @@ SplitView { property int displayDecimals: 5 property bool stripTrailingZeroes: true } + } + + QtObject { + id: transactionDetails + + property string nonce: "0x123" + property string blockNumber: "0x124" + property string txHash: "0x4de3f6278C0DdFd3F29df9DcD979038F5c7bbc35" + property string txHashOut: "0x4de3f6278C0DdFd3F29df9DcD979038F5c7bbc35" + property string input: "0x40e8d703000000000000000000000000670dca62b3418bddd08cbc69cb4490a5a3382a9f0000000000000000000000000000000000000000000000000000000000000064ddd08cbc69cb4490a5a3382a9f0000000000" + property string contract: "0x4de3f6278C0DdFd3F29df9DcD979038F5c7bbc35" readonly property var totalFees: QtObject { property real amount: (transactionData.value / 15) * Math.pow(10, 9) @@ -167,10 +173,6 @@ SplitView { property bool stripTrailingZeroes: true } - readonly property var gasPrice: QtObject { - property real amount: 0.0000005 - property string symbol: "ETH" - } } QtObject { @@ -179,6 +181,25 @@ SplitView { property var mixedcaseAddress: root.isIncoming ? transactionData.to : transactionData.from } + QtObject { + id: controllerMockup + + property var activityEntry: transactionData + property var activityDetails + + function fetchExtraTxDetails() { + extraDetailsTimer.start() + } + + readonly property Timer extraDetailsTimer: Timer { + id: extraDetailsTimer + interval: 1000 + onTriggered: { + controllerMockup.activityDetails = transactionDetails + } + } + } + SplitView { orientation: Qt.Vertical SplitView.fillWidth: true @@ -203,7 +224,7 @@ SplitView { active: root.globalUtilsReady && root.mainModuleReady && root.rootStoreReady sourceComponent: TransactionDetailView { contactsStore: contactsStoreMockup - transaction: transactionData + controller: controllerMockup overview: overviewMockup } } diff --git a/ui/app/AppLayouts/Wallet/stores/RootStore.qml b/ui/app/AppLayouts/Wallet/stores/RootStore.qml index b230ed69fa..442d4c56f5 100644 --- a/ui/app/AppLayouts/Wallet/stores/RootStore.qml +++ b/ui/app/AppLayouts/Wallet/stores/RootStore.qml @@ -52,6 +52,7 @@ QtObject { property var activityController: walletSectionInst.activityController property var tmpActivityController0: walletSectionInst.tmpActivityController0 property var tmpActivityController1: walletSectionInst.tmpActivityController1 + property var activityDetailsController: walletSectionInst.activityDetailsController property string signingPhrase: walletSectionInst.signingPhrase property string mnemonicBackedUp: walletSectionInst.isMnemonicBackedUp property var walletConnectController: walletSectionInst.walletConnectController diff --git a/ui/app/AppLayouts/Wallet/views/RightTabView.qml b/ui/app/AppLayouts/Wallet/views/RightTabView.qml index 47a278b5f5..b1d56e49ca 100644 --- a/ui/app/AppLayouts/Wallet/views/RightTabView.qml +++ b/ui/app/AppLayouts/Wallet/views/RightTabView.qml @@ -208,9 +208,8 @@ RightTabBaseView { showAllAccounts: RootStore.showAllAccounts sendModal: root.sendModal filterVisible: filterButton.checked - onLaunchTransactionDetail: function (entryIndex) { - transactionDetailView.transactionIndex = entryIndex - transactionDetailView.transaction = Qt.binding(() => selectedTransaction) + onLaunchTransactionDetail: function (txID) { + RootStore.activityController.fetchTxDetails(txID) stack.currentIndex = 3 } } @@ -218,6 +217,10 @@ RightTabBaseView { } } CollectibleDetailView { + id: collectibleDetailView + + visible : (stack.currentIndex === 1) + collectible: RootStore.collectiblesStore.detailedCollectible isCollectibleLoading: RootStore.collectiblesStore.isDetailedCollectibleLoading activityModel: d.detailedCollectibleActivityController.model @@ -230,6 +233,14 @@ RightTabBaseView { RootStore.resetCurrentViewedHolding(Constants.TokenType.ERC721) } } + + onLaunchTransactionDetail: function (txID) { + d.detailedCollectibleActivityController.fetchTxDetails(txID) + stack.currentIndex = 3 + + // Take user to the activity view when they press the "Back" button + walletTabBar.currentIndex = 2 + } } AssetsDetailView { id: assetDetailView @@ -252,6 +263,7 @@ RightTabBaseView { TransactionDetailView { id: transactionDetailView + controller: RootStore.activityDetailsController onVisibleChanged: { if (visible) { if (!!transaction) { @@ -261,7 +273,7 @@ RightTabBaseView { } } } else { - transaction = null + controller.resetActivityEntry() } } showAllAccounts: RootStore.showAllAccounts diff --git a/ui/app/AppLayouts/Wallet/views/TransactionDetailView.qml b/ui/app/AppLayouts/Wallet/views/TransactionDetailView.qml index 6447fd0fb5..aadd11c34e 100644 --- a/ui/app/AppLayouts/Wallet/views/TransactionDetailView.qml +++ b/ui/app/AppLayouts/Wallet/views/TransactionDetailView.qml @@ -29,33 +29,44 @@ Item { property var contactsStore property var communitiesStore property var networkConnectionStore - property var transaction - property int transactionIndex + property var controller property var sendModal property bool showAllAccounts: false - readonly property bool isTransactionValid: transaction !== undefined && !!transaction - onTransactionChanged: { - d.reEvaluateSender = !d.reEvaluateSender - d.reEvaluateRecipient = !d.reEvaluateRecipient - d.reEvaluateSender = !d.reEvaluateSender - d.reEvaluateRecipient = !d.reEvaluateRecipient - - d.updateTransactionDetails() - } + readonly property alias transaction: d.transaction Component.onCompleted: d.updateTransactionDetails() QtObject { id: d + readonly property var transaction: root.controller.activityEntry + readonly property bool isTransactionValid: transaction !== undefined && !!transaction + + onTransactionChanged: { + d.reEvaluateSender = !d.reEvaluateSender + d.reEvaluateRecipient = !d.reEvaluateRecipient + d.reEvaluateSender = !d.reEvaluateSender + d.reEvaluateRecipient = !d.reEvaluateRecipient + + d.updateTransactionDetails() + } + property bool reEvaluateSender: true property bool reEvaluateRecipient: true - property var details: null + property var details: root.controller.activityDetails readonly property bool isDetailsValid: details !== undefined && !!details + + onDetailsChanged: { + if (!!d.details && !!d.details.input && d.details.input !== "0x") { + d.loadingInputDate = true + RootStore.fetchDecodedTxData(d.details.txHashOut, d.details.input) + } + } + readonly property bool isIncoming: transactionType === Constants.TransactionType.Received || transactionType === Constants.TransactionType.ContractDeployment - readonly property string networkShortName: root.isTransactionValid ? RootStore.getNetworkShortName(transaction.chainId) : "" + readonly property string networkShortName: d.isTransactionValid ? RootStore.getNetworkShortName(transaction.chainId) : "" readonly property string networkIcon: isTransactionValid ? RootStore.getNetworkIcon(transaction.chainId) : "network/Network=Custom" readonly property int blockNumber: isDetailsValid ? details.blockNumber : 0 readonly property int blockNumberIn: isDetailsValid ? details.blockNumberIn : 0 @@ -67,30 +78,30 @@ Item { readonly property string outSymbol: isTransactionValid ? transaction.outSymbol : "" readonly property var multichainNetworks: [] // TODO fill icon for networks for multichain readonly property string fiatValueFormatted: { - if (!root.isTransactionValid || transactionHeader.isMultiTransaction || !symbol) + if (!d.isTransactionValid || transactionHeader.isMultiTransaction || !symbol) return "" return RootStore.formatCurrencyAmount(transactionHeader.fiatValue, RootStore.currentCurrency) } readonly property string cryptoValueFormatted: { - if (!root.isTransactionValid || transactionHeader.isMultiTransaction) + if (!d.isTransactionValid || transactionHeader.isMultiTransaction) return "" const formatted = RootStore.formatCurrencyAmount(transaction.amount, transaction.symbol) return symbol || (!d.isDetailsValid || !d.details.contract) ? formatted : "%1 (%2)".arg(formatted).arg(Utils.compactAddress(transaction.tokenAddress, 4)) } readonly property string outFiatValueFormatted: { - if (!root.isTransactionValid || !transactionHeader.isMultiTransaction || !outSymbol) + if (!d.isTransactionValid || !transactionHeader.isMultiTransaction || !outSymbol) return "" return RootStore.formatCurrencyAmount(transactionHeader.outFiatValue, RootStore.currentCurrency) } readonly property string outCryptoValueFormatted: { - if (!root.isTransactionValid || !transactionHeader.isMultiTransaction) + if (!d.isTransactionValid || !transactionHeader.isMultiTransaction) return "" const formatted = RootStore.formatCurrencyAmount(transaction.outAmount, transaction.outSymbol) return outSymbol || !transaction.tokenOutAddress ? formatted : "%1 (%2)".arg(formatted).arg(Utils.compactAddress(transaction.tokenOutAddress, 4)) } readonly property real feeEthValue: d.details ? RootStore.getFeeEthValue(d.details.totalFees) : 0 - readonly property real feeFiatValue: root.isTransactionValid ? RootStore.getFiatValue(d.feeEthValue, Constants.ethToken) : 0 - readonly property int transactionType: root.isTransactionValid ? transaction.txType : Constants.TransactionType.Send + readonly property real feeFiatValue: d.isTransactionValid ? RootStore.getFiatValue(d.feeEthValue, Constants.ethToken) : 0 + readonly property int transactionType: d.isTransactionValid ? transaction.txType : Constants.TransactionType.Send readonly property bool isBridge: d.transactionType === Constants.TransactionType.Bridge property string decodedInputData: "" @@ -102,23 +113,17 @@ Item { function updateTransactionDetails() { d.decodedInputData = "" - if (!transaction) + if (!d.transaction) return - RootStore.fetchTxDetails(transactionIndex) - d.details = RootStore.getTxDetails() - - if (!!d.details && !!d.details.input) { - d.loadingInputDate = true - RootStore.fetchDecodedTxData(d.details.txHashOut, d.details.input) - } + root.controller.fetchExtraTxDetails() } } Connections { target: RootStore.walletSectionInst function onTxDecoded(txHash: string, dataDecoded: string) { - if (!root.isTransactionValid || (d.isDetailsValid && txHash !== d.details.txHashOut)) + if (!d.isTransactionValid || (d.isDetailsValid && txHash !== d.details.txHashOut)) return if (!dataDecoded) { d.loadingInputDate = false @@ -165,7 +170,7 @@ Item { showAllAccounts: root.showAllAccounts modelData: transaction - timeStampText: root.isTransactionValid ? qsTr("Signed at %1").arg(LocaleUtils.formatDateTime(transaction.timestamp * 1000, Locale.LongFormat)): "" + timeStampText: d.isTransactionValid ? qsTr("Signed at %1").arg(LocaleUtils.formatDateTime(transaction.timestamp * 1000, Locale.LongFormat)): "" rootStore: RootStore walletRootStore: WalletStores.RootStore community: isModelDataValid && communityId && communitiesStore ? communitiesStore.getCommunityDetailsAsJson(communityId) : null @@ -179,18 +184,18 @@ Item { WalletTxProgressBlock { id: progressBlock width: Math.min(513, root.width) - readonly property int latestBlockNumber: root.isTransactionValid && !pending && !error ? WalletStores.RootStore.getEstimatedLatestBlockNumber(root.transaction.chainId) : 0 - readonly property int latestBlockNumberIn: root.isTransactionValid && !pending && !error && transactionHeader.isMultiTransaction && d.isBridge ? WalletStores.RootStore.getEstimatedLatestBlockNumber(root.transaction.chainIdIn) : 0 + readonly property int latestBlockNumber: d.isTransactionValid && !pending && !error ? WalletStores.RootStore.getEstimatedLatestBlockNumber(d.transaction.chainId) : 0 + readonly property int latestBlockNumberIn: d.isTransactionValid && !pending && !error && transactionHeader.isMultiTransaction && d.isBridge ? WalletStores.RootStore.getEstimatedLatestBlockNumber(d.transaction.chainIdIn) : 0 error: transactionHeader.transactionStatus === Constants.TransactionStatus.Failed pending: transactionHeader.transactionStatus === Constants.TransactionStatus.Pending - outNetworkLayer: root.isTransactionValid ? Number(RootStore.getNetworkLayer(transactionHeader.isMultiTransaction ? root.transaction.chainIdOut : root.transaction.chainId)) : 0 - inNetworkLayer: root.isTransactionValid && transactionHeader.isMultiTransaction && d.isBridge ? Number(RootStore.getNetworkLayer(root.transaction.chainIdIn)) : 0 - outNetworkTimestamp: root.isTransactionValid ? root.transaction.timestamp : 0 - inNetworkTimestamp: root.isTransactionValid ? root.transaction.timestamp : 0 + outNetworkLayer: d.isTransactionValid ? Number(RootStore.getNetworkLayer(transactionHeader.isMultiTransaction ? d.transaction.chainIdOut : d.transaction.chainId)) : 0 + inNetworkLayer: d.isTransactionValid && transactionHeader.isMultiTransaction && d.isBridge ? Number(RootStore.getNetworkLayer(d.transaction.chainIdIn)) : 0 + outNetworkTimestamp: d.isTransactionValid ? d.transaction.timestamp : 0 + inNetworkTimestamp: d.isTransactionValid ? d.transaction.timestamp : 0 outChainName: transactionHeader.isMultiTransaction ? transactionHeader.networkNameOut : transactionHeader.networkName inChainName: transactionHeader.isMultiTransaction && d.isBridge ? transactionHeader.networkNameIn : "" - outNetworkConfirmations: root.isTransactionValid && latestBlockNumber > 0 ? latestBlockNumber - d.blockNumberOut : 0 - inNetworkConfirmations: root.isTransactionValid && latestBlockNumberIn > 0 ? latestBlockNumberIn - d.blockNumberIn : 0 + outNetworkConfirmations: d.isTransactionValid && latestBlockNumber > 0 ? latestBlockNumber - d.blockNumberOut : 0 + inNetworkConfirmations: d.isTransactionValid && latestBlockNumberIn > 0 ? latestBlockNumberIn - d.blockNumberIn : 0 } Separator { @@ -198,13 +203,13 @@ Item { } WalletNftPreview { - visible: root.isTransactionValid && transactionHeader.isNFT && !!transaction.nftImageUrl + visible: d.isTransactionValid && transactionHeader.isNFT && !!transaction.nftImageUrl width: Math.min(304, progressBlock.width) - nftName: root.isTransactionValid ? transaction.nftName : "" - nftUrl: root.isTransactionValid && !!transaction.nftImageUrl ? transaction.nftImageUrl : "" + nftName: d.isTransactionValid ? transaction.nftName : "" + nftUrl: d.isTransactionValid && !!transaction.nftImageUrl ? transaction.nftImageUrl : "" strikethrough: d.transactionType === Constants.TransactionType.Destroy - tokenId: root.isTransactionValid ? transaction.tokenID : "" - tokenAddress: root.isTransactionValid ? transaction.tokenAddress : "" + tokenId: d.isTransactionValid ? transaction.tokenID : "" + tokenAddress: d.isTransactionValid ? transaction.tokenAddress : "" areTestNetworksEnabled: WalletStores.RootStore.areTestNetworksEnabled isGoerliEnabled: WalletStores.RootStore.isGoerliEnabled } @@ -237,7 +242,7 @@ Item { Layout.fillHeight: true title: qsTr("From") subTitle: { - if (!root.isTransactionValid) + if (!d.isTransactionValid) return "" switch(d.transactionType) { case Constants.TransactionType.Swap: @@ -249,13 +254,13 @@ Item { } } asset.name: { - if (!root.isTransactionValid) + if (!d.isTransactionValid) return "" switch(d.transactionType) { case Constants.TransactionType.Swap: return Constants.tokenIcon(d.outSymbol) case Constants.TransactionType.Bridge: - return Style.svg(RootStore.getNetworkIcon(root.transaction.chainIdOut)) ?? Style.svg("network/Network=Custom") + return Style.svg(RootStore.getNetworkIcon(d.transaction.chainIdOut)) ?? Style.svg("network/Network=Custom") default: return "" } @@ -282,7 +287,7 @@ Item { case Constants.TransactionType.Swap: return Constants.tokenIcon(d.inSymbol) case Constants.TransactionType.Bridge: - return Style.svg(RootStore.getNetworkIcon(root.transaction.chainIdIn)) ?? Style.svg("network/Network=Custom") + return Style.svg(RootStore.getNetworkIcon(d.transaction.chainIdIn)) ?? Style.svg("network/Network=Custom") default: return "" } @@ -294,7 +299,7 @@ Item { width: parent.width title: d.transactionType === Constants.TransactionType.Swap || d.transactionType === Constants.TransactionType.Bridge ? qsTr("In") : qsTr("From") - addresses: root.isTransactionValid && d.reEvaluateSender? [root.transaction.sender] : [] + addresses: d.isTransactionValid && d.reEvaluateSender? [d.transaction.sender] : [] contactsStore: root.contactsStore rootStore: WalletStores.RootStore onButtonClicked: { @@ -337,7 +342,7 @@ Item { TransactionAddressTile { width: parent.width title: qsTr("To") - addresses: root.isTransactionValid && visible && d.reEvaluateRecipient? [root.transaction.recipient] : [] + addresses: d.isTransactionValid && visible && d.reEvaluateRecipient? [d.transaction.recipient] : [] contactsStore: root.contactsStore rootStore: WalletStores.RootStore onButtonClicked: addressMenu.openReceiverMenu(this, addresses[0], [d.networkShortName]) @@ -388,7 +393,7 @@ Item { // Used to display contract address for any network address: d.isDetailsValid ? d.details.contractIn : "" symbol: { - if (!root.isTransactionValid) + if (!d.isTransactionValid) return "" return d.symbol ? d.symbol : "(%1)".arg(Utils.compactAddress(transaction.tokenAddress, 4)) } @@ -407,7 +412,7 @@ Item { TransactionContractTile { // Used for Bridge and Swap to display 'To' network token contract address address: { - if (!root.isTransactionValid) + if (!d.isTransactionValid) return "" switch(d.transactionType) { case Constants.TransactionType.Swap: @@ -418,7 +423,7 @@ Item { } } symbol: { - if (!root.isTransactionValid) + if (!d.isTransactionValid) return "" switch(d.transactionType) { case Constants.TransactionType.Swap: @@ -431,7 +436,7 @@ Item { } networkName: transactionHeader.networkNameIn shortNetworkName: d.networkShortNameIn - visible: root.isTransactionValid && !!subTitle + visible: d.isTransactionValid && !!subTitle } } @@ -567,7 +572,7 @@ Item { width: parent.width title: !!transactionHeader.networkNameOut ? qsTr("Included in Block on %1").arg(transactionHeader.networkNameOut) : qsTr("Included on Block") subTitle: d.blockNumberOut - tertiaryTitle: root.isTransactionValid ? LocaleUtils.formatDateTime(transaction.timestamp * 1000, Locale.LongFormat) : "" + tertiaryTitle: d.isTransactionValid ? LocaleUtils.formatDateTime(transaction.timestamp * 1000, Locale.LongFormat) : "" visible: d.blockNumberOut > 0 && transactionHeader.isMultiTransaction } TransactionDataTile { @@ -577,7 +582,7 @@ Item { readonly property string networkName: transactionHeader.isMultiTransaction ? transactionHeader.networkNameIn : transactionHeader.networkName title: !!networkName ? qsTr("Included in Block on %1").arg(networkName) : qsTr("Included on Block") subTitle: blockNumber - tertiaryTitle: root.isTransactionValid ? LocaleUtils.formatDateTime(transaction.timestamp * 1000, Locale.LongFormat) : "" + tertiaryTitle: d.isTransactionValid ? LocaleUtils.formatDateTime(transaction.timestamp * 1000, Locale.LongFormat) : "" visible: blockNumber > 0 } } @@ -603,7 +608,7 @@ Item { Layout.alignment: Qt.AlignRight font.pixelSize: 15 color: Theme.palette.directColor5 - text: root.isTransactionValid ? qsTr("as of %1").arg(LocaleUtils.formatDateTime(transaction.timestamp * 1000, Locale.LongFormat)) : "" + text: d.isTransactionValid ? qsTr("as of %1").arg(LocaleUtils.formatDateTime(transaction.timestamp * 1000, Locale.LongFormat)) : "" elide: Text.ElideRight } } @@ -633,7 +638,7 @@ Item { width: parent.width title: transactionHeader.transactionStatus === Constants.TransactionStatus.Pending ? qsTr("Amount to receive") : qsTr("Amount received") subTitle: { - if (!root.isTransactionValid || transactionHeader.isNFT) + if (!d.isTransactionValid || transactionHeader.isNFT) return "" const type = d.transactionType if (type === Constants.TransactionType.Swap) { @@ -661,7 +666,7 @@ Item { width: parent.width title: d.symbol ? qsTr("Fees") : qsTr("Estimated max fee") subTitle: { - if (!root.isTransactionValid || transactionHeader.isNFT || !d.isDetailsValid) + if (!d.isTransactionValid || transactionHeader.isNFT || !d.isDetailsValid) return "" if (!d.symbol) { const maxFeeEth = RootStore.getFeeEthValue(d.details.maxTotalFees) @@ -749,7 +754,7 @@ Item { RowLayout { width: progressBlock.width - visible: root.isTransactionValid + visible: d.isTransactionValid spacing: 8 StatusButton { Layout.fillWidth: true @@ -757,10 +762,10 @@ Item { text: qsTr("Repeat transaction") size: StatusButton.Small - property alias tx: root.transaction + property alias tx: d.transaction visible: { - if (!root.isTransactionValid || root.overview.isWatchOnlyAccount) + if (!d.isTransactionValid || root.overview.isWatchOnlyAccount) return false return WalletStores.RootStore.isTxRepeatable(tx) @@ -809,7 +814,7 @@ Item { width: parent.width height: { // Using childrenRect and transactionvalid properties to refresh this binding - if (!isTransactionValid || detailsColumn.childrenRect.height === 0) + if (!d.isTransactionValid || detailsColumn.childrenRect.height === 0) return 0 // Height is calculated from visible children because Column doesn't handle diff --git a/ui/app/AppLayouts/Wallet/views/collectibles/CollectibleDetailView.qml b/ui/app/AppLayouts/Wallet/views/collectibles/CollectibleDetailView.qml index bd577fbac8..7f9bd9b42d 100644 --- a/ui/app/AppLayouts/Wallet/views/collectibles/CollectibleDetailView.qml +++ b/ui/app/AppLayouts/Wallet/views/collectibles/CollectibleDetailView.qml @@ -19,6 +19,8 @@ import "../../controls" Item { id: root + signal launchTransactionDetail(string txID) + required property var rootStore required property var walletRootStore required property var communitiesStore @@ -212,7 +214,11 @@ Item { community: isModelDataValid && !!communityId && !!root.communitiesStore ? root.communitiesStore.getCommunityDetailsAsJson(communityId) : null loading: false onClicked: { - // TODO: Implement switch to transaction details screen + if (mouse.button === Qt.RightButton) { + // TODO: Implement context menu + } else { + root.launchTransactionDetail(modelData.id) + } } } } diff --git a/ui/imports/shared/controls/TransactionDelegate.qml b/ui/imports/shared/controls/TransactionDelegate.qml index 80829e1afe..95e0b97a4d 100644 --- a/ui/imports/shared/controls/TransactionDelegate.qml +++ b/ui/imports/shared/controls/TransactionDelegate.qml @@ -50,6 +50,7 @@ StatusListItem { readonly property bool isModelDataValid: modelData !== undefined && !!modelData + readonly property string txID: isModelDataValid ? modelData.id : "INVALID" readonly property int transactionStatus: isModelDataValid ? modelData.status : Constants.TransactionStatus.Pending readonly property bool isMultiTransaction: isModelDataValid && modelData.isMultiTransaction readonly property string currentCurrency: rootStore.currentCurrency @@ -176,11 +177,6 @@ StatusListItem { } function getDetailsString(detailsObj) { - if (!detailsObj) { - rootStore.fetchTxDetails(index) - detailsObj = rootStore.getTxDetails() - } - let details = "" const endl = "\n" const endl2 = endl + endl diff --git a/ui/imports/shared/stores/RootStore.qml b/ui/imports/shared/stores/RootStore.qml index 97e651da7c..9b5158ef42 100644 --- a/ui/imports/shared/stores/RootStore.qml +++ b/ui/imports/shared/stores/RootStore.qml @@ -208,12 +208,13 @@ QtObject { walletSectionInst.fetchDecodedTxData(txHash, input) } - function fetchTxDetails(modelIndex) { - walletSectionInst.activityController.fetchTxDetails(modelIndex) + function fetchTxDetails(txID) { + walletSectionInst.activityController.fetchTxDetails(txID) + walletSectionInst.activityDetailsController.fetchExtraTxDetails() } function getTxDetails() { - return walletSectionInst.activityController.activityDetails + return walletSectionInst.activityDetailsController.activityDetails } property bool marketHistoryIsLoading: Global.appIsReady? walletSectionAllTokens.marketHistoryIsLoading : false diff --git a/ui/imports/shared/views/HistoryView.qml b/ui/imports/shared/views/HistoryView.qml index 55589fe7af..5e25598ac6 100644 --- a/ui/imports/shared/views/HistoryView.qml +++ b/ui/imports/shared/views/HistoryView.qml @@ -37,11 +37,9 @@ ColumnLayout { property bool hideVerticalScrollbar: false property int firstItemOffset: 0 - property var selectedTransaction - property real yPosition: transactionListRoot.visibleArea.yPosition * transactionListRoot.contentHeight - signal launchTransactionDetail(int entryIndex) + signal launchTransactionDetail(string txID) function resetView() { if (!!filterPanelLoader.item) { @@ -102,18 +100,13 @@ ColumnLayout { property string openTxDetailsHash - function openTxDetails(txHash) { + function openTxDetails(txID) { // Prevent opening details when loading, that will invalidate the model data if (RootStore.loadingHistoryTransactions) { return false } - const index = WalletStores.RootStore.currentActivityFiltersStore.transactionsList.getIndex(txHash) - if (index < 0) - return false - const entry = transactionListRoot.itemAtIndex(index) - root.selectedTransaction = Qt.binding(() => entry.modelData) - root.launchTransactionDetail(index) + root.launchTransactionDetail(txID) return true } } @@ -391,7 +384,11 @@ ColumnLayout { if (delegateMenu.transaction.sender !== delegateMenu.transaction.recipient) { WalletStores.RootStore.addressWasShown(delegateMenu.transaction.recipient) } - RootStore.copyToClipboard(delegateMenu.transactionDelegate.getDetailsString()) + + RootStore.fetchTxDetails(delegateMenu.transaction.id) + let detailsObj = RootStore.getTxDetails() + let detailsString = delegateMenu.transactionDelegate.getDetailsString(detailsObj) + RootStore.copyToClipboard(detailsString) } } StatusMenuSeparator { @@ -496,8 +493,7 @@ ColumnLayout { if (mouse.button === Qt.RightButton) { delegateMenu.openMenu(this, mouse, modelData) } else { - root.selectedTransaction = Qt.binding(() => transactionDelegate.model.activityEntry) - launchTransactionDetail(transactionDelegate.index) + launchTransactionDetail(modelData.id) } } }