feat(@desktop/wallet): Transaction activity summary (#10809)

closes #10751
This commit is contained in:
Cuteivist 2023-05-31 11:25:16 +02:00 committed by GitHub
parent 367640af6c
commit 7caa89b050
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 719 additions and 204 deletions

View File

@ -1,4 +1,4 @@
import NimQml, strutils, uri, strformat, strutils, stint import NimQml, strutils, uri, strformat, strutils, stint, re
import stew/byteutils import stew/byteutils
import ./utils/qrcodegen import ./utils/qrcodegen
@ -69,7 +69,7 @@ QtObject:
proc hex2Dec*(self: Utils, value: string): string {.slot.} = proc hex2Dec*(self: Utils, value: string): string {.slot.} =
# somehow this value crashes the app # somehow this value crashes the app
if value == "0x0": if value.find(re("0x0+$")) >= 0:
return "0" return "0"
return $stint.fromHex(StUint[256], value) return $stint.fromHex(StUint[256], value)

View File

@ -281,22 +281,10 @@ ListModel {
title: "TokenItem" title: "TokenItem"
section: "Components" section: "Components"
} }
ListElement {
title: "TransactionDelegate"
section: "Components"
}
ListElement { ListElement {
title: "CommunityPermissionsRow" title: "CommunityPermissionsRow"
section: "Components" section: "Components"
} }
ListElement {
title: "TransactionAddress"
section: "Components"
}
ListElement {
title: "TransactionAddressTile"
section: "Components"
}
ListElement { ListElement {
title: "StatusImageCropPanel" title: "StatusImageCropPanel"
section: "Components" section: "Components"
@ -321,6 +309,22 @@ ListModel {
title: "PopupSizing" title: "PopupSizing"
section: "Research / Examples" section: "Research / Examples"
} }
ListElement {
title: "TransactionDelegate"
section: "Wallet"
}
ListElement {
title: "TransactionAddress"
section: "Wallet"
}
ListElement {
title: "TransactionAddressTile"
section: "Wallet"
}
ListElement {
title: "TransactionDetailView"
section: "Wallet"
}
ListElement { ListElement {
title: "WalletHeader" title: "WalletHeader"
section: "Wallet" section: "Wallet"

View File

@ -0,0 +1,194 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import Storybook 1.0
import AppLayouts.Wallet 1.0
import AppLayouts.Wallet.stores 1.0 as WalletStores
import "../../ui/app/AppLayouts/Wallet/views" // NOTE - there is no AppLayout.Wallet.views
import shared.controls 1.0
import shared.stores 1.0
import utils 1.0
SplitView {
id: root
property bool globalUtilsReady: false
property bool mainModuleReady: false
property bool rootStoreReady: false
Component.onCompleted: {
RootStore.getFiatValue = (cryptoValue, symbol, currentCurrency) => { return 123 }
RootStore.getNetworkIcon = (chainId) => { return "tiny/network/Network=Ethereum" }
RootStore.getLatestBlockNumber = () => { return 4 }
RootStore.hex2Dec = (number) => { return 10 }
RootStore.getNetworkColor = (number) => { return "blue" }
RootStore.getNetworkFullName = (chainId) => { return "Ethereum Mainnet" }
RootStore.getNetworkShortName = (chainId) => { return "eth" }
RootStore.formatCurrencyAmount = (value, symbol) => { return value + " " + symbol }
RootStore.getNameForSavedWalletAddress = (address) => { return "Saved Wallet Name" }
RootStore.getNameForAddress = (address) => { return "Address Name" }
RootStore.getEnsForSavedWalletAddress = (address) => { return "123" }
RootStore.getChainShortNamesForSavedWalletAddress = (address) => { return "" }
RootStore.currentCurrency = "USD"
root.rootStoreReady = true
}
// globalUtilsInst mock
QtObject {
function getCompressedPk(publicKey) { return "zx3sh" + publicKey }
function getColorHashAsJson(publicKey) {
return JSON.stringify([{"segmentLength":1,"colorId":12},{"segmentLength":5,"colorId":18},
{"segmentLength":3,"colorId":25},{"segmentLength":3,"colorId":23},
{"segmentLength":1,"colorId":10},{"segmentLength":3,"colorId":26},
{"segmentLength":2,"colorId":30},{"segmentLength":1,"colorId":18},
{"segmentLength":4,"colorId":28},{"segmentLength":1,"colorId":17},
{"segmentLength":2,"colorId":2}])
}
function isCompressedPubKey(publicKey) { return true }
function getColorId(publicKey) { return Math.floor(Math.random() * 10) }
Component.onCompleted: {
Utils.globalUtilsInst = this
root.globalUtilsReady = true
}
Component.onDestruction: {
root.globalUtilsReady = false
Utils.globalUtilsInst = {}
}
}
// mainModuleInst mock
QtObject {
function getContactDetailsAsJson(publicKey, getVerification) {
return JSON.stringify({
displayName: "ArianaP",
displayIcon: "",
publicKey: publicKey,
name: "",
alias: "",
localNickname: "",
isContact: true
})
}
function isEnsVerified(publicKey) { return false }
Component.onCompleted: {
Utils.mainModuleInst = this
root.mainModuleReady = true
}
Component.onDestruction: {
root.mainModuleReady = false
Utils.mainModuleInst = {}
}
}
QtObject {
id: contactsStoreMockup
readonly property var myContactsModel: QtObject {
signal itemChanged(address: string)
}
function getContactPublicKeyByAddress(address) {
return ""
}
}
QtObject {
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: "0xdasdja214i12r0uf0jh013rfj01rfj12-09fuj12f012fuj0-129fuj012ujf1209u120912er902iue30912e"
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
property string symbol: "eth"
}
}
SplitView {
orientation: Qt.Vertical
SplitView.fillWidth: true
Item {
SplitView.fillWidth: true
SplitView.fillHeight: true
Rectangle {
anchors.fill: viewLoader
anchors.margins: -1
color: "transparent"
border.width: 1
border.color: "#808080"
}
Loader {
id: viewLoader
anchors.centerIn: parent
width: 800
height: 500
active: root.globalUtilsReady && root.mainModuleReady && root.rootStoreReady
sourceComponent: TransactionDetailView {
contactsStore: contactsStoreMockup
transaction: transactionData
}
}
}
LogsAndControlsPanel {
SplitView.minimumHeight: 100
SplitView.preferredHeight: 150
SplitView.fillWidth: true
}
}
Pane {
SplitView.minimumWidth: 300
SplitView.preferredWidth: 300
ColumnLayout {
Label {
text: "Amount:"
}
SpinBox {
id: amountSpinbox
from: 0
to: 999999999
value: 12345
stepSize: 1
editable: true
readonly property int multiplier: Math.pow(10, decimals)
property int decimals: 5
property real realValue: value / multiplier
validator: DoubleValidator { bottom: 0.0 }
textFromValue: function(value, locale) { return Number(value / amountSpinbox.multiplier).toLocaleString(locale, 'f', amountSpinbox.decimals) }
valueFromText: function(text, locale) { return Number.fromLocaleString(locale, text) * amountSpinbox.multiplier }
}
CheckBox {
text: "is NFT"
checked: transactionData.isNFT
onCheckedChanged: transactionData.isNFT = checked
}
}
}
}

View File

@ -9,7 +9,20 @@ QtObject {
property bool isWalletEnabled property bool isWalletEnabled
property var getSelectedTextWithFormationChars property var getSelectedTextWithFormationChars
property var gifColumnA property var gifColumnA
property var currentCurrency
property var currencyStore property var currencyStore
property var getNetworkIcon property var getNetworkIcon
property var getFiatValue
property var getLatestBlockNumber
property var hex2Dec
property var getNetworkColor
property var getNetworkFullName
property var getNetworkShortName
property var formatCurrencyAmount
property var getNameForSavedWalletAddress
property var getNameForAddress
property var getEnsForSavedWalletAddress
property var getChainShortNamesForSavedWalletAddress
} }

View File

@ -137,7 +137,6 @@ Rectangle {
MouseArea { MouseArea {
id: sensor id: sensor
z: 1 // Gives ability to hide siblings under the MouseArea
anchors.fill: parent anchors.fill: parent
cursorShape: containsMouse ? Qt.PointingHandCursor : Qt.ArrowCursor cursorShape: containsMouse ? Qt.PointingHandCursor : Qt.ArrowCursor
acceptedButtons: Qt.NoButton acceptedButtons: Qt.NoButton
@ -289,7 +288,7 @@ Rectangle {
objectName: "statusListItemSubTitle" objectName: "statusListItemSubTitle"
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
Layout.preferredWidth: inlineTagModelRepeater.count > 0 ? contentWidth : parent.width Layout.preferredWidth: inlineTagModelRepeater.count > 0 ? contentWidth : parent.width - subTitleBadgeLoader.width
text: root.subTitle text: root.subTitle
font.pixelSize: 15 font.pixelSize: 15

View File

@ -45,11 +45,6 @@ ColumnLayout {
readonly property int progress: (Math.floor(Date.now() / 1000) - root.timeStamp) / 3600 readonly property int progress: (Math.floor(Date.now() / 1000) - root.timeStamp) / 3600
} }
Separator {
Layout.fillWidth: true
implicitHeight: 1
}
StatusTxProgressBar { StatusTxProgressBar {
id: progressBar id: progressBar
Layout.topMargin: 8 Layout.topMargin: 8
@ -133,10 +128,4 @@ ColumnLayout {
} }
} }
} }
Separator {
Layout.fillWidth: true
Layout.topMargin: 8
implicitHeight: 1
}
} }

View File

@ -68,7 +68,7 @@ StatusMenu {
} }
function openMenu(delegate) { function openMenu(delegate) {
const x = delegate.width - root.contentWidth / 2 const x = delegate.width - 40
const y = delegate.height / 2 + 20 const y = delegate.height / 2 + 20
root.popup(delegate, x, y) root.popup(delegate, x, y)
} }
@ -102,6 +102,7 @@ StatusMenu {
function openEthAddressMenu(delegate, address) { function openEthAddressMenu(delegate, address) {
d.selectedAddress = address d.selectedAddress = address
address = address.toLowerCase()
const contactPubKey = "" // TODO retrive contact public key or contact data directly from address const contactPubKey = "" // TODO retrive contact public key or contact data directly from address
let contactData = Utils.getContactDetailsAsJson(contactPubKey) let contactData = Utils.getContactDetailsAsJson(contactPubKey)
let isWalletAccount = false let isWalletAccount = false
@ -135,6 +136,7 @@ StatusMenu {
function openTxMenu(delegate, address, chainShortName="") { function openTxMenu(delegate, address, chainShortName="") {
d.addressType = TransactionAddressMenu.AddressType.Tx d.addressType = TransactionAddressMenu.AddressType.Tx
d.selectedAddress = address d.selectedAddress = address
chainShortName = chainShortName.toLowerCase()
if (chainShortName === root.arbiscanShortChainName) { if (chainShortName === root.arbiscanShortChainName) {
showOnArbiscanAction.enabled = true showOnArbiscanAction.enabled = true
} else if (chainShortName === root.optimismShortChainName) { } else if (chainShortName === root.optimismShortChainName) {
@ -149,6 +151,7 @@ StatusMenu {
d.addressType = TransactionAddressMenu.AddressType.Contract d.addressType = TransactionAddressMenu.AddressType.Contract
d.contractName = name d.contractName = name
d.selectedAddress = address d.selectedAddress = address
chainShortName = chainShortName.toLowerCase()
if (chainShortName === root.arbiscanShortChainName) { if (chainShortName === root.arbiscanShortChainName) {
showOnArbiscanAction.enabled = true showOnArbiscanAction.enabled = true
} else if (chainShortName === root.optimismShortChainName) { } else if (chainShortName === root.optimismShortChainName) {
@ -232,8 +235,6 @@ StatusMenu {
successText: { successText: {
switch(d.addressType) { switch(d.addressType) {
case TransactionAddressMenu.AddressType.Contract: case TransactionAddressMenu.AddressType.Contract:
if (d.contractName.length > 0)
return qsTr("%1 contract address copied").arg(d.contractName)
return qsTr("Contract address copied") return qsTr("Contract address copied")
case TransactionAddressMenu.AddressType.InputData: case TransactionAddressMenu.AddressType.InputData:
return qsTr("Input data copied") return qsTr("Input data copied")
@ -250,8 +251,6 @@ StatusMenu {
defaultText: { defaultText: {
switch(d.addressType) { switch(d.addressType) {
case TransactionAddressMenu.AddressType.Contract: case TransactionAddressMenu.AddressType.Contract:
if (d.contractName.length > 0)
return qsTr("Copy %1 contract address").arg(d.contractName)
return qsTr("Copy contract address") return qsTr("Copy contract address")
case TransactionAddressMenu.AddressType.InputData: case TransactionAddressMenu.AddressType.InputData:
return qsTr("Copy input data") return qsTr("Copy input data")

View File

@ -2,6 +2,7 @@ import QtQuick 2.13
import QtQuick.Layouts 1.13 import QtQuick.Layouts 1.13
import QtQuick.Controls 2.14 import QtQuick.Controls 2.14
import QtQuick.Window 2.12 import QtQuick.Window 2.12
import QtGraphicalEffects 1.15
import StatusQ.Components 0.1 import StatusQ.Components 0.1
import StatusQ.Core.Theme 0.1 import StatusQ.Core.Theme 0.1
@ -10,6 +11,7 @@ import StatusQ.Controls 0.1
import StatusQ.Popups 0.1 import StatusQ.Popups 0.1
import shared.controls 1.0 import shared.controls 1.0
import shared.panels 1.0
import utils 1.0 import utils 1.0
import shared.stores 1.0 import shared.stores 1.0
@ -38,96 +40,360 @@ Item {
readonly property string to: root.isTransactionValid ? !!savedAddressNameTo ? savedAddressNameTo : Utils.compactAddress(transaction.to, 4): "" readonly property string to: root.isTransactionValid ? !!savedAddressNameTo ? savedAddressNameTo : Utils.compactAddress(transaction.to, 4): ""
readonly property string savedAddressEns: root.isTransactionValid ? RootStore.getEnsForSavedWalletAddress(isIncoming ? transaction.from : transaction.to) : "" readonly property string savedAddressEns: root.isTransactionValid ? RootStore.getEnsForSavedWalletAddress(isIncoming ? transaction.from : transaction.to) : ""
readonly property string savedAddressChains: root.isTransactionValid ? RootStore.getChainShortNamesForSavedWalletAddress(isIncoming ? transaction.from : transaction.to) : "" readonly property string savedAddressChains: root.isTransactionValid ? RootStore.getChainShortNamesForSavedWalletAddress(isIncoming ? transaction.from : transaction.to) : ""
readonly property string networkShortName: root.isTransactionValid ? RootStore.getNetworkShortName(transaction.chainId) : ""
readonly property string networkFullName: root.isTransactionValid ? RootStore.getNetworkFullName(transaction.chainId): ""
readonly property string networkIcon: root.isTransactionValid ? RootStore.getNetworkIcon(transaction.chainId): ""
readonly property int blockNumber: root.isTransactionValid ? RootStore.hex2Dec(root.transaction.blockNumber) : 0
readonly property string bridgeNetworkIcon: "" // TODO fill when bridge data is implemented
readonly property string bridgeNetworkFullname: "" // TODO fill when bridge data is implemented
readonly property string bridgeNetworkShortName: "" // TODO fill when bridge data is implemented
readonly property int bridgeBlockNumber: 0 // TODO fill when bridge data is implemented
readonly property string swapSymbol: "" // TODO fill when swap data is implemented
readonly property string symbol: root.isTransactionValid ? transaction.symbol : ""
readonly property var multichainNetworks: [] // TODO fill icon for networks for multichain
function getNameForSavedWalletAddress(address) { function getNameForSavedWalletAddress(address) {
return RootStore.getNameForSavedWalletAddress(address) return RootStore.getNameForSavedWalletAddress(address)
} }
function retryTransaction() {
// TODO handle failed transaction retry
}
} }
StatusScrollView { StatusScrollView {
id: scrollView
anchors.top: parent.top anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
width: parent.width width: parent.width
height: parent.height height: parent.height
contentHeight: column.height contentHeight: column.height
contentWidth: parent.width
Column { Column {
id: column id: column
width: parent.width - Style.current.xlPadding width: scrollView.availableWidth - Style.current.xlPadding
spacing: Style.current.bigPadding spacing: Style.current.xlPadding + Style.current.halfPadding
TransactionDelegate { Column {
id: transactionHeader
objectName: "transactionDetailHeader"
width: parent.width width: parent.width
leftPadding: 0 spacing: Style.current.bigPadding
modelData: transaction TransactionDelegate {
transactionType: d.isIncoming ? TransactionDelegate.Receive : TransactionDelegate.Send id: transactionHeader
currentCurrency: RootStore.currentCurrency objectName: "transactionDetailHeader"
cryptoValue: root.isTransactionValid ? transaction.value.amount: 0.0 width: parent.width
fiatValue: root.isTransactionValid ? RootStore.getFiatValue(cryptoValue, symbol, currentCurrency): 0.0 leftPadding: 0
networkIcon: root.isTransactionValid ? RootStore.getNetworkIcon(transaction.chainId): ""
networkColor: root.isTransactionValid ? RootStore.getNetworkColor(transaction.chainId): ""
networkName: root.isTransactionValid ? RootStore.getNetworkFullName(transaction.chainId): ""
symbol: root.isTransactionValid ? transaction.symbol : ""
transferStatus: root.isTransactionValid ? RootStore.hex2Dec(transaction.txStatus): ""
timeStampText: root.isTransactionValid ? qsTr("Signed at %1").arg(LocaleUtils.formatDateTime(transaction.timestamp * 1000, Locale.LongFormat)): ""
addressNameTo: root.isTransactionValid ? WalletStores.RootStore.getNameForAddress(transaction.to): ""
addressNameFrom: root.isTransactionValid ? WalletStores.RootStore.getNameForAddress(transaction.from): ""
sensor.enabled: false
formatCurrencyAmount: RootStore.formatCurrencyAmount
color: Theme.palette.transparent
state: "header"
onRetryClicked: { modelData: transaction
// TODO handle failed transaction retry transactionType: d.isIncoming ? TransactionDelegate.Receive : TransactionDelegate.Send
currentCurrency: RootStore.currentCurrency
cryptoValue: root.isTransactionValid ? transaction.value.amount: 0.0
fiatValue: root.isTransactionValid ? RootStore.getFiatValue(cryptoValue, symbol, currentCurrency): 0.0
networkIcon: d.networkIcon
networkColor: root.isTransactionValid ? RootStore.getNetworkColor(transaction.chainId): ""
networkName: d.networkFullName
swapSymbol: d.swapSymbol
bridgeNetworkName: d.bridgeNetworkFullname
symbol: d.symbol
transferStatus: root.isTransactionValid ? RootStore.hex2Dec(transaction.txStatus): ""
timeStampText: root.isTransactionValid ? qsTr("Signed at %1").arg(LocaleUtils.formatDateTime(transaction.timestamp * 1000, Locale.LongFormat)): ""
addressNameTo: root.isTransactionValid ? WalletStores.RootStore.getNameForAddress(transaction.to): ""
addressNameFrom: root.isTransactionValid ? WalletStores.RootStore.getNameForAddress(transaction.from): ""
sensor.enabled: false
formatCurrencyAmount: RootStore.formatCurrencyAmount
color: Theme.palette.transparent
state: "header"
onRetryClicked: d.retryTransaction()
} }
Separator { }
} }
WalletTxProgressBlock { WalletTxProgressBlock {
id: progressBlock
width: Math.min(513, root.width) width: Math.min(513, root.width)
error: transactionHeader.transactionStatus === TransactionDelegate.TransactionStatus.Failed error: transactionHeader.transactionStatus === TransactionDelegate.TransactionStatus.Failed
isLayer1: RootStore.getNetworkLayer(root.transaction.chainId) == 1 isLayer1: RootStore.getNetworkLayer(root.transaction.chainId) == 1
confirmations: root.isTransactionValid ? Math.abs(WalletStores.RootStore.getLatestBlockNumber(root.transaction.chainId) - RootStore.hex2Dec(root.transaction.blockNumber)): 0 confirmations: root.isTransactionValid ? Math.abs(WalletStores.RootStore.getLatestBlockNumber(root.transaction.chainId) - d.blockNumber): 0
chainName: root.isTransactionValid ? RootStore.getNetworkFullName(root.transaction.chainId): "" chainName: d.networkFullName
timeStamp: root.isTransactionValid ? transaction.timestamp: "" timeStamp: root.isTransactionValid ? transaction.timestamp: ""
} }
SavedAddressesDelegate { Separator {
width: parent.width width: progressBlock.width
name: d.isIncoming ? d.savedAddressNameFrom : d.savedAddressNameTo
address: root.isTransactionValid ? d.isIncoming ? transaction.from : transaction.to : ""
ens: d.savedAddressEns
chainShortNames: d.savedAddressChains
title: d.isIncoming ? d.from : d.to
subTitle: root.isTransactionValid ? d.isIncoming ? !!d.savedAddressNameFrom ? Utils.compactAddress(transaction.from, 4) : "" : !!d.savedAddressNameTo ? Utils.compactAddress(transaction.to, 4) : "": ""
store: WalletStores.RootStore
contactsStore: root.contactsStore
onOpenSendModal: root.sendModal.open(address);
saveAddress: function(name, address, favourite, chainShortNames, ens) {
RootStore.createOrUpdateSavedAddress(name, address, favourite, chainShortNames, ens)
}
deleteSavedAddress: function(address, ens) {
RootStore.deleteSavedAddress(address, ens)
}
} }
StatusExpandableItem { Column {
width: parent.width width: progressBlock.width
anchors.horizontalCenter: parent.horizontalCenter spacing: 0
type: StatusExpandableItem.Type.Tertiary StatusBaseText {
expandable: true width: parent.width
primaryText: qsTr("Transaction summary") font.pixelSize: 15
expandableComponent: transactionSummary color: Theme.palette.directColor5
separatorVisible: false text: qsTr("Transaction summary")
expanded: true elide: Text.ElideRight
}
Item {
width: parent.width
height: Style.current.smallPadding
}
DetailsPanel {
RowLayout {
spacing: 0
width: parent.width
height: opacity > 0 ? Math.max(implicitHeight, 85) : 0
opacity: fromNetworkTile.visible || toNetworkTile.visible ? 1 : 0
TransactionDataTile {
id: fromNetworkTile
Layout.fillWidth: true
Layout.fillHeight: true
title: qsTr("From")
subTitle: {
switch(transactionHeader.transactionType) {
case TransactionDelegate.Swap:
return d.symbol
case TransactionDelegate.Bridge:
return d.networkFullName
default:
return ""
}
}
asset.name: {
switch(transactionHeader.transactionType) {
case TransactionDelegate.Swap:
return !!d.symbol ? Style.png("tokens/%1".arg(d.symbol)) : ""
case TransactionDelegate.Bridge:
return !!d.networkIcon ? Style.svg(d.networkIcon) : ""
default:
return ""
}
}
visible: !!subTitle
}
TransactionDataTile {
id: toNetworkTile
Layout.fillWidth: true
Layout.fillHeight: true
title: qsTr("To")
subTitle: {
switch(transactionHeader.transactionType) {
case TransactionDelegate.Swap:
return d.swapSymbol
case TransactionDelegate.Bridge:
return d.bridgeNetworkFullname
default:
return ""
}
}
asset.name: {
switch(transactionHeader.transactionType) {
case TransactionDelegate.Swap:
return !!d.swapSymbol ? Style.png("tokens/%1".arg(d.swapSymbol)) : ""
case TransactionDelegate.Bridge:
return !!d.bridgeNetworkIcon ? Style.svg(d.bridgeNetworkIcon) : ""
default:
return ""
}
}
visible: !!subTitle
}
}
TransactionAddressTile {
width: parent.width
title: transactionHeader.transactionType === TransactionDelegate.Swap || transactionHeader.transactionType === TransactionDelegate.Bridge ?
qsTr("In") : qsTr("From")
addresses: root.isTransactionValid ? [root.transaction.from] : []
contactsStore: root.contactsStore
rootStore: WalletStores.RootStore
onButtonClicked: {
if (transactionHeader.transactionType === TransactionDelegate.Swap || transactionHeader.transactionType === TransactionDelegate.Bridge) {
addressMenu.openEthAddressMenu(this, addresses[0])
} else {
addressMenu.openSenderMenu(this, addresses[0])
}
}
}
TransactionAddressTile {
width: parent.width
title: qsTr("To")
addresses: root.isTransactionValid ? [root.transaction.to] : []
contactsStore: root.contactsStore
rootStore: WalletStores.RootStore
onButtonClicked: addressMenu.openReceiverMenu(this, addresses[0])
visible: transactionHeader.transactionType !== TransactionDelegate.Swap && transactionHeader.transactionType !== TransactionDelegate.Bridge && transactionHeader.transactionType !== TransactionDelegate.Destroy
}
TransactionDataTile {
width: parent.width
title: qsTr("Using")
buttonIconName: "external"
subTitle: "" // TODO fill protocol name for Swap and Bridge
asset.name: "" // TODO fill protocol icon for Bridge and Swap e.g. Style.svg("network/Network=Arbitrum")
onButtonClicked: {
// TODO handle
}
visible: !!subTitle
}
TransactionDataTile {
width: parent.width
title: qsTr("%1 Tx hash").arg(d.networkFullName)
subTitle: root.isTransactionValid ? root.transaction.txHash : ""
visible: !!subTitle
buttonIconName: "more"
onButtonClicked: addressMenu.openTxMenu(this, subTitle, d.networkShortName)
}
TransactionDataTile {
width: parent.width
title: qsTr("%1 Tx hash").arg(d.bridgeNetworkFullname)
subTitle: "" // TODO fill tx hash for Bridge
visible: !!subTitle
buttonIconName: "more"
onButtonClicked: addressMenu.openTxMenu(this, subTitle, d.bridgeNetworkShortName)
}
TransactionContractTile {
// Used for Bridge and Swap to display 'From' network Protocol contract address
address: "" // TODO fill protocol contract address for 'from' network for Bridge and Swap
symbol: "" // TODO fill protocol name for Bridge and Swap
networkName: d.networkFullName
shortNetworkName: d.networkShortName
visible: !!subTitle && (transactionHeader.transactionType === TransactionDelegate.Bridge || transactionHeader.transactionType === TransactionDelegate.Swap)
}
TransactionContractTile {
// Used to display contract address for any network
address: root.isTransactionValid ? transaction.contract : ""
symbol: root.isTransactionValid ? transaction.value.symbol.toUpperCase() : ""
networkName: d.networkFullName
shortNetworkName: d.networkShortName
}
TransactionContractTile {
// Used for Bridge to display 'To' network Protocol contract address
address: "" // TODO fill protocol contract address for 'to' network for Bridge
symbol: "" // TODO fill protocol name for Bridge
networkName: d.bridgeNetworkFullname
shortNetworkName: d.bridgeNetworkShortName
visible: !!subTitle && transactionHeader.transactionType === TransactionDelegate.Bridge
}
TransactionContractTile {
// Used for Bridge and Swap to display 'To' network token contract address
address: {
if (!root.isTransactionValid)
return ""
switch(transactionHeader.transactionType) {
case TransactionDelegate.Swap:
return transaction.contract
case TransactionDelegate.Bridge:
return "" // TODO fill swap token's contract address for 'to' network for Bridge
default:
return ""
}
}
symbol: {
if (!root.isTransactionValid)
return ""
switch(transactionHeader.transactionType) {
case TransactionDelegate.Swap:
return d.swapSymbol
case TransactionDelegate.Bridge:
return transaction.value.symbol.toUpperCase()
default:
return ""
}
}
networkName: d.bridgeNetworkFullname
shortNetworkName: d.bridgeNetworkShortName
}
}
Item {
width: parent.width
height: Style.current.bigPadding
}
DetailsPanel {
width: progressBlock.width
RowLayout {
width: parent.width
height: Math.max(implicitHeight, 85)
spacing: 0
TransactionDataTile {
id: multichainNetworksTile
Layout.fillHeight: true
Layout.fillWidth: true
title: qsTr("Networks")
visible: d.multichainNetworks.length > 0
Row {
anchors {
top: parent.top
topMargin: multichainNetworksTile.statusListItemTitleArea.height + multichainNetworksTile.topPadding
left: parent.left
leftMargin: multichainNetworksTile.leftPadding
}
spacing: -4
Repeater {
model: d.multichainNetworks
delegate: StatusRoundedImage {
width: 20
height: 20
visible: image.source !== ""
border.width: index === 0 ? 0 : 1
border.color: Theme.palette.white
image.source: Style.svg("tiny/" + modelData)
z: index + 1
}
}
}
}
TransactionDataTile {
Layout.fillHeight: true
Layout.fillWidth: true
title: qsTr("Network")
subTitle: d.networkFullName
asset.name: !!d.networkIcon ? Style.svg("%1".arg(d.networkIcon)) : ""
smallIcon: true
visible: transactionHeader.transactionType !== TransactionDelegate.Bridge
}
TransactionDataTile {
Layout.fillHeight: true
Layout.fillWidth: true
title: qsTr("Token format")
subTitle: root.isTransactionValid ? transaction.type.toUpperCase() : ""
visible: !!subTitle
}
TransactionDataTile {
Layout.fillHeight: true
Layout.fillWidth: true
title: qsTr("Nonce")
subTitle: root.isTransactionValid ? RootStore.hex2Dec(root.transaction.nonce) : ""
visible: !!subTitle
}
}
TransactionDataTile {
width: parent.width
title: qsTr("Input data")
subTitle: root.isTransactionValid ? root.transaction.input : ""
visible: !!subTitle
buttonIconName: "more"
onButtonClicked: addressMenu.openInputDataMenu(this, subTitle)
}
TransactionDataTile {
width: parent.width
title: !!d.networkFullName ? qsTr("Included in Block on %1").arg(d.networkFullName) : qsTr("Included on Block")
subTitle: d.blockNumber
tertiaryTitle: root.isTransactionValid ? LocaleUtils.formatDateTime(transaction.timestamp * 1000, Locale.LongFormat) : ""
visible: d.blockNumber > 0
}
TransactionDataTile {
width: parent.width
title: !!d.bridgeNetworkFullname ? qsTr("Included in Block on %1").arg(d.bridgeNetworkFullname) : qsTr("Included on Block")
subTitle: d.bridgeBlockNumber
tertiaryTitle: root.isTransactionValid ? LocaleUtils.formatDateTime(transaction.timestamp * 1000, Locale.LongFormat) : ""
visible: d.bridgeBlockNumber > 0
}
}
} }
StatusExpandableItem { StatusExpandableItem {
@ -140,76 +406,6 @@ Item {
expandableComponent: fees expandableComponent: fees
expanded: true expanded: true
} }
InformationTile {
maxWidth: parent.width
primaryText: qsTr("Data")
secondaryText: root.isTransactionValid ? root.transaction.input : ""
copy: true
onCopyClicked: RootStore.copyToClipboard(textToCopy)
}
}
}
Component {
id: transactionSummary
Column {
id: column
width: parent.width
spacing: 8
TransactionDelegate {
width: parent.width
modelData: transaction
transactionType: d.isIncoming ? TransactionDelegate.Receive : TransactionDelegate.Send
currentCurrency: RootStore.currentCurrency
cryptoValue: root.isTransactionValid ? transaction.value.amount: 0.0
fiatValue: root.isTransactionValid ? RootStore.getFiatValue(cryptoValue, symbol, currentCurrency): 0.0
networkIcon: root.isTransactionValid ? RootStore.getNetworkIcon(transaction.chainId) : ""
networkColor: root.isTransactionValid ? RootStore.getNetworkColor(transaction.chainId): ""
networkName: root.isTransactionValid ? RootStore.getNetworkShortName(transaction.chainId): ""
symbol: root.isTransactionValid ? transaction.symbol : ""
transferStatus: root.isTransactionValid ? RootStore.hex2Dec(transaction.txStatus): ""
timeStampText: root.isTransactionValid ? LocaleUtils.formatTime(transaction.timestamp * 1000, Locale.ShortFormat): ""
addressNameTo: root.isTransactionValid ? RootStore.getNameForSavedWalletAddress(transaction.to): ""
addressNameFrom: root.isTransactionValid ? RootStore.getNameForSavedWalletAddress(transaction.from): ""
formatCurrencyAmount: RootStore.formatCurrencyAmount
sensor.enabled: false
color: Theme.palette.statusListItem.backgroundColor
border.width: 1
border.color: Theme.palette.directColor8
}
Row {
spacing: 8
InformationTile {
maxWidth: parent.width
primaryText: qsTr("Time")
secondaryText: root.transaction !== undefined && !!root.transaction ? qsTr("%1 <font color=\"#939BA1\">on</font> %2").
arg(LocaleUtils.formatTime(transaction.timestamp * 1000, Locale.ShortFormat)).
arg(LocaleUtils.formatDate(transaction.timestamp * 1000, Locale.ShortFormat)): ""
}
InformationTile {
maxWidth: parent.width
primaryText: qsTr("Confirmations")
secondaryText: {
if(root.isTransactionValid)
return Math.abs(WalletStores.RootStore.getLatestBlockNumber(root.transaction.chainId) - RootStore.hex2Dec(root.transaction.blockNumber))
else
return ""
}
}
InformationTile {
maxWidth: parent.width
primaryText: qsTr("Nonce")
secondaryText: root.isTransactionValid ? RootStore.hex2Dec(root.transaction.nonce) : ""
}
InformationTile {
maxWidth: parent.width
primaryText: qsTr("TokenID")
secondaryText: root.isTransactionValid ? root.transaction.tokenID : ""
visible: root.isTransactionValid && d.isNFT
}
}
} }
} }
@ -258,4 +454,41 @@ Item {
contactsStore: root.contactsStore contactsStore: root.contactsStore
onOpenSendModal: (address) => root.sendModal.open(address) onOpenSendModal: (address) => root.sendModal.open(address)
} }
component DetailsPanel: Item {
width: parent.width
height: detailsColumn.childrenRect.height
default property alias content: detailsColumn.children
Rectangle {
id: tileBackground
anchors.fill: parent
radius: 8
border.width: 1
border.color: Style.current.separator
}
Column {
id: detailsColumn
anchors.fill: parent
anchors.margins: 1
spacing: 0
layer.enabled: true
layer.effect: OpacityMask {
maskSource: tileBackground
}
}
}
component TransactionContractTile: TransactionDataTile {
property string networkName: ""
property string symbol: ""
property string address: ""
property string shortNetworkName: ""
width: parent.width
title: qsTr("%1 %2 contract address").arg(networkName).arg(symbol)
subTitle: !!address && !/0x0+$/.test(address) ? address : ""
buttonIconName: "more"
visible: !!subTitle
onButtonClicked: addressMenu.openContractMenu(this, address, shortNetworkName, symbol)
}
} }

View File

@ -178,6 +178,7 @@ Item {
font.pixelSize: 15 font.pixelSize: 15
color: Theme.palette.directColor1 color: Theme.palette.directColor1
wrapMode: Text.WrapAnywhere wrapMode: Text.WrapAnywhere
enabled: false // Set to false to disable hover for rich text
text: { text: {
if(!!root.address == false) if(!!root.address == false)
return "" return ""

View File

@ -9,7 +9,7 @@ import StatusQ.Core.Theme 0.1
/*! /*!
\qmltype TransactionAddressTile \qmltype TransactionAddressTile
\inherits StatusListItem \inherits TransactionDataTile
\inqmlmodule shared.controls \inqmlmodule shared.controls
\since shared.controls 1.0 \since shared.controls 1.0
\brief It displays list of addresses for wallet activity. \brief It displays list of addresses for wallet activity.
@ -21,7 +21,6 @@ import StatusQ.Core.Theme 0.1
title: qsTr("From") title: qsTr("From")
width: parent.width width: parent.width
rootStore: WalletStores.RootStore rootStore: WalletStores.RootStore
roundedCornersBottom: false
addresses: [ addresses: [
"eth:arb:opt:0x4de3f6278C0DdFd3F29df9DcD979038F5c7bbc35", "eth:arb:opt:0x4de3f6278C0DdFd3F29df9DcD979038F5c7bbc35",
"0x4de3f6278C0DdFd3F29df9DcD979038F5c7bbc35", "0x4de3f6278C0DdFd3F29df9DcD979038F5c7bbc35",
@ -30,7 +29,7 @@ import StatusQ.Core.Theme 0.1
\endqml \endqml
*/ */
StatusListItem { TransactionDataTile {
id: root id: root
/*! /*!
@ -44,48 +43,11 @@ StatusListItem {
*/ */
property var rootStore property var rootStore
/*!
\qmlproperty int TransactionAddressTile::topPadding
This property holds spacing between top and content item in tile.
*/
property int topPadding: 12
/*!
\qmlproperty int TransactionAddressTile::bottomPadding
This property holds spacing between bottom and content item in tile.
*/
property int bottomPadding: 12
/* /internal Property hold reference to contacts store to refresh contact data on any change. */ /* /internal Property hold reference to contacts store to refresh contact data on any change. */
property var contactsStore property var contactsStore
signal showContextMenu() implicitHeight: transactionColumn.height + transactionColumn.spacing + root.topPadding + root.bottomPadding
buttonIconName: "more"
leftPadding: 12
rightPadding: 12
radius: 0
implicitHeight: transactionColumn.height + statusListItemTitleArea.height + root.topPadding + root.bottomPadding
statusListItemTitle.customColor: Theme.palette.directColor5
statusListItemTitleArea.anchors {
top: statusListItemTitleArea.parent.top
topMargin: root.topPadding
right: statusListItemTitleArea.parent.right
verticalCenter: undefined
}
components: [
StatusRoundButton {
id: button
width: 32
height: 32
icon.color: hovered ? Theme.palette.directColor1 : Theme.palette.baseColor1
icon.name: "more"
type: StatusRoundButton.Type.Quinary
radius: 8
visible: root.sensor.containsMouse
onClicked: root.showContextMenu()
}
]
Column { Column {
id: transactionColumn id: transactionColumn
@ -93,14 +55,12 @@ StatusListItem {
left: parent.left left: parent.left
leftMargin: root.leftPadding leftMargin: root.leftPadding
right: parent.right right: parent.right
rightMargin: button.width + root.rightPadding * 2 rightMargin: root.statusListItemComponentsSlot.width + root.rightPadding * 2
bottom: parent.bottom bottom: parent.bottom
bottomMargin: root.bottomPadding bottomMargin: root.bottomPadding
} }
height: childrenRect.height height: childrenRect.height
spacing: 4 spacing: 4
// Moving it under sensor, because Rich Text steals hovering
z: root.sensor.z - 1
Repeater { Repeater {
model: root.addresses model: root.addresses

View File

@ -0,0 +1,122 @@
import QtQuick 2.13
import StatusQ.Components 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Core 0.1
import StatusQ.Controls 0.1
import shared.panels 1.0
/*!
\qmltype TransactionDataTile
\inherits StatusListItem
\inqmlmodule shared.controls
\since shared.controls 1.0
\brief It displays data for wallet activity.
The \c TransactionDataTile can display wallet activity data as a tile.
To show button fill \l{buttonIcon} property.
\qml
TransactionDataTile {
width: parent.width
title: qsTr("From")
buttonIcon: "more"
}
\endqml
*/
StatusListItem {
id: root
/*!
\qmlproperty int TransactionDataTile::topPadding
This property holds spacing between top and content item in tile.
*/
property int topPadding: 12
/*!
\qmlproperty int TransactionDataTile::bottomPadding
This property holds spacing between bottom and content item in tile.
*/
property int bottomPadding: 12
/*!
\qmlproperty bool TransactionDataTile::smallIcon
This property holds information about icon state. Setting it to true will display small icon before subtitle.
Default value is false.
*/
property bool smallIcon: false
/*!
\qmlproperty string TransactionDataTile::buttonIconName
This property holds button icon source string.
To show button icon source must be filled
*/
property string buttonIconName
signal buttonClicked()
leftPadding: 12
rightPadding: 12
height: implicitHeight + bottomPadding
radius: 0
sensor.cursorShape: Qt.ArrowCursor
// Title
statusListItemTitle.customColor: Theme.palette.directColor5
statusListItemTitle.enabled: false
statusListItemTitleArea.anchors {
left: statusListItemTitleArea.parent.left
top: statusListItemTitleArea.parent.top
topMargin: topPadding
right: statusListItemTitleArea.parent.right
verticalCenter: undefined
}
// Subtitle
statusListItemTagsRowLayout.anchors.topMargin: 8
statusListItemTagsRowLayout.width: statusListItemTagsRowLayout.parent.width - (!!root.buttonIconName ? 36 : 0)
statusListItemSubTitle.customColor: Theme.palette.directColor1
// Tertiary title
statusListItemTertiaryTitle.anchors.topMargin: -statusListItemTertiaryTitle.height
statusListItemTertiaryTitle.horizontalAlignment: Qt.AlignRight
// Icon
asset.isImage: false
statusListItemTagsRowLayout.spacing: 8
subTitleBadgeComponent: !!asset.name ? iconComponent : null
statusListItemIcon.asset: StatusAssetSettings {}
Component {
id: iconComponent
StatusRoundIcon {
asset: StatusAssetSettings {
name: root.asset.name
color: "transparent"
width: root.smallIcon ? 20 : 36
height: root.smallIcon ? 20 : 36
bgWidth: width
bgHeight: height
}
}
}
components: Loader {
active: !!root.buttonIconName
sourceComponent: StatusRoundButton {
width: 32
height: 32
icon.color: hovered ? Theme.palette.directColor1 : Theme.palette.baseColor1
icon.name: root.buttonIconName
type: StatusRoundButton.Type.Quinary
radius: 8
visible: root.sensor.containsMouse
onClicked: root.buttonClicked()
}
}
Separator {
anchors.bottom: parent.bottom
}
}

View File

@ -55,7 +55,7 @@ StatusListItem {
property string symbol property string symbol
property string swapSymbol // TODO fill when swap data is implemented property string swapSymbol // TODO fill when swap data is implemented
property int transactionType property int transactionType
property int transactionStatus: transferStatus === 0 ? TransactionDelegate.TransactionStatus.Failed : TransactionDelegate.TransactionStatus.Finished property int transactionStatus: transferStatus === 0 ? TransactionDelegate.TransactionStatus.Pending : TransactionDelegate.TransactionStatus.Finished
property string currentCurrency property string currentCurrency
property int transferStatus property int transferStatus
property double cryptoValue property double cryptoValue

View File

@ -26,6 +26,7 @@ Timer 1.0 Timer.qml
TransactionDelegate 1.0 TransactionDelegate.qml TransactionDelegate 1.0 TransactionDelegate.qml
TransactionAddress 1.0 TransactionAddress.qml TransactionAddress 1.0 TransactionAddress.qml
TransactionAddressTile 1.0 TransactionAddressTile.qml TransactionAddressTile 1.0 TransactionAddressTile.qml
TransactionDataTile 1.0 TransactionDataTile.qml
TransactionFormGroup 1.0 TransactionFormGroup.qml TransactionFormGroup 1.0 TransactionFormGroup.qml
EmojiHash 1.0 EmojiHash.qml EmojiHash 1.0 EmojiHash.qml
InformationTile 1.0 InformationTile.qml InformationTile 1.0 InformationTile.qml