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 ./utils/qrcodegen
@ -69,7 +69,7 @@ QtObject:
proc hex2Dec*(self: Utils, value: string): string {.slot.} =
# somehow this value crashes the app
if value == "0x0":
if value.find(re("0x0+$")) >= 0:
return "0"
return $stint.fromHex(StUint[256], value)

View File

@ -281,22 +281,10 @@ ListModel {
title: "TokenItem"
section: "Components"
}
ListElement {
title: "TransactionDelegate"
section: "Components"
}
ListElement {
title: "CommunityPermissionsRow"
section: "Components"
}
ListElement {
title: "TransactionAddress"
section: "Components"
}
ListElement {
title: "TransactionAddressTile"
section: "Components"
}
ListElement {
title: "StatusImageCropPanel"
section: "Components"
@ -321,6 +309,22 @@ ListModel {
title: "PopupSizing"
section: "Research / Examples"
}
ListElement {
title: "TransactionDelegate"
section: "Wallet"
}
ListElement {
title: "TransactionAddress"
section: "Wallet"
}
ListElement {
title: "TransactionAddressTile"
section: "Wallet"
}
ListElement {
title: "TransactionDetailView"
section: "Wallet"
}
ListElement {
title: "WalletHeader"
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 var getSelectedTextWithFormationChars
property var gifColumnA
property var currentCurrency
property var currencyStore
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 {
id: sensor
z: 1 // Gives ability to hide siblings under the MouseArea
anchors.fill: parent
cursorShape: containsMouse ? Qt.PointingHandCursor : Qt.ArrowCursor
acceptedButtons: Qt.NoButton
@ -289,7 +288,7 @@ Rectangle {
objectName: "statusListItemSubTitle"
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
font.pixelSize: 15

View File

@ -45,11 +45,6 @@ ColumnLayout {
readonly property int progress: (Math.floor(Date.now() / 1000) - root.timeStamp) / 3600
}
Separator {
Layout.fillWidth: true
implicitHeight: 1
}
StatusTxProgressBar {
id: progressBar
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) {
const x = delegate.width - root.contentWidth / 2
const x = delegate.width - 40
const y = delegate.height / 2 + 20
root.popup(delegate, x, y)
}
@ -102,6 +102,7 @@ StatusMenu {
function openEthAddressMenu(delegate, address) {
d.selectedAddress = address
address = address.toLowerCase()
const contactPubKey = "" // TODO retrive contact public key or contact data directly from address
let contactData = Utils.getContactDetailsAsJson(contactPubKey)
let isWalletAccount = false
@ -135,6 +136,7 @@ StatusMenu {
function openTxMenu(delegate, address, chainShortName="") {
d.addressType = TransactionAddressMenu.AddressType.Tx
d.selectedAddress = address
chainShortName = chainShortName.toLowerCase()
if (chainShortName === root.arbiscanShortChainName) {
showOnArbiscanAction.enabled = true
} else if (chainShortName === root.optimismShortChainName) {
@ -149,6 +151,7 @@ StatusMenu {
d.addressType = TransactionAddressMenu.AddressType.Contract
d.contractName = name
d.selectedAddress = address
chainShortName = chainShortName.toLowerCase()
if (chainShortName === root.arbiscanShortChainName) {
showOnArbiscanAction.enabled = true
} else if (chainShortName === root.optimismShortChainName) {
@ -232,8 +235,6 @@ StatusMenu {
successText: {
switch(d.addressType) {
case TransactionAddressMenu.AddressType.Contract:
if (d.contractName.length > 0)
return qsTr("%1 contract address copied").arg(d.contractName)
return qsTr("Contract address copied")
case TransactionAddressMenu.AddressType.InputData:
return qsTr("Input data copied")
@ -250,8 +251,6 @@ StatusMenu {
defaultText: {
switch(d.addressType) {
case TransactionAddressMenu.AddressType.Contract:
if (d.contractName.length > 0)
return qsTr("Copy %1 contract address").arg(d.contractName)
return qsTr("Copy contract address")
case TransactionAddressMenu.AddressType.InputData:
return qsTr("Copy input data")

View File

@ -2,6 +2,7 @@ import QtQuick 2.13
import QtQuick.Layouts 1.13
import QtQuick.Controls 2.14
import QtQuick.Window 2.12
import QtGraphicalEffects 1.15
import StatusQ.Components 0.1
import StatusQ.Core.Theme 0.1
@ -10,6 +11,7 @@ import StatusQ.Controls 0.1
import StatusQ.Popups 0.1
import shared.controls 1.0
import shared.panels 1.0
import utils 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 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 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) {
return RootStore.getNameForSavedWalletAddress(address)
}
function retryTransaction() {
// TODO handle failed transaction retry
}
}
StatusScrollView {
id: scrollView
anchors.top: parent.top
anchors.left: parent.left
width: parent.width
height: parent.height
contentHeight: column.height
contentWidth: parent.width
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 {
id: transactionHeader
objectName: "transactionDetailHeader"
Column {
width: parent.width
leftPadding: 0
spacing: Style.current.bigPadding
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.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"
TransactionDelegate {
id: transactionHeader
objectName: "transactionDetailHeader"
width: parent.width
leftPadding: 0
onRetryClicked: {
// TODO handle failed transaction retry
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: 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 {
id: progressBlock
width: Math.min(513, root.width)
error: transactionHeader.transactionStatus === TransactionDelegate.TransactionStatus.Failed
isLayer1: RootStore.getNetworkLayer(root.transaction.chainId) == 1
confirmations: root.isTransactionValid ? Math.abs(WalletStores.RootStore.getLatestBlockNumber(root.transaction.chainId) - RootStore.hex2Dec(root.transaction.blockNumber)): 0
chainName: root.isTransactionValid ? RootStore.getNetworkFullName(root.transaction.chainId): ""
confirmations: root.isTransactionValid ? Math.abs(WalletStores.RootStore.getLatestBlockNumber(root.transaction.chainId) - d.blockNumber): 0
chainName: d.networkFullName
timeStamp: root.isTransactionValid ? transaction.timestamp: ""
}
SavedAddressesDelegate {
width: parent.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)
}
Separator {
width: progressBlock.width
}
StatusExpandableItem {
width: parent.width
anchors.horizontalCenter: parent.horizontalCenter
Column {
width: progressBlock.width
spacing: 0
type: StatusExpandableItem.Type.Tertiary
expandable: true
primaryText: qsTr("Transaction summary")
expandableComponent: transactionSummary
separatorVisible: false
expanded: true
StatusBaseText {
width: parent.width
font.pixelSize: 15
color: Theme.palette.directColor5
text: qsTr("Transaction summary")
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 {
@ -140,76 +406,6 @@ Item {
expandableComponent: fees
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
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
color: Theme.palette.directColor1
wrapMode: Text.WrapAnywhere
enabled: false // Set to false to disable hover for rich text
text: {
if(!!root.address == false)
return ""

View File

@ -9,7 +9,7 @@ import StatusQ.Core.Theme 0.1
/*!
\qmltype TransactionAddressTile
\inherits StatusListItem
\inherits TransactionDataTile
\inqmlmodule shared.controls
\since shared.controls 1.0
\brief It displays list of addresses for wallet activity.
@ -21,7 +21,6 @@ import StatusQ.Core.Theme 0.1
title: qsTr("From")
width: parent.width
rootStore: WalletStores.RootStore
roundedCornersBottom: false
addresses: [
"eth:arb:opt:0x4de3f6278C0DdFd3F29df9DcD979038F5c7bbc35",
"0x4de3f6278C0DdFd3F29df9DcD979038F5c7bbc35",
@ -30,7 +29,7 @@ import StatusQ.Core.Theme 0.1
\endqml
*/
StatusListItem {
TransactionDataTile {
id: root
/*!
@ -44,48 +43,11 @@ StatusListItem {
*/
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. */
property var contactsStore
signal showContextMenu()
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()
}
]
implicitHeight: transactionColumn.height + transactionColumn.spacing + root.topPadding + root.bottomPadding
buttonIconName: "more"
Column {
id: transactionColumn
@ -93,14 +55,12 @@ StatusListItem {
left: parent.left
leftMargin: root.leftPadding
right: parent.right
rightMargin: button.width + root.rightPadding * 2
rightMargin: root.statusListItemComponentsSlot.width + root.rightPadding * 2
bottom: parent.bottom
bottomMargin: root.bottomPadding
}
height: childrenRect.height
spacing: 4
// Moving it under sensor, because Rich Text steals hovering
z: root.sensor.z - 1
Repeater {
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 swapSymbol // TODO fill when swap data is implemented
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 int transferStatus
property double cryptoValue

View File

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