diff --git a/src/app/modules/main/wallet_section/accounts/model.nim b/src/app/modules/main/wallet_section/accounts/model.nim
index 79d97877d7..4be211a2eb 100644
--- a/src/app/modules/main/wallet_section/accounts/model.nim
+++ b/src/app/modules/main/wallet_section/accounts/model.nim
@@ -97,3 +97,9 @@ QtObject:
result = newQVariant(item.keyUid())
of ModelRole.AssetsLoading:
result = newQVariant(item.assetsLoading())
+
+ proc getNameByAddress*(self: Model, address: string): string =
+ for item in self.items:
+ if(item.address() == address):
+ return item.name()
+ return ""
\ No newline at end of file
diff --git a/src/app/modules/main/wallet_section/accounts/view.nim b/src/app/modules/main/wallet_section/accounts/view.nim
index ccab7249d5..51d6b58d39 100644
--- a/src/app/modules/main/wallet_section/accounts/view.nim
+++ b/src/app/modules/main/wallet_section/accounts/view.nim
@@ -44,4 +44,7 @@ QtObject:
self.delegate.deleteAccount(address)
proc updateAccount(self: View, address: string, accountName: string, color: string, emoji: string) {.slot.} =
- self.delegate.updateAccount(address, accountName, color, emoji)
\ No newline at end of file
+ self.delegate.updateAccount(address, accountName, color, emoji)
+
+ proc getNameByAddress(self: View, address: string): string {.slot.}=
+ return self.accounts.getNameByAddress(address)
\ No newline at end of file
diff --git a/storybook/PagesModel.qml b/storybook/PagesModel.qml
index 8b5c351d33..96bca0d1f5 100644
--- a/storybook/PagesModel.qml
+++ b/storybook/PagesModel.qml
@@ -265,6 +265,10 @@ ListModel {
title: "TokenItem"
section: "Components"
}
+ ListElement {
+ title: "TransactionDelegate"
+ section: "Components"
+ }
ListElement {
title: "CommunityPermissionsRow"
section: "Components"
diff --git a/storybook/pages/TransactionDelegatePage.qml b/storybook/pages/TransactionDelegatePage.qml
new file mode 100644
index 0000000000..f374a3d15b
--- /dev/null
+++ b/storybook/pages/TransactionDelegatePage.qml
@@ -0,0 +1,149 @@
+import QtQuick 2.15
+import QtQuick.Controls 2.15
+import QtQuick.Layouts 1.15
+
+import StatusQ.Core 0.1
+import Storybook 1.0
+import utils 1.0
+
+import shared.controls 1.0
+
+SplitView {
+ id: root
+
+ readonly property QtObject mockupModelData: QtObject {
+ property int timestamp: Date.now() / 1000
+ property int txStatus: 0
+ property string from: "0xfB8131c260749c7835a08ccBdb64728De432858E"
+ property string to: "0x3fb81384583b3910BB14Cc72582E8e8a56E83ae9"
+ property bool isNFT: false
+ property string tokenID: "4981676894159712808201908443964193325271219637660871887967796332739046670337"
+ property string nftName: "Happy Meow"
+ property string nftImageUrl: Style.png("collectibles/HappyMeow")
+ }
+
+ SplitView {
+ orientation: Qt.Vertical
+ SplitView.fillWidth: true
+ Item {
+ SplitView.fillWidth: true
+ SplitView.fillHeight: true
+
+ Rectangle {
+ anchors.fill: column
+ anchors.margins: -1
+ border.color: "lightgray"
+ }
+
+ ColumnLayout {
+ id: column
+
+ anchors.centerIn: parent
+
+ width: 600
+
+ TransactionDelegate {
+ id: delegate
+ Layout.fillWidth: true
+ modelData: root.mockupModelData
+ 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: function(amount, symbol, options = null, locale = null) {
+ const currencyAmount = {
+ amount: amount,
+ symbol: symbol,
+ displayDecimals: 8,
+ stripTrailingZeroes: true
+ }
+ return LocaleUtils.currencyAmountToLocaleString(currencyAmount, options)
+ }
+ }
+ }
+ }
+
+ LogsAndControlsPanel {
+ SplitView.minimumHeight: 100
+ SplitView.preferredHeight: 200
+
+ SplitView.fillWidth: true
+ }
+ }
+
+ Pane {
+ SplitView.minimumWidth: 300
+ SplitView.preferredWidth: 300
+
+ ColumnLayout {
+ CheckBox {
+ text: "Is loading"
+ checked: delegate.loading
+ onToggled: delegate.loading = checked
+ }
+ CheckBox {
+ text: "Is activity details header"
+ readonly property string headerState: "header"
+ checked: delegate.state === headerState
+ onToggled: delegate.state = checked ? headerState : ""
+ }
+
+ CheckBox {
+ text: "Is NFT"
+ checked: delegate.isNFT
+ onToggled: root.mockupModelData.isNFT = checked
+ }
+
+ Label {
+ Layout.topMargin: 10
+ Layout.fillWidth: true
+ text: "Transaction type:"
+ }
+
+ ComboBox {
+ Layout.fillWidth: true
+ textRole: "name"
+ valueRole: "type"
+ model: ListModel {
+ ListElement { name: "Sent"; type: TransactionDelegate.Send }
+ ListElement { name: "Receive"; type: TransactionDelegate.Receive }
+ ListElement { name: "Buy"; type: TransactionDelegate.Buy }
+ ListElement { name: "Sell"; type: TransactionDelegate.Sell }
+ ListElement { name: "Destroy"; type: TransactionDelegate.Destroy }
+ ListElement { name: "Swap"; type: TransactionDelegate.Swap }
+ ListElement { name: "Bridge"; type: TransactionDelegate.Bridge }
+ }
+ onActivated: delegate.transactionType = model.get(currentIndex).type
+ }
+
+ Label {
+ Layout.topMargin: 10
+ Layout.fillWidth: true
+ text: "Transaction status:"
+ }
+
+ ComboBox {
+ Layout.fillWidth: true
+ textRole: "name"
+ valueRole: "type"
+ model: ListModel {
+ ListElement { name: "Pending"; status: TransactionDelegate.Pending }
+ ListElement { name: "Failed"; status: TransactionDelegate.Failed }
+ ListElement { name: "Verified"; status: TransactionDelegate.Verified }
+ ListElement { name: "Finished"; status: TransactionDelegate.Finished }
+ }
+ onActivated: delegate.transactionStatus = model.get(currentIndex).status
+ }
+ }
+ }
+}
diff --git a/ui/StatusQ/src/StatusQ/Components/StatusListItem.qml b/ui/StatusQ/src/StatusQ/Components/StatusListItem.qml
index ca8d0cecf3..3a4567d1bd 100644
--- a/ui/StatusQ/src/StatusQ/Components/StatusListItem.qml
+++ b/ui/StatusQ/src/StatusQ/Components/StatusListItem.qml
@@ -81,6 +81,7 @@ Rectangle {
property alias statusListItemLabel: statusListItemLabel
property alias subTitleBadgeComponent: subTitleBadgeLoader.sourceComponent
property alias errorIcon: errorIcon
+ property alias statusListItemTagsRowLayout: statusListItemSubtitleTagsRow
signal clicked(string itemId, var mouse)
signal titleClicked(string titleId)
diff --git a/ui/StatusQ/src/StatusQ/Components/StatusRoundIcon.qml b/ui/StatusQ/src/StatusQ/Components/StatusRoundIcon.qml
index 3c494e3a08..443bb8f68d 100644
--- a/ui/StatusQ/src/StatusQ/Components/StatusRoundIcon.qml
+++ b/ui/StatusQ/src/StatusQ/Components/StatusRoundIcon.qml
@@ -20,7 +20,8 @@ Rectangle {
implicitWidth: asset.bgWidth
implicitHeight: asset.bgHeight
radius: asset.bgRadius
-
+ border.width: asset.bgBorderWidth
+ border.color: asset.bgBorderColor
StatusIcon {
id: statusIcon
diff --git a/ui/StatusQ/src/StatusQ/Components/StatusSmartIdenticon.qml b/ui/StatusQ/src/StatusQ/Components/StatusSmartIdenticon.qml
index 3199caaf08..d6f825a535 100644
--- a/ui/StatusQ/src/StatusQ/Components/StatusSmartIdenticon.qml
+++ b/ui/StatusQ/src/StatusQ/Components/StatusSmartIdenticon.qml
@@ -80,6 +80,8 @@ Loader {
asset.name: root.asset.name
asset.rotation: root.asset.rotation
asset.color: root.asset.color
+ asset.bgBorderWidth: root.asset.bgBorderWidth
+ asset.bgBorderColor: root.asset.bgBorderColor
signal clicked(var mouse)
diff --git a/ui/StatusQ/src/StatusQ/Controls/StatusTextWithLoadingState.qml b/ui/StatusQ/src/StatusQ/Controls/StatusTextWithLoadingState.qml
index 6f53c37206..265942ffff 100644
--- a/ui/StatusQ/src/StatusQ/Controls/StatusTextWithLoadingState.qml
+++ b/ui/StatusQ/src/StatusQ/Controls/StatusTextWithLoadingState.qml
@@ -35,12 +35,19 @@ StatusBaseText {
*/
property bool loading: false
/*!
- \qmlproperty bool StatusTextWithLoadingState::customColor
+ \qmlproperty color StatusTextWithLoadingState::customColor
This property sets the user defined color for the text and handles
transparency in loading state.
*/
property color customColor: Theme.palette.directColor1
+ /*!
+ \qmlproperty int StatusTextWithLoadingState::maximumLoadingStateWidth
+ This property sets maximum width of loading component.
+ The default value is 140.
+ */
+ property int maximumLoadingStateWidth: 140
+
color: loading ? "transparent" : customColor
Loader {
@@ -51,7 +58,7 @@ StatusBaseText {
anchors.centerIn: parent
radius: textMetrics.font.pixelSize === 15 ? 4 : 8
height: textMetrics.tightBoundingRect.height
- width: Math.min(root.width, 140)
+ width: Math.min(root.width, root.maximumLoadingStateWidth)
}
}
diff --git a/ui/StatusQ/src/StatusQ/Core/LocaleUtils.qml b/ui/StatusQ/src/StatusQ/Core/LocaleUtils.qml
index 0112cf7d9d..67771a7d8c 100644
--- a/ui/StatusQ/src/StatusQ/Core/LocaleUtils.qml
+++ b/ui/StatusQ/src/StatusQ/Core/LocaleUtils.qml
@@ -301,10 +301,13 @@ QtObject {
// otherwise
var fullFormatString = d.fixupTimeFormatString(loc.dateTimeFormat(Locale.ShortFormat))
- if (now.getFullYear() === value.getFullYear())
- fullFormatString = fullFormatString.replace(/y/g, "") // strip year part, if current year -> "31 December 09:41"
- else
+ if (now.getFullYear() === value.getFullYear()) {
+ // strip year part, if current year -> "31 December 09:41"
+ // It remove preceding dot or space
+ fullFormatString = fullFormatString.replace(/([.\s])?\b(y+)\b/g, "")
+ } else if (!fullFormatString.includes("yyyy")) {
fullFormatString = fullFormatString.replace("yy", "yyyy") // different year -> "31 December 2022 09:41"
+ }
return value.toLocaleString(loc, fullFormatString)
}
diff --git a/ui/StatusQ/src/StatusQ/Core/StatusAssetSettings.qml b/ui/StatusQ/src/StatusQ/Core/StatusAssetSettings.qml
index 16e6e6fb62..24e3a896b4 100644
--- a/ui/StatusQ/src/StatusQ/Core/StatusAssetSettings.qml
+++ b/ui/StatusQ/src/StatusQ/Core/StatusAssetSettings.qml
@@ -30,6 +30,7 @@ QtObject {
property int bgRadius
property color bgColor: "transparent"
property color bgBorderColor: "transparent"
+ property int bgBorderWidth: 0
//image
property bool isImage: false
diff --git a/ui/StatusQ/src/StatusQ/Core/StatusIcon.qml b/ui/StatusQ/src/StatusQ/Core/StatusIcon.qml
index c36c65a980..ef1430b15e 100644
--- a/ui/StatusQ/src/StatusQ/Core/StatusIcon.qml
+++ b/ui/StatusQ/src/StatusQ/Core/StatusIcon.qml
@@ -13,7 +13,7 @@ Image {
fillMode: Image.PreserveAspectFit
onIconChanged: {
- if(icon.startsWith("data:image/") || icon.startsWith("https://")) {
+ if(icon.startsWith("data:image/") || icon.startsWith("https://") || icon.startsWith("qrc:/") || icon.startsWith("file:/")) {
//raw image data
source = icon
objectName = "custom-icon"
diff --git a/ui/StatusQ/src/assets.qrc b/ui/StatusQ/src/assets.qrc
index 3a371aa6f6..edff1b7ded 100644
--- a/ui/StatusQ/src/assets.qrc
+++ b/ui/StatusQ/src/assets.qrc
@@ -294,6 +294,7 @@
assets/img/icons/time.svg
assets/img/icons/token-sale.svg
assets/img/icons/token.svg
+ assets/img/icons/destroy.svg
assets/img/icons/touch-id.svg
assets/img/icons/travel-and-places.svg
assets/img/icons/tributeToTalk.svg
diff --git a/ui/StatusQ/src/assets/img/icons/destroy.svg b/ui/StatusQ/src/assets/img/icons/destroy.svg
new file mode 100644
index 0000000000..9bbd5e9917
--- /dev/null
+++ b/ui/StatusQ/src/assets/img/icons/destroy.svg
@@ -0,0 +1,10 @@
+
diff --git a/ui/app/AppLayouts/Wallet/stores/RootStore.qml b/ui/app/AppLayouts/Wallet/stores/RootStore.qml
index bebe2e766d..ac7a3e87f5 100644
--- a/ui/app/AppLayouts/Wallet/stores/RootStore.qml
+++ b/ui/app/AppLayouts/Wallet/stores/RootStore.qml
@@ -169,6 +169,14 @@ QtObject {
return walletSectionSavedAddresses.getNameByAddress(address)
}
+ function getNameForAddress(address) {
+ let name = getNameForSavedWalletAddress(address)
+ if (name.length === 0) {
+ name = walletSectionAccounts.getNameByAddress(address)
+ }
+ return name
+ }
+
function createOrUpdateSavedAddress(name, address, favourite, chainShortNames, ens) {
return walletSectionSavedAddresses.createOrUpdateSavedAddress(name, address, favourite, chainShortNames, ens)
}
diff --git a/ui/app/AppLayouts/Wallet/views/TransactionDetailView.qml b/ui/app/AppLayouts/Wallet/views/TransactionDetailView.qml
index 9265b72804..aa89f24bb2 100644
--- a/ui/app/AppLayouts/Wallet/views/TransactionDetailView.qml
+++ b/ui/app/AppLayouts/Wallet/views/TransactionDetailView.qml
@@ -28,7 +28,7 @@ Item {
QtObject {
id: d
- readonly property bool isIncoming: root.isTransactionValid ? root.transaction.to === root.overview.mixedcaseAddress : false
+ readonly property bool isIncoming: root.isTransactionValid ? root.transaction.to.toLowerCase() === root.overview.mixedcaseAddress.toLowerCase() : false
readonly property bool isNFT: root.isTransactionValid ? root.transaction.isNFT : false
readonly property string savedAddressNameTo: root.isTransactionValid ? d.getNameForSavedWalletAddress(transaction.to) : ""
readonly property string savedAddressNameFrom: root.isTransactionValid ? d.getNameForSavedWalletAddress(transaction.from): ""
@@ -59,26 +59,31 @@ Item {
spacing: Style.current.bigPadding
TransactionDelegate {
+ id: transactionHeader
objectName: "transactionDetailHeader"
width: parent.width
modelData: transaction
- isIncoming: d.isIncoming
+ 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): ""
+ networkName: root.isTransactionValid ? RootStore.getNetworkFullName(transaction.chainId): ""
symbol: root.isTransactionValid ? transaction.symbol : ""
transferStatus: root.isTransactionValid ? RootStore.hex2Dec(transaction.txStatus): ""
- shortTimeStamp: root.isTransactionValid ? LocaleUtils.formatTime(transaction.timestamp * 1000, Locale.ShortFormat): ""
- savedAddressNameTo: root.isTransactionValid ? RootStore.getNameForSavedWalletAddress(transaction.to): ""
- savedAddressNameFrom: root.isTransactionValid ? RootStore.getNameForSavedWalletAddress(transaction.from): ""
- isSummary: false
+ 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.statusListItem.backgroundColor
- state: "big"
+ state: "header"
+
+ onRetryClicked: {
+ // TODO handle failed transaction retry
+ }
}
SavedAddressesDelegate {
@@ -144,7 +149,7 @@ Item {
TransactionDelegate {
width: parent.width
modelData: transaction
- isIncoming: d.isIncoming
+ 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
@@ -153,10 +158,10 @@ Item {
networkName: root.isTransactionValid ? RootStore.getNetworkShortName(transaction.chainId): ""
symbol: root.isTransactionValid ? transaction.symbol : ""
transferStatus: root.isTransactionValid ? RootStore.hex2Dec(transaction.txStatus): ""
- shortTimeStamp: root.isTransactionValid ? LocaleUtils.formatTime(transaction.timestamp * 1000, Locale.ShortFormat): ""
- savedAddressNameTo: root.isTransactionValid ? RootStore.getNameForSavedWalletAddress(transaction.to): ""
- savedAddressNameFrom: root.isTransactionValid ? RootStore.getNameForSavedWalletAddress(transaction.from): ""
- isSummary: false
+ 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
diff --git a/ui/imports/assets/icons/transaction/failed.svg b/ui/imports/assets/icons/transaction/failed.svg
new file mode 100644
index 0000000000..95144d2f55
--- /dev/null
+++ b/ui/imports/assets/icons/transaction/failed.svg
@@ -0,0 +1,4 @@
+
diff --git a/ui/imports/assets/icons/transaction/finished.svg b/ui/imports/assets/icons/transaction/finished.svg
new file mode 100644
index 0000000000..2a0c9698ca
--- /dev/null
+++ b/ui/imports/assets/icons/transaction/finished.svg
@@ -0,0 +1,8 @@
+
diff --git a/ui/imports/assets/icons/transaction/pending.svg b/ui/imports/assets/icons/transaction/pending.svg
new file mode 100644
index 0000000000..ae6bea5352
--- /dev/null
+++ b/ui/imports/assets/icons/transaction/pending.svg
@@ -0,0 +1,6 @@
+
diff --git a/ui/imports/assets/icons/transaction/verified.svg b/ui/imports/assets/icons/transaction/verified.svg
new file mode 100644
index 0000000000..946358c3a3
--- /dev/null
+++ b/ui/imports/assets/icons/transaction/verified.svg
@@ -0,0 +1,8 @@
+
diff --git a/ui/imports/shared/controls/TransactionDelegate.qml b/ui/imports/shared/controls/TransactionDelegate.qml
index 53172690b4..4aff708ce7 100644
--- a/ui/imports/shared/controls/TransactionDelegate.qml
+++ b/ui/imports/shared/controls/TransactionDelegate.qml
@@ -9,187 +9,502 @@ import StatusQ.Controls 0.1
import utils 1.0
import shared 1.0
-import shared.stores 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
- property alias cryptoValueText: cryptoValueText
- property alias fiatValueText: fiatValueText
+ signal retryClicked()
property var modelData
property string symbol
- property bool isIncoming
+ 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 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 shortTimeStamp
- property string savedAddressNameTo
- property string savedAddressNameFrom
- property bool isSummary: false
+ 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 name: isModelDataValid ?
- root.isNFT ?
- modelData.nftName ?
- modelData.nftName :
- "#" + modelData.tokenID :
- root.isSummary ? root.symbol : RootStore.formatCurrencyAmount(cryptoValue, symbol) :
- "N/A"
+ 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 image: isModelDataValid ?
- root.isNFT ?
- modelData.nftImageUrl ?
- modelData.nftImageUrl :
- "" :
- root.symbol ?
- Style.png("tokens/%1".arg(root.symbol)) :
- "" :
- ""
+ 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 toAddress: !!savedAddressNameTo ?
- savedAddressNameTo :
+ 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: !!savedAddressNameFrom ?
- savedAddressNameFrom :
+ readonly property string fromAddress: !!addressNameFrom ?
+ addressNameFrom :
isModelDataValid ?
Utils.compactAddress(modelData.from, 4) :
""
- state: "normal"
- enabled: !loading
- asset.isImage: !loading
- asset.name: root.image
- asset.isLetterIdenticon: loading
- title: root.isModelDataValid ?
- isIncoming ?
- isSummary ?
- qsTr("Receive %1").arg(root.name) :
- qsTr("Received %1 from %2").arg(root.name).arg(root.fromAddress):
- isSummary ?
- qsTr("Send %1 to %2").arg(root.name).arg(root.toAddress) :
- qsTr("Sent %1 to %2").arg(root.name).arg(root.toAddress) :
- ""
- subTitle: shortTimeStamp
- inlineTagModel: 1
- inlineTagDelegate: InformationTag {
- tagPrimaryLabel.text: networkName
- tagPrimaryLabel.color: networkColor
- image.source: !!networkIcon ? Style.svg("tiny/%1".arg(networkIcon)) : ""
- customBackground: Component {
- Rectangle {
- color: "transparent"
- border.width: 1
- border.color: Theme.palette.baseColor2
- radius: 36
+
+ 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 ""
}
}
- width: 51
- height: root.loading ? textMetrics.tightBoundingRect.height : 24
- rightComponent: transferStatus === Constants.TransactionStatus.Success ? completedIcon : loadingIndicator
- loading: root.loading
}
- TextMetrics {
- id: textMetrics
- font: statusListItemSubTitle.font
- text: statusListItemSubTitle.text
+
+ property StatusAssetSettings tokenIconAsset: StatusAssetSettings {
+ width: 18
+ height: 18
+ bgWidth: width
+ bgHeight: height
+ bgColor: "transparent"
+ color: "transparent"
+ isImage: !loading
+ name: root.tokenImage
+ isLetterIdenticon: loading
}
- components: [
- ColumnLayout {
- visible: !root.isNFT
- Row {
- Layout.alignment: Qt.AlignRight
- spacing: 4
- StatusIcon {
- color: isIncoming ? Theme.palette.successColor1 : Theme.palette.dangerColor1
- icon: "arrow-up"
- rotation: isIncoming ? 135 : 45
- height: 18
- visible: !root.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.black
+ 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(toAddress).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: RootStore.formatCurrencyAmount(cryptoValue, root.symbol)
- Binding on width {
- when: root.loading
- value: 111
- restoreMode: Binding.RestoreBindingOrValue
+ 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
+ }
}
- customColor: 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
+ }
}
- StatusTextWithLoadingState {
- id: fiatValueText
- Layout.alignment: Qt.AlignRight
- text: RootStore.formatCurrencyAmount(fiatValue, root.currentCurrency)
- font.pixelSize: 15
- 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
+ }
}
}
]
- Component {
- id: loadingIndicator
- StatusLoadingIndicator {
- height: 10
- width: 10
- }
- }
-
- Component {
- id: completedIcon
- StatusIcon {
- visible: icon !== ""
- icon: "checkmark"
- color: Theme.palette.baseColor1
- width: 10
- height: 10
- }
- }
-
states: [
State {
- name: "normal"
+ name: "header"
PropertyChanges {
- target: asset
- width: 40
- height: 40
+ target: headerStatusLoader
+ active: true
}
PropertyChanges {
- target: statusListItemTitle
- font.weight: Font.Medium
- font.pixelSize: 15
+ target: leftIconStatusIcon
+ visible: false
}
PropertyChanges {
- target: cryptoValueText
- font.pixelSize: 15
- }
- },
- State {
- name: "big"
- PropertyChanges {
- target: asset
- width: 50
- height: 50
+ target: root.statusListItemIcon
+ active: false
}
PropertyChanges {
- target: statusListItemTitle
- font.weight: Font.Bold
- font.pixelSize: 17
+ target: root.asset
+ bgBorderWidth: root.transactionStatus === TransactionDelegate.Failed ? 0 : 1
+ width: 34
+ height: 34
+ bgWidth: 56
+ bgHeight: 56
}
PropertyChanges {
- target: cryptoValueText
- font.pixelSize: 17
+ 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
}
}
]
diff --git a/ui/imports/shared/stores/RootStore.qml b/ui/imports/shared/stores/RootStore.qml
index f0cac70692..8a02669965 100644
--- a/ui/imports/shared/stores/RootStore.qml
+++ b/ui/imports/shared/stores/RootStore.qml
@@ -54,6 +54,10 @@ QtObject {
return networksModule.all.getNetworkShortName(chainId)
}
+ function getNetworkFullName(chainId) {
+ return networksModule.all.getNetworkFullName(chainId)
+ }
+
function getNetworkIconUrl(symbol) {
return networksModule.all.getNetworkIconUrl(symbol)
}
diff --git a/ui/imports/shared/views/HistoryView.qml b/ui/imports/shared/views/HistoryView.qml
index 863a9741cf..d95caadb9c 100644
--- a/ui/imports/shared/views/HistoryView.qml
+++ b/ui/imports/shared/views/HistoryView.qml
@@ -17,6 +17,8 @@ import "../popups"
import "../stores"
import "../controls"
+import AppLayouts.Wallet.stores 1.0 as WalletStores
+
ColumnLayout {
id: root
@@ -193,19 +195,19 @@ ColumnLayout {
TransactionDelegate {
width: ListView.view.width
modelData: model
- isIncoming: isModelDataValid ? modelData.to === root.overview.mixedcaseAddress: false
+ transactionType: isModelDataValid && modelData.to.toLowerCase() === root.overview.mixedcaseAddress.toLowerCase() ? TransactionDelegate.Receive : TransactionDelegate.Send
currentCurrency: RootStore.currentCurrency
cryptoValue: isModelDataValid ? modelData.value.amount : 0.0
fiatValue: isModelDataValid ? RootStore.getFiatValue(cryptoValue, symbol, currentCurrency): 0.0
networkIcon: isModelDataValid ? RootStore.getNetworkIcon(modelData.chainId) : ""
networkColor: isModelDataValid ? RootStore.getNetworkColor(modelData.chainId) : ""
- networkName: isModelDataValid ? RootStore.getNetworkShortName(modelData.chainId) : ""
+ networkName: isModelDataValid ? RootStore.getNetworkFullName(modelData.chainId) : ""
symbol: isModelDataValid && !!modelData.symbol ? modelData.symbol : ""
transferStatus: isModelDataValid ? RootStore.hex2Dec(modelData.txStatus) : ""
- shortTimeStamp: isModelDataValid ? LocaleUtils.formatTime(modelData.timestamp * 1000, Locale.ShortFormat) : ""
- savedAddressNameTo: isModelDataValid ? RootStore.getNameForSavedWalletAddress(modelData.to) : ""
- savedAddressNameFrom: isModelDataValid ? RootStore.getNameForSavedWalletAddress(modelData.from) : ""
- isSummary: true
+ timeStampText: isModelDataValid ? LocaleUtils.formatRelativeTimestamp(modelData.timestamp * 1000) : ""
+ addressNameTo: isModelDataValid ? WalletStores.RootStore.getNameForAddress(modelData.to) : ""
+ addressNameFrom: isModelDataValid ? WalletStores.RootStore.getNameForAddress(modelData.from) : ""
+ formatCurrencyAmount: RootStore.formatCurrencyAmount
onClicked: launchTransactionDetail(modelData)
loading: isModelDataValid ? modelData.loadingTransaction : false