feat(wallet) user can repeat a Send transaction from activity view
Enable user action to repeat a Send transaction from the activity view (HistoryView) and details view (TransactionDetailView). Extend AppMain send modal entry and SendModal API to allow for selecting all the required parameters for repeating a transaction. Optimize update of start timestamp for activity filter only when user attempts to open the filter panel. Closes #12122
This commit is contained in:
parent
41672271dc
commit
e805a9bf26
|
@ -343,8 +343,6 @@ QtObject:
|
|||
self.allAddressesSelected = allAddressesSelected
|
||||
self.status.setIsFilterDirty(true)
|
||||
|
||||
self.updateStartTimestamp()
|
||||
|
||||
proc setFilterAddressesJson*(self: Controller, jsonArray: string, allAddressesSelected: bool) {.slot.} =
|
||||
let addressesJson = parseJson(jsonArray)
|
||||
if addressesJson.kind != JArray:
|
||||
|
@ -393,9 +391,6 @@ QtObject:
|
|||
if res.result.getBool():
|
||||
self.status.setLoadingRecipients(false)
|
||||
|
||||
proc updateFilterBase(self: Controller) {.slot.} =
|
||||
self.updateStartTimestamp()
|
||||
|
||||
proc getStatus*(self: Controller): QVariant {.slot.} =
|
||||
return newQVariant(self.status)
|
||||
|
||||
|
|
|
@ -21,10 +21,6 @@ Column {
|
|||
|
||||
spacing: 12
|
||||
|
||||
Component.onCompleted: {
|
||||
activityFilterStore.updateFilterBase()
|
||||
}
|
||||
|
||||
function resetView() {
|
||||
activityFilterMenu.resetView()
|
||||
}
|
||||
|
@ -43,7 +39,10 @@ Column {
|
|||
border.color: Theme.palette.directColor8
|
||||
type: StatusRoundButton.Type.Tertiary
|
||||
icon.color: Theme.palette.primaryColor1
|
||||
onClicked: activityFilterMenu.popup(x, y + height + 4)
|
||||
onClicked: {
|
||||
activityFilterStore.updateStartTimestamp()
|
||||
activityFilterMenu.popup(x, y + height + 4)
|
||||
}
|
||||
}
|
||||
|
||||
ActivityFilterTagItem {
|
||||
|
|
|
@ -247,8 +247,8 @@ QtObject {
|
|||
activityController.updateFilter()
|
||||
}
|
||||
|
||||
function updateFilterBase() {
|
||||
activityController.updateFilterBase()
|
||||
function updateStartTimestamp() {
|
||||
activityController.updateStartTimestamp()
|
||||
}
|
||||
|
||||
function applyAllFilters() {
|
||||
|
|
|
@ -5,6 +5,7 @@ import QtQuick 2.13
|
|||
import utils 1.0
|
||||
import SortFilterProxyModel 0.2
|
||||
import StatusQ.Core.Theme 0.1
|
||||
import StatusQ.Core.Utils 0.1
|
||||
|
||||
QtObject {
|
||||
id: root
|
||||
|
@ -237,6 +238,59 @@ QtObject {
|
|||
return name
|
||||
}
|
||||
|
||||
enum LookupType {
|
||||
Account = 0,
|
||||
SavedAddress = 1
|
||||
}
|
||||
|
||||
// Returns object of type {type: null, object: null} or null if lookup didn't find anything
|
||||
function lookupAddressObject(address) {
|
||||
let res = null
|
||||
let acc = ModelUtils.getByKey(root.accounts, "address", address)
|
||||
if (acc) {
|
||||
res = {type: RootStore.LookupType.Account, object: acc}
|
||||
} else {
|
||||
let sa = ModelUtils.getByKey(walletSectionSavedAddresses.model, "address", address)
|
||||
if (sa) {
|
||||
res = {type: RootStore.LookupType.SavedAddress, object: sa}
|
||||
}
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
function getAssetForSendTx(tx) {
|
||||
if (tx.isNFT) {
|
||||
return {
|
||||
uid: tx.tokenID,
|
||||
chainId: tx.chainId,
|
||||
name: tx.nftName,
|
||||
imageUrl: tx.nftImageUrl,
|
||||
collectionUid: "",
|
||||
collectionName: ""
|
||||
}
|
||||
} else {
|
||||
return tx.symbol
|
||||
}
|
||||
}
|
||||
|
||||
function isTxRepeatable(tx) {
|
||||
if (tx.txType !== Constants.TransactionType.Send)
|
||||
return false
|
||||
|
||||
let res = root.lookupAddressObject(tx.sender)
|
||||
if (!res || res.type !== RootStore.LookupType.Account || res.object.walletType == Constants.watchWalletType)
|
||||
return false
|
||||
|
||||
if (tx.isNFT) {
|
||||
// TODO #12275: check if account owns enough NFT
|
||||
} else {
|
||||
// TODO #12275: Check if account owns enough tokens
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
function isOwnedAccount(address) {
|
||||
return walletSectionAccounts.isOwnedAccount(address)
|
||||
}
|
||||
|
|
|
@ -145,6 +145,7 @@ Item {
|
|||
id: historyView
|
||||
overview: RootStore.overview
|
||||
showAllAccounts: root.showAllAccounts
|
||||
sendModal: root.sendModal
|
||||
onLaunchTransactionDetail: function (entry, entryIndex) {
|
||||
transactionDetailView.transactionIndex = entryIndex
|
||||
transactionDetailView.transaction = entry
|
||||
|
|
|
@ -13,7 +13,7 @@ import StatusQ.Popups 0.1
|
|||
import shared.controls 1.0
|
||||
import shared.panels 1.0
|
||||
import utils 1.0
|
||||
import shared.stores 1.0
|
||||
import shared.popups.send 1.0
|
||||
|
||||
import "../controls"
|
||||
import "../popups"
|
||||
|
@ -714,7 +714,7 @@ Item {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Separator {
|
||||
width: progressBlock.width
|
||||
}
|
||||
|
@ -728,10 +728,29 @@ Item {
|
|||
Layout.preferredHeight: copyDetailsButton.height
|
||||
text: qsTr("Repeat transaction")
|
||||
size: StatusButton.Small
|
||||
visible: root.isTransactionValid && !root.overview.isWatchOnlyAccount && d.transactionType === TransactionDelegate.Send
|
||||
|
||||
property alias tx: root.transaction
|
||||
|
||||
visible: {
|
||||
if (!root.isTransactionValid || root.overview.isWatchOnlyAccount)
|
||||
return false
|
||||
|
||||
return WalletStores.RootStore.isTxRepeatable(tx)
|
||||
}
|
||||
onClicked: {
|
||||
root.sendModal.open(root.transaction.to)
|
||||
// TODO handle other types
|
||||
let asset = WalletStores.RootStore.getAssetForSendTx(tx)
|
||||
let req = Helpers.lookupAddressesForSendModal(tx.sender, tx.recipient, asset, tx.isNFT, tx.amount)
|
||||
|
||||
root.sendModal.preSelectedAccount = req.preSelectedAccount
|
||||
root.sendModal.preSelectedRecipient = req.preSelectedRecipient
|
||||
root.sendModal.preSelectedRecipientType = req.preSelectedRecipientType
|
||||
root.sendModal.preSelectedHolding = req.preSelectedHolding
|
||||
root.sendModal.preSelectedHoldingID = req.preSelectedHoldingID
|
||||
root.sendModal.preSelectedHoldingType = req.preSelectedHoldingType
|
||||
root.sendModal.preSelectedSendType = req.preSelectedSendType
|
||||
root.sendModal.preDefinedAmountToSend = req.preDefinedAmountToSend
|
||||
root.sendModal.onlyAssets = false
|
||||
root.sendModal.open()
|
||||
}
|
||||
}
|
||||
StatusButton {
|
||||
|
|
|
@ -24,6 +24,7 @@ import shared.popups.keycard 1.0
|
|||
import shared.status 1.0
|
||||
import shared.stores 1.0
|
||||
import shared.popups.send 1.0
|
||||
import shared.popups.send.views 1.0
|
||||
|
||||
import StatusQ.Core.Theme 0.1
|
||||
import StatusQ.Components 0.1
|
||||
|
@ -1360,16 +1361,20 @@ Item {
|
|||
this.active = true
|
||||
this.item.open()
|
||||
}
|
||||
|
||||
function closed() {
|
||||
// this.sourceComponent = undefined // kill an opened instance
|
||||
this.active = false
|
||||
}
|
||||
|
||||
property var preSelectedAccount
|
||||
property var preSelectedRecipient
|
||||
property int preSelectedRecipientType
|
||||
property var preSelectedHolding
|
||||
property string preSelectedHoldingID
|
||||
property int preSelectedHoldingType
|
||||
property int preSelectedSendType: Constants.SendType.Unknown
|
||||
property string preDefinedAmountToSend
|
||||
property bool onlyAssets: false
|
||||
|
||||
sourceComponent: SendModal {
|
||||
|
@ -1381,12 +1386,18 @@ Item {
|
|||
sendModal.preSelectedHoldingType = Constants.HoldingType.Unknown
|
||||
sendModal.preSelectedHolding = undefined
|
||||
sendModal.preSelectedAccount = undefined
|
||||
sendModal.preSelectedRecipient = undefined
|
||||
sendModal.preDefinedAmountToSend = ""
|
||||
}
|
||||
}
|
||||
onLoaded: {
|
||||
if (!!sendModal.preSelectedAccount) {
|
||||
item.preSelectedAccount = sendModal.preSelectedAccount
|
||||
}
|
||||
if (!!sendModal.preSelectedRecipient) {
|
||||
item.preSelectedRecipient = sendModal.preSelectedRecipient
|
||||
item.preSelectedRecipientType = sendModal.preSelectedRecipientType
|
||||
}
|
||||
if(sendModal.preSelectedSendType !== Constants.SendType.Unknown) {
|
||||
item.preSelectedSendType = sendModal.preSelectedSendType
|
||||
}
|
||||
|
@ -1397,6 +1408,9 @@ Item {
|
|||
if(!!preSelectedHolding) {
|
||||
item.preSelectedHolding = preSelectedHolding
|
||||
}
|
||||
if(preDefinedAmountToSend != "") {
|
||||
item.preDefinedAmountToSend = preDefinedAmountToSend
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
pragma Singleton
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQml 2.15
|
||||
|
||||
import utils 1.0
|
||||
import shared.stores 1.0
|
||||
import shared.stores.send 1.0
|
||||
import AppLayouts.Wallet.stores 1.0 as WalletStores
|
||||
|
||||
import StatusQ.Core 0.1
|
||||
|
||||
import "./panels"
|
||||
import "./controls"
|
||||
import "./views"
|
||||
|
||||
QtObject {
|
||||
id: root
|
||||
|
||||
function createSendModalRequirements() {
|
||||
return {
|
||||
preSelectedAccount: null,
|
||||
preSelectedRecipientType: TabAddressSelectorView.Type.Address,
|
||||
preSelectedRecipient: null,
|
||||
preSelectedHoldingType: 0,
|
||||
preSelectedHolding: null,
|
||||
preSelectedHoldingID: "",
|
||||
preDefinedAmountToSend: "",
|
||||
preSelectedSendType: Constants.SendType.Transfer
|
||||
}
|
||||
}
|
||||
|
||||
// \c token is an collectible object in case of \c isCollectible == true otherwise a token code (e.g. "ETH")
|
||||
function lookupAddressesForSendModal(senderAddress, recipientAddress, token, isCollectible, amount) {
|
||||
let req = createSendModalRequirements()
|
||||
|
||||
req.preSelectedSendType = Constants.SendType.Transfer
|
||||
let senderAccount = null
|
||||
let resolvedAcc = WalletStores.RootStore.lookupAddressObject(senderAddress)
|
||||
if (resolvedAcc && resolvedAcc.type == WalletStores.RootStore.LookupType.Account) {
|
||||
req.preSelectedAccount = resolvedAcc.object
|
||||
}
|
||||
|
||||
let res = WalletStores.RootStore.lookupAddressObject(recipientAddress)
|
||||
if (res) {
|
||||
if (res.type == WalletStores.RootStore.LookupType.Account) {
|
||||
req.preSelectedRecipientType = TabAddressSelectorView.Type.Account
|
||||
req.preSelectedRecipient = res.object
|
||||
} else if (res.type == WalletStores.RootStore.LookupType.SavedAddress) {
|
||||
req.preSelectedRecipientType = TabAddressSelectorView.Type.SavedAddress
|
||||
req.preSelectedRecipient = res.object
|
||||
}
|
||||
} else {
|
||||
req.preSelectedRecipientType = TabAddressSelectorView.Type.Address
|
||||
req.preSelectedRecipient = recipientAddress
|
||||
}
|
||||
|
||||
if (isCollectible) {
|
||||
req.preSelectedHoldingType = Constants.HoldingType.Collectible
|
||||
req.preSelectedHolding = token
|
||||
} else {
|
||||
req.preSelectedHoldingType = Constants.HoldingType.Asset
|
||||
req.preSelectedHoldingID = token
|
||||
}
|
||||
|
||||
req.preDefinedAmountToSend = LocaleUtils.numberToLocaleString(amount)
|
||||
|
||||
return req
|
||||
}
|
||||
}
|
|
@ -24,9 +24,14 @@ import "./views"
|
|||
StatusDialog {
|
||||
id: popup
|
||||
|
||||
property string preSelectedRecipient
|
||||
// expected content depends on the preSelectedRecipientType value.
|
||||
// If type Address this must be a string else it expects an object. See RecipientView.selectedRecipientType
|
||||
property var preSelectedRecipient
|
||||
property int preSelectedRecipientType: TabAddressSelectorView.Type.Address
|
||||
property string preDefinedAmountToSend
|
||||
// requires to have assigned an item from assets model
|
||||
property var preSelectedHolding
|
||||
// token symbol
|
||||
property string preSelectedHoldingID
|
||||
property int preSelectedHoldingType
|
||||
property int preSelectedSendType
|
||||
|
@ -156,8 +161,12 @@ StatusDialog {
|
|||
}
|
||||
|
||||
if(!!popup.preSelectedRecipient) {
|
||||
recipientLoader.selectedRecipientType = TabAddressSelectorView.Type.Address
|
||||
recipientLoader.selectedRecipient = {address: popup.preSelectedRecipient}
|
||||
recipientLoader.selectedRecipientType = popup.preSelectedRecipientType
|
||||
if (popup.preSelectedRecipientType == TabAddressSelectorView.Type.Address) {
|
||||
recipientLoader.selectedRecipient = {address: popup.preSelectedRecipient}
|
||||
} else {
|
||||
recipientLoader.selectedRecipient = popup.preSelectedRecipient
|
||||
}
|
||||
}
|
||||
|
||||
if(d.isBridgeTx) {
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
SendModal 1.0 SendModal.qml
|
||||
singleton Helpers 1.0 Helpers.qml
|
||||
|
|
|
@ -37,7 +37,7 @@ Loader {
|
|||
if(!!root.selectedRecipient && root.selectedRecipientType !== TabAddressSelectorView.Type.None) {
|
||||
let preferredChainIds = []
|
||||
switch(root.selectedRecipientType) {
|
||||
case TabAddressSelectorView.Type.Account: {
|
||||
case TabAddressSelectorView.Type.Account: {
|
||||
root.addressText = root.selectedRecipient.address
|
||||
preferredChainIds = root.selectedRecipient.preferredSharingChainIds
|
||||
break
|
||||
|
@ -105,8 +105,10 @@ Loader {
|
|||
}
|
||||
}
|
||||
|
||||
sourceComponent: root.selectedRecipientType === TabAddressSelectorView.Type.SavedAddress ? savedAddressRecipient:
|
||||
root.selectedRecipientType === TabAddressSelectorView.Type.Account ? myAccountRecipient : addressRecipient
|
||||
sourceComponent: root.selectedRecipientType === TabAddressSelectorView.Type.SavedAddress
|
||||
? savedAddressRecipient
|
||||
: root.selectedRecipientType === TabAddressSelectorView.Type.Account
|
||||
? myAccountRecipient : addressRecipient
|
||||
|
||||
Component {
|
||||
id: savedAddressRecipient
|
||||
|
|
|
@ -15,6 +15,7 @@ import utils 1.0
|
|||
|
||||
import "../panels"
|
||||
import "../popups"
|
||||
import "../popups/send"
|
||||
import "../stores"
|
||||
import "../controls"
|
||||
|
||||
|
@ -28,6 +29,7 @@ ColumnLayout {
|
|||
|
||||
property var overview
|
||||
property bool showAllAccounts: false
|
||||
property var sendModal
|
||||
|
||||
signal launchTransactionDetail(var transaction, int entryIndex)
|
||||
|
||||
|
@ -303,7 +305,6 @@ ColumnLayout {
|
|||
|
||||
delegateMenu.transactionDelegate = delegate
|
||||
delegateMenu.transaction = data
|
||||
repeatTransactionAction.enabled = !overview.isWatchOnlyAccount && delegate.modelData.txType === TransactionDelegate.Send
|
||||
popup(delegate, mouse.x, mouse.y)
|
||||
}
|
||||
|
||||
|
@ -314,13 +315,35 @@ ColumnLayout {
|
|||
|
||||
StatusAction {
|
||||
id: repeatTransactionAction
|
||||
|
||||
text: qsTr("Repeat transaction")
|
||||
enabled: false
|
||||
icon.name: "rotate"
|
||||
|
||||
property alias tx: delegateMenu.transaction
|
||||
|
||||
enabled: {
|
||||
if (!overview.isWatchOnlyAccount && !tx)
|
||||
return false
|
||||
return WalletStores.RootStore.isTxRepeatable(tx)
|
||||
}
|
||||
|
||||
onTriggered: {
|
||||
if (!delegateMenu.transaction)
|
||||
if (!tx)
|
||||
return
|
||||
root.sendModal.open(delegateMenu.transaction.to)
|
||||
let asset = WalletStores.RootStore.getAssetForSendTx(tx)
|
||||
|
||||
let req = Helpers.lookupAddressesForSendModal(tx.sender, tx.recipient, asset, tx.isNFT, tx.amount)
|
||||
|
||||
root.sendModal.preSelectedAccount = req.preSelectedAccount
|
||||
root.sendModal.preSelectedRecipient = req.preSelectedRecipient
|
||||
root.sendModal.preSelectedRecipientType = req.preSelectedRecipientType
|
||||
root.sendModal.preSelectedHolding = req.preSelectedHolding
|
||||
root.sendModal.preSelectedHoldingID = req.preSelectedHoldingID
|
||||
root.sendModal.preSelectedHoldingType = req.preSelectedHoldingType
|
||||
root.sendModal.preSelectedSendType = req.preSelectedSendType
|
||||
root.sendModal.preDefinedAmountToSend = req.preDefinedAmountToSend
|
||||
root.sendModal.onlyAssets = false
|
||||
root.sendModal.open()
|
||||
}
|
||||
}
|
||||
StatusSuccessAction {
|
||||
|
|
Loading…
Reference in New Issue