import QtQuick 2.15
import QtQuick.Layouts 1.15
import QtQml 2.15
import StatusQ.Components 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Core 0.1
import StatusQ.Controls 0.1
import utils 1.0
import shared 1.0
/*!
\qmltype TransactionDelegate
\inherits StatusListItem
\inqmlmodule shared.controls
\since shared.controls 1.0
\brief Delegate for transaction activity list
Delegate to display transaction activity data.
\qml
TransactionDelegate {
id: delegate
width: ListView.view.width
modelData: model
swapCryptoValue: 0.18
swapFiatValue: 340
swapSymbol: "SNT"
timeStampText: LocaleUtils.formatRelativeTimestamp(modelData.timestamp * 1000)
cryptoValue: 0.1234
fiatValue: 123123
currentCurrency: "USD"
networkName: "Optimism"
symbol: "ETH"
bridgeNetworkName: "Mainnet"
feeFiatValue: 10.34
feeCryptoValue: 0.013
transactionStatus: TransactionDelegate.Pending
transactionType: TransactionDelegate.Send
formatCurrencyAmount: RootStore.formatCurrencyAmount
loading: isModelDataValid && modelData.loadingTransaction
}
\endqml
Additional usages should be handled using states.
*/
StatusListItem {
id: root
signal retryClicked()
property var modelData
property string symbol
property string swapSymbol // TODO fill when swap data is implemented
property int transactionType
property int transactionStatus: transferStatus === 0 ? TransactionDelegate.TransactionStatus.Pending : TransactionDelegate.TransactionStatus.Finished
property string currentCurrency
property int transferStatus
property double cryptoValue
property double swapCryptoValue // TODO fill when swap data is implemented
property double fiatValue
property double swapFiatValue // TODO fill when swap data is implemented
property double feeCryptoValue // TODO fill when bridge data is implemented
property double feeFiatValue // TODO fill when bridge data is implemented
property string networkIcon
property string networkColor
property string networkName
property string bridgeNetworkName // TODO fill when bridge data is implemented
property string timeStampText
property string addressNameTo
property string addressNameFrom
property var formatCurrencyAmount: function() {}
readonly property bool isModelDataValid: modelData !== undefined && !!modelData
readonly property bool isNFT: isModelDataValid && modelData.isNFT
readonly property string transactionValue: {
if (!isModelDataValid)
return qsTr("N/A")
if (root.isNFT) {
return modelData.nftName ? modelData.nftName : "#" + modelData.tokenID
} else {
return root.formatCurrencyAmount(cryptoValue, symbol)
}
}
readonly property string swapTransactionValue: {
if (!isModelDataValid) {
return qsTr("N/A")
}
return root.formatCurrencyAmount(swapCryptoValue, swapSymbol)
}
readonly property string tokenImage: {
if (!isModelDataValid)
return ""
if (root.isNFT) {
return modelData.nftImageUrl ? modelData.nftImageUrl : ""
} else {
return root.symbol ? Style.png("tokens/%1".arg(root.symbol)) : ""
}
}
readonly property string swapTokenImage: {
if (!isModelDataValid)
return ""
return root.swapSymbol ? Style.png("tokens/%1".arg(root.swapSymbol)) : ""
}
readonly property string toAddress: !!addressNameTo ?
addressNameTo :
isModelDataValid ?
Utils.compactAddress(modelData.to, 4) :
""
readonly property string fromAddress: !!addressNameFrom ?
addressNameFrom :
isModelDataValid ?
Utils.compactAddress(modelData.from, 4) :
""
property StatusAssetSettings statusIconAsset: StatusAssetSettings {
width: 12
height: 12
bgWidth: width + 2
bgHeight: bgWidth
bgRadius: bgWidth / 2
bgColor: root.color
color: "transparent"
name: {
switch(root.transactionStatus) {
case TransactionDelegate.TransactionStatus.Pending:
return Style.svg("transaction/pending")
case TransactionDelegate.TransactionStatus.Verified:
return Style.svg("transaction/verified")
case TransactionDelegate.TransactionStatus.Finished:
return Style.svg("transaction/finished")
case TransactionDelegate.TransactionStatus.Failed:
return Style.svg("transaction/failed")
default:
return ""
}
}
}
property StatusAssetSettings tokenIconAsset: StatusAssetSettings {
width: 18
height: 18
bgWidth: width
bgHeight: height
bgColor: "transparent"
color: "transparent"
isImage: !loading
name: root.tokenImage
isLetterIdenticon: loading
}
enum TransactionType {
Send,
Receive,
Buy,
Sell,
Destroy,
Swap,
Bridge
}
enum TransactionStatus {
Pending,
Failed,
Verified,
Finished
}
QtObject {
id: d
property int loadingPixelSize: 13
property int datePixelSize: 12
property int titlePixelSize: 15
property int subtitlePixelSize: 13
}
rightPadding: 16
enabled: !loading
color: sensor.containsMouse ? Theme.palette.baseColor5 : Theme.palette.statusListItem.backgroundColor
statusListItemIcon.active: (loading || root.asset.name)
asset {
width: 24
height: 24
isImage: false
imgIsIdenticon: true
isLetterIdenticon: loading
name: {
switch(root.transactionType) {
case TransactionDelegate.TransactionType.Send:
return "receive"
case TransactionDelegate.TransactionType.Receive:
return "send"
case TransactionDelegate.TransactionType.Buy:
case TransactionDelegate.TransactionType.Sell:
return "token"
case TransactionDelegate.TransactionType.Destroy:
return "destroy"
case TransactionDelegate.TransactionType.Swap:
return "swap"
case TransactionDelegate.TransactionType.Bridge:
return "bridge"
default:
return ""
}
}
bgColor: "transparent"
color: Theme.palette.directColor1
bgBorderWidth: 1
bgBorderColor: Theme.palette.primaryColor3
}
sensor.children: [
StatusRoundIcon {
id: leftIconStatusIcon
visible: !root.loading
anchors {
right: root.statusListItemIcon.right
bottom: root.statusListItemIcon.bottom
}
asset: root.statusIconAsset
}
]
// Title
title: {
if (root.loading) {
return "dummmy"
} else if (!root.isModelDataValid) {
return ""
}
const isPending = root.transactionStatus === TransactionDelegate.TransactionStatus.Pending
const failed = root.transactionStatus === TransactionDelegate.TransactionStatus.Failed
switch(root.transactionType) {
case TransactionDelegate.TransactionType.Send:
return failed ? qsTr("Send failed") : (isPending ? qsTr("Sending") : qsTr("Sent"))
case TransactionDelegate.TransactionType.Receive:
return failed ? qsTr("Receive failed") : (isPending ? qsTr("Receiving") : qsTr("Received"))
case TransactionDelegate.TransactionType.Buy:
return failed ? qsTr("Buy failed") : (isPending ? qsTr("Buying") : qsTr("Bought"))
case TransactionDelegate.TransactionType.Sell:
return failed ? qsTr("Sell failed") : (isPending ? qsTr("Selling") : qsTr("Sold"))
case TransactionDelegate.TransactionType.Destroy:
return failed ? qsTr("Destroy failed") : (isPending ? qsTr("Destroying") : qsTr("Destroyed"))
case TransactionDelegate.TransactionType.Swap:
return failed ? qsTr("Swap failed") : (isPending ? qsTr("Swapping") : qsTr("Swapped"))
case TransactionDelegate.TransactionType.Bridge:
return failed ? qsTr("Bridge failed") : (isPending ? qsTr("Bridging") : qsTr("Bridged"))
default:
return ""
}
}
statusListItemTitleArea.anchors.rightMargin: root.rightPadding
statusListItemTitle.font.weight: Font.DemiBold
statusListItemTitle.font.pixelSize: root.loading ? d.loadingPixelSize : d.titlePixelSize
// title icons and date
statusListItemTitleIcons.sourceComponent: Row {
spacing: 8
Row {
visible: !root.loading
spacing: swapTokenImage.visible ? -tokenImage.width * 0.2 : 0
StatusRoundIcon {
id: tokenImage
anchors.verticalCenter: parent.verticalCenter
asset: root.tokenIconAsset
}
StatusRoundIcon {
id: swapTokenImage
visible: !root.isNFT && !!root.swapTokenImage && root.transactionType === TransactionDelegate.TransactionType.Swap
anchors.verticalCenter: parent.verticalCenter
asset: StatusAssetSettings {
width: root.tokenIconAsset.width
height: root.tokenIconAsset.height
bgWidth: width + 2
bgHeight: height + 2
bgRadius: bgWidth / 2
bgColor: root.color
isImage:root.tokenIconAsset.isImage
color: root.tokenIconAsset.color
name: root.swapTokenImage
isLetterIdenticon: root.tokenIconAsset.isLetterIdenticon
}
}
}
StatusTextWithLoadingState {
anchors.verticalCenter: parent.verticalCenter
text: root.loading ? root.title : root.timeStampText
verticalAlignment: Qt.AlignVCenter
font.pixelSize: root.loading ? d.loadingPixelSize : d.datePixelSize
visible: !!text
loading: root.loading
customColor: Theme.palette.baseColor1
}
}
// subtitle
subTitle: {
if (!root.isModelDataValid) {
return ""
}
switch(root.transactionType) {
case TransactionDelegate.TransactionType.Receive:
return qsTr("%1 from %2 via %3").arg(transactionValue).arg(fromAddress).arg(networkName)
case TransactionDelegate.TransactionType.Buy:
case TransactionDelegate.TransactionType.Sell:
return qsTr("%1 on %2 via %3").arg(transactionValue).arg(toAddress).arg(networkName)
case TransactionDelegate.TransactionType.Destroy:
return qsTr("%1 at %2 via %3").arg(transactionValue).arg(toAddress).arg(networkName)
case TransactionDelegate.TransactionType.Swap:
return qsTr("%1 to %2 via %3").arg(transactionValue).arg(swapTransactionValue).arg(networkName)
case TransactionDelegate.TransactionType.Bridge:
return qsTr("%1 from %2 to %3").arg(transactionValue).arg(bridgeNetworkName).arg(networkName)
default:
return qsTr("%1 to %2 via %3").arg(transactionValue).arg(toAddress).arg(networkName)
}
}
statusListItemSubTitle.maximumLoadingStateWidth: 300
statusListItemSubTitle.customColor: Theme.palette.directColor1
statusListItemSubTitle.font.pixelSize: root.loading ? d.loadingPixelSize : d.subtitlePixelSize
statusListItemTagsRowLayout.anchors.topMargin: 4 // Spacing between title row nad subtitle row
// Right side components
components: [
Loader {
active: !headerStatusLoader.active
visible: active
sourceComponent: ColumnLayout {
StatusTextWithLoadingState {
id: cryptoValueText
text: {
if (root.loading) {
return "dummy text"
} else if (!root.isModelDataValid || root.isNFT) {
return ""
}
switch(root.transactionType) {
case TransactionDelegate.TransactionType.Send:
case TransactionDelegate.TransactionType.Sell:
return "-" + root.transactionValue
case TransactionDelegate.TransactionType.Buy:
case TransactionDelegate.TransactionType.Receive:
return "+" + root.transactionValue
case TransactionDelegate.TransactionType.Swap:
return "-%2 / +%5"
.arg(Theme.palette.directColor1)
.arg(root.transactionValue)
.arg(Theme.palette.baseColor1)
.arg(Theme.palette.successColor1)
.arg(root.swapTransactionValue)
case TransactionDelegate.TransactionType.Bridge:
return "-" + root.formatCurrencyAmount(feeCryptoValue, root.symbol)
default:
return ""
}
}
horizontalAlignment: Qt.AlignRight
Layout.alignment: Qt.AlignRight
font.pixelSize: root.loading ? d.loadingPixelSize : 13
customColor: {
switch(root.transactionType) {
case TransactionDelegate.TransactionType.Receive:
case TransactionDelegate.TransactionType.Buy:
case TransactionDelegate.TransactionType.Swap:
return Theme.palette.successColor1
default:
return Theme.palette.directColor1
}
}
loading: root.loading
}
StatusTextWithLoadingState {
id: fiatValueText
Layout.alignment: Qt.AlignRight
horizontalAlignment: Qt.AlignRight
text: {
if (root.loading) {
return "dummy text"
} else if (!root.isModelDataValid || root.isNFT) {
return ""
}
switch(root.transactionType) {
case TransactionDelegate.TransactionType.Send:
case TransactionDelegate.TransactionType.Sell:
case TransactionDelegate.TransactionType.Buy:
return "-" + root.formatCurrencyAmount(root.fiatValue, root.currentCurrency)
case TransactionDelegate.TransactionType.Receive:
return "+" + root.formatCurrencyAmount(root.fiatValue, root.currentCurrency)
case TransactionDelegate.TransactionType.Swap:
return "-%1 / +%2".arg(root.formatCurrencyAmount(root.fiatValue, root.currentCurrency))
.arg(root.formatCurrencyAmount(root.swapFiatValue, root.currentCurrency))
case TransactionDelegate.TransactionType.Bridge:
return "-" + root.formatCurrencyAmount(root.feeFiatValue, root.currentCurrency)
default:
return ""
}
}
font.pixelSize: root.loading ? d.loadingPixelSize : 12
customColor: Theme.palette.baseColor1
loading: root.loading
}
}
},
Loader {
id: headerStatusLoader
active: false
visible: active
sourceComponent: Rectangle {
id: statusRect
width: transactionTypeIcon.width + (retryButton.visible ? retryButton.width + 5 : 0)
height: transactionTypeIcon.height
anchors.verticalCenter: parent.verticalCenter
color: "transparent"
radius: 100
border {
width: retryButton.visible ? 1 : 0
color: root.asset.bgBorderColor
}
StatusButton {
id: retryButton
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: 10
radius: height / 2
height: parent.height * 0.7
verticalPadding: 0
horizontalPadding: radius
textFillWidth: true
text: qsTr("Retry")
size: StatusButton.Small
type: StatusButton.Primary
visible: !root.loading && root.transactionStatus === TransactionDelegate.Failed
onClicked: root.retryClicked()
}
StatusSmartIdenticon {
id: transactionTypeIcon
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
enabled: false
asset: root.asset
active: !!root.asset.name
loading: root.loading
name: root.title
}
StatusRoundIcon {
visible: !root.loading
anchors {
right: transactionTypeIcon.right
bottom: transactionTypeIcon.bottom
}
asset: root.statusIconAsset
}
}
}
]
states: [
State {
name: "header"
PropertyChanges {
target: headerStatusLoader
active: true
}
PropertyChanges {
target: leftIconStatusIcon
visible: false
}
PropertyChanges {
target: root.statusListItemIcon
active: false
}
PropertyChanges {
target: root.asset
bgBorderWidth: root.transactionStatus === TransactionDelegate.Failed ? 0 : 1
width: 34
height: 34
bgWidth: 56
bgHeight: 56
}
PropertyChanges {
target: root.statusIconAsset
width: 17
height: 17
}
PropertyChanges {
target: root.tokenIconAsset
width: 20
height: 20
}
PropertyChanges {
target: d
titlePixelSize: 17
datePixelSize: 13
subtitlePixelSize: 15
loadingPixelSize: 14
}
}
]
}