chore(Wallet): AssetView replaced with the refactored version

Closes: #14704
Closes: #14939
This commit is contained in:
Michał Cieślak 2024-06-14 20:20:07 +02:00 committed by Michał
parent 38914df0f0
commit d700a1ad53
13 changed files with 583 additions and 1430 deletions

View File

@ -1,271 +0,0 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import shared.views 1.0
import utils 1.0
import Storybook 1.0
import Qt.labs.settings 1.1
SplitView {
id: root
ListModel {
id: assetsModel
function format(amount, symbol) {
return `${amount.toLocaleString(Qt.locale())} ${symbol}`
}
Component.onCompleted: {
const data = [
{
key: "key_ETH",
symbol: "ETH",
name: "Ether",
icon: Constants.tokenIcon("ETH", false),
balance: 10.0,
balanceText: format(10.0, "ETH"),
error: "",
marketDetailsAvailable: true,
marketDetailsLoading: true,
marketPrice: 0,
marketChangePct24hour: 0,
communityId: "",
communityName: "",
communityIcon: Qt.resolvedUrl(""),
position: 2,
canBeHidden: false
},
{
key: "key_SNT",
symbol: "SNT",
name: "Status",
icon: Constants.tokenIcon("SNT", false),
balance: 20023.0,
balanceText: format(20023.0, "SNT"),
error: "",
marketDetailsAvailable: true,
marketDetailsLoading: false,
marketPrice: 50.23,
marketChangePct24hour: 12,
communityId: "",
communityName: "",
communityIcon: Qt.resolvedUrl(""),
position: 1,
canBeHidden: true
},
{
key: "key_MCT",
symbol: "MCT",
name: "My custom token",
icon: Constants.tokenIcon("ZRX", false),
balance: 102.4,
balanceText: format(102.4, "MCT"),
error: "",
marketDetailsAvailable: false,
marketDetailsLoading: false,
marketPrice: 0,
marketChangePct24hour: 0,
communityId: "34",
communityName: "Crypto Kitties",
communityIcon: Constants.tokenIcon("DAI", false),
position: 4,
canBeHidden: true
},
{
key: "key_DAI",
symbol: "DAI",
name: "Dai",
icon: Constants.tokenIcon("DAI", false),
balance: 123.24,
balanceText: format(123.24, "DAI"),
error: "",
marketDetailsAvailable: true,
marketDetailsLoading: false,
marketPrice: 23.23,
marketChangePct24hour: 2.3,
communityId: "",
communityName: "",
communityIcon: Qt.resolvedUrl(""),
position: 3,
canBeHidden: true
},
{
key: "key_USDT",
symbol: "USDT",
name: "USDT",
icon: Constants.tokenIcon("USDT", false),
balance: 15.24,
balanceText: format(15.24, "USDT"),
error: "",
marketDetailsAvailable: true,
marketDetailsLoading: false,
marketPrice: 0.99,
marketChangePct24hour: 0,
communityId: "",
communityName: "",
communityIcon: Qt.resolvedUrl(""),
position: 5,
canBeHidden: true
},
{
key: "key_TBT",
symbol: "TBT",
name: "The best token",
icon: Constants.tokenIcon("UNI", false),
balance: 102,
balanceText: format(102, "TBT"),
error: "Pocket Network (POKT) & Infura are currently both "
+ "unavailable for %1. %1 balances are as of %2."
.arg("TBT").arg("10/06/2024"),
marketDetailsAvailable: false,
marketDetailsLoading: false,
marketPrice: 0,
marketChangePct24hour: 0,
communityId: "3423",
communityName: "Best tokens",
communityIcon: Constants.tokenIcon("UNI", false),
position: 6,
canBeHidden: true
}
]
append(data)
}
}
SplitView {
SplitView.fillWidth: true
SplitView.fillHeight: true
orientation: Qt.Vertical
Pane {
SplitView.fillWidth: true
SplitView.fillHeight: true
AssetsViewNew {
anchors.fill: parent
loading: loadingCheckBox.checked
sorterVisible: sorterVisibleCheckBox.checked
customOrderAvailable: customOrderAvailableCheckBox.checked
sendEnabled: sendEnabledCheckBox.checked
swapEnabled: swapEnabledCheckBox.checked
swapVisible: swapVisibleCheckBox.checked
balanceError: balanceErrorCheckBox.checked
? "Balance error!" : ""
marketDataError: marketDataErrorCheckBox.checked
? "Market data error!" : ""
model: assetsModel
onSendRequested: logs.logEvent(`send requested: ${key}`)
onReceiveRequested: logs.logEvent(`receive requested: ${key}`)
onSwapRequested: logs.logEvent(`swap requested: ${key}`)
onAssetClicked: logs.logEvent(`asset clicked: ${key}`)
onHideRequested: logs.logEvent(`hide requested: ${key}`)
onHideCommunityAssets: logs.logEvent(`hide community assets requested: ${communityKey}`)
onManageTokensRequested: logs.logEvent(`manage tokens requested`)
}
}
Logs {
id: logs
}
LogsView {
clip: true
SplitView.preferredHeight: 150
SplitView.fillWidth: true
logText: logs.logText
}
}
Pane {
SplitView.preferredWidth: 300
ColumnLayout {
CheckBox {
id: loadingCheckBox
text: "loading"
}
CheckBox {
id: sorterVisibleCheckBox
text: "sorter visible"
}
CheckBox {
id: customOrderAvailableCheckBox
text: "custom order available"
}
CheckBox {
id: sendEnabledCheckBox
text: "send enabled"
}
CheckBox {
id: swapEnabledCheckBox
text: "swap enabled"
}
CheckBox {
id: swapVisibleCheckBox
text: "swap visible"
}
CheckBox {
id: balanceErrorCheckBox
text: "balance error"
}
CheckBox {
id: marketDataErrorCheckBox
text: "market data error"
}
}
}
Settings {
property alias loading: loadingCheckBox.checked
property alias filterVisible: sorterVisibleCheckBox.checked
property alias customOrderAvailable: customOrderAvailableCheckBox.checked
property alias sendEnabled: sendEnabledCheckBox.checked
property alias swapEnabled: swapEnabledCheckBox.checked
property alias swapVisible: swapVisibleCheckBox.checked
property alias balanceError: balanceErrorCheckBox.checked
property alias marketDataError: marketDataErrorCheckBox.checked
}
}
// category: Views

View File

@ -1,248 +1,271 @@
import QtQuick 2.15 import QtQuick 2.15
import QtQuick.Layouts 1.15
import QtQuick.Controls 2.15 import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import Qt.labs.settings 1.0 import shared.views 1.0
import SortFilterProxyModel 0.2
import StatusQ 0.1
import StatusQ.Models 0.1
import StatusQ.Core 0.1
import StatusQ.Core.Utils 0.1 as SQUtils
import mainui 1.0
import utils 1.0 import utils 1.0
import shared.controls 1.0
import shared.views 1.0
import shared.stores 1.0
import Storybook 1.0 import Storybook 1.0
import Models 1.0
import AppLayouts.Wallet.views 1.0
import AppLayouts.Wallet.stores 1.0
import Qt.labs.settings 1.1
SplitView { SplitView {
id: root id: root
Logs { id: logs } ListModel {
id: assetsModel
orientation: Qt.Horizontal function format(amount, symbol) {
return `${amount.toLocaleString(Qt.locale())} ${symbol}`
QtObject {
id: d
readonly property string networksChainsCurrentlySelected: {
let supportedNwChains = []
for (let i = 0; i< networksRepeater.count; i++) {
if (networksRepeater.itemAt(i).checked && networksRepeater.itemAt(i).visible)
supportedNwChains.push(networksRepeater.itemAt(i).chainID)
}
return supportedNwChains.join(":")
} }
readonly property string addressesSelected: { Component.onCompleted: {
let supportedAddresses = [] const data = [
for (let i = 0; i< accountsRepeater.count; i++) { {
if (accountsRepeater.itemAt(i).checked && accountsRepeater.itemAt(i).visible) key: "key_ETH",
supportedAddresses.push(accountsRepeater.itemAt(i).address) symbol: "ETH",
} name: "Ether",
return supportedAddresses.join(":") icon: Constants.tokenIcon("ETH", false),
} balance: 10.0,
balanceText: format(10.0, "ETH"),
error: "",
readonly property var currencyStore: CurrenciesStore {} marketDetailsAvailable: true,
marketDetailsLoading: true,
marketPrice: 0,
marketChangePct24hour: 0,
property WalletAssetsStore walletAssetStore: WalletAssetsStore { communityId: "",
assetsWithFilteredBalances: d.assetsWithFilteredBalances communityName: "",
assetsController: assetsView.controller communityIcon: Qt.resolvedUrl(""),
}
// Added this here simply because the network and address filtering wont work in Storybook applied in AssetsView position: 2,
readonly property SubmodelProxyModel assetsWithFilteredBalances: SubmodelProxyModel { canBeHidden: false
sourceModel: d.walletAssetStore.groupedAccountsAssetsModel },
submodelRoleName: "balances" {
delegateModel: SortFilterProxyModel { key: "key_SNT",
sourceModel: submodel symbol: "SNT",
filters: FastExpressionFilter { name: "Status",
expression: { icon: Constants.tokenIcon("SNT", false),
d.networksChainsCurrentlySelected balance: 20023.0,
return d.networksChainsCurrentlySelected.split(":").includes(model.chainId+"") balanceText: format(20023.0, "SNT"),
} error: "",
expectedRoles: ["chainId"]
marketDetailsAvailable: true,
marketDetailsLoading: false,
marketPrice: 50.23,
marketChangePct24hour: 12,
communityId: "",
communityName: "",
communityIcon: Qt.resolvedUrl(""),
position: 1,
canBeHidden: true
},
{
key: "key_MCT",
symbol: "MCT",
name: "My custom token",
icon: Constants.tokenIcon("ZRX", false),
balance: 102.4,
balanceText: format(102.4, "MCT"),
error: "",
marketDetailsAvailable: false,
marketDetailsLoading: false,
marketPrice: 0,
marketChangePct24hour: 0,
communityId: "34",
communityName: "Crypto Kitties",
communityIcon: Constants.tokenIcon("DAI", false),
position: 4,
canBeHidden: true
},
{
key: "key_DAI",
symbol: "DAI",
name: "Dai",
icon: Constants.tokenIcon("DAI", false),
balance: 123.24,
balanceText: format(123.24, "DAI"),
error: "",
marketDetailsAvailable: true,
marketDetailsLoading: false,
marketPrice: 23.23,
marketChangePct24hour: 2.3,
communityId: "",
communityName: "",
communityIcon: Qt.resolvedUrl(""),
position: 3,
canBeHidden: true
},
{
key: "key_USDT",
symbol: "USDT",
name: "USDT",
icon: Constants.tokenIcon("USDT", false),
balance: 15.24,
balanceText: format(15.24, "USDT"),
error: "",
marketDetailsAvailable: true,
marketDetailsLoading: false,
marketPrice: 0.99,
marketChangePct24hour: 0,
communityId: "",
communityName: "",
communityIcon: Qt.resolvedUrl(""),
position: 5,
canBeHidden: true
},
{
key: "key_TBT",
symbol: "TBT",
name: "The best token",
icon: Constants.tokenIcon("UNI", false),
balance: 102,
balanceText: format(102, "TBT"),
error: "Pocket Network (POKT) & Infura are currently both "
+ "unavailable for %1. %1 balances are as of %2."
.arg("TBT").arg("10/06/2024"),
marketDetailsAvailable: false,
marketDetailsLoading: false,
marketPrice: 0,
marketChangePct24hour: 0,
communityId: "3423",
communityName: "Best tokens",
communityIcon: Constants.tokenIcon("UNI", false),
position: 6,
canBeHidden: true
} }
} ]
append(data)
} }
} }
Popups { SplitView {
popupParent: root
rootStore: QtObject {}
communityTokensStore: QtObject {}
walletAssetsStore: d.walletAssetStore
}
StackLayout {
id: stack
SplitView.fillWidth: true SplitView.fillWidth: true
SplitView.fillHeight: true SplitView.fillHeight: true
currentIndex: 0 orientation: Qt.Vertical
AssetsView { Pane {
id: assetsView SplitView.fillWidth: true
Layout.fillHeight: true SplitView.fillHeight: true
Layout.fillWidth: true
areAssetsLoading: loadingCheckbox.checked
controller: ManageTokensController {
sourceModel: d.walletAssetStore.groupedAccountAssetsModel
settingsKey: "WalletAssets"
serializeAsCollectibles: false
onRequestSaveSettings: (jsonData) => { AssetsView {
savingStarted() anchors.fill: parent
settingsStore.setValue(settingsKey, jsonData)
savingFinished()
}
onRequestLoadSettings: {
loadingStarted()
const jsonData = settingsStore.value(settingsKey, null)
loadingFinished(jsonData)
}
onRequestClearSettings: {
settingsStore.setValue(settingsKey, null)
}
onTokenHidden: (symbol, name) => Global.displayToastMessage( loading: loadingCheckBox.checked
qsTr("%1 (%2) was successfully hidden").arg(name).arg(symbol), "", "checkmark-circle", sorterVisible: sorterVisibleCheckBox.checked
false, Constants.ephemeralNotificationType.success, "") customOrderAvailable: customOrderAvailableCheckBox.checked
onCommunityTokenGroupHidden: (communityName) => Global.displayToastMessage(
qsTr("%1 community assets successfully hidden").arg(communityName), "", "checkmark-circle",
false, Constants.ephemeralNotificationType.success, "")
onTokenShown: (symbol, name) => Global.displayToastMessage(qsTr("%1 is now visible").arg(name), "", "checkmark-circle",
false, Constants.ephemeralNotificationType.success, "")
onCommunityTokenGroupShown: (communityName) => Global.displayToastMessage(
qsTr("%1 community assets are now visible").arg(communityName), "", "checkmark-circle",
false, Constants.ephemeralNotificationType.success, "")
}
filterVisible: ctrlFilterVisible.checked
currencyStore: d.currencyStore
tokensStore: TokensStore {
displayAssetsBelowBalance: ctrlBalanceThresholdSwitch.checked
getDisplayAssetsBelowBalanceThresholdDisplayAmount: () => ctrlBalanceThreshold.value
}
networkFilters: d.networksChainsCurrentlySelected
addressFilters: d.addressesSelected
onAssetClicked: {
stack.currentIndex = 1
detailsView.token = token
logs.logEvent("onAssetClicked", ["token"], [token.symbol, token.communityId])
}
onSendRequested: logs.logEvent("onSendRequested", ["symbol"], arguments)
onReceiveRequested: logs.logEvent("onReceiveRequested", ["symbol"], arguments)
onSwitchToCommunityRequested: logs.logEvent("onSwitchToCommunityRequested", ["communityId"], arguments)
onManageTokensRequested: logs.logEvent("onManageTokensRequested")
Settings { sendEnabled: sendEnabledCheckBox.checked
id: settingsStore swapEnabled: swapEnabledCheckBox.checked
category: "ManageTokens-" + assetsView.controller.settingsKey swapVisible: swapVisibleCheckBox.checked
balanceError: balanceErrorCheckBox.checked
? "Balance error!" : ""
marketDataError: marketDataErrorCheckBox.checked
? "Market data error!" : ""
model: assetsModel
onSendRequested: logs.logEvent(`send requested: ${key}`)
onReceiveRequested: logs.logEvent(`receive requested: ${key}`)
onSwapRequested: logs.logEvent(`swap requested: ${key}`)
onAssetClicked: logs.logEvent(`asset clicked: ${key}`)
onHideRequested: logs.logEvent(`hide requested: ${key}`)
onHideCommunityAssets: logs.logEvent(`hide community assets requested: ${communityKey}`)
onManageTokensRequested: logs.logEvent(`manage tokens requested`)
} }
} }
ColumnLayout { Logs {
Layout.fillHeight: true id: logs
Layout.fillWidth: true }
Button {
text: "go back" LogsView {
onClicked: stack.currentIndex = 0 clip: true
}
AssetsDetailView { SplitView.preferredHeight: 150
id: detailsView SplitView.fillWidth: true
Layout.fillHeight: true
Layout.fillWidth: true logText: logs.logText
currencyStore: d.currencyStore
allNetworksModel: NetworksModel.flatNetworks
networkFilters: d.networksChainsCurrentlySelected
}
} }
} }
Pane { Pane {
SplitView.preferredWidth: 250 SplitView.preferredWidth: 300
ColumnLayout { ColumnLayout {
spacing: 12
anchors.fill: parent
Switch {
id: ctrlFilterVisible
text: "Filter visible"
checked: true
}
CheckBox { CheckBox {
id: loadingCheckbox id: loadingCheckBox
checked: false
text: "loading" text: "loading"
} }
CheckBox {
id: sorterVisibleCheckBox
ColumnLayout { text: "sorter visible"
Layout.fillWidth: true
Text {
text: "select supported network(s)"
}
Repeater {
id: networksRepeater
model: NetworksModel.flatNetworks
delegate: CheckBox {
readonly property int chainID: chainId
width: parent.width
text: chainName
visible: isTest
checked: true
onToggled: {
isEnabled = checked
}
}
}
} }
CheckBox {
id: customOrderAvailableCheckBox
ColumnLayout { text: "custom order available"
Layout.fillWidth: true
Text {
text: "select account(s)"
}
Repeater {
id: accountsRepeater
model: WalletAccountsModel {}
delegate: CheckBox {
readonly property string address: model.address
checked: true
visible: index<2
width: parent.width
text: name
}
}
} }
CheckBox {
id: sendEnabledCheckBox
ColumnLayout { text: "send enabled"
Layout.fillWidth: true }
Switch { CheckBox {
id: ctrlBalanceThresholdSwitch id: swapEnabledCheckBox
text: qsTr("Currency balance threshold")
checked: false text: "swap enabled"
} }
CurrencyAmountInput { CheckBox {
id: ctrlBalanceThreshold id: swapVisibleCheckBox
value: 10.1
} text: "swap visible"
}
CheckBox {
id: balanceErrorCheckBox
text: "balance error"
}
CheckBox {
id: marketDataErrorCheckBox
text: "market data error"
} }
} }
} }
Settings {
property alias loading: loadingCheckBox.checked
property alias filterVisible: sorterVisibleCheckBox.checked
property alias customOrderAvailable: customOrderAvailableCheckBox.checked
property alias sendEnabled: sendEnabledCheckBox.checked
property alias swapEnabled: swapEnabledCheckBox.checked
property alias swapVisible: swapVisibleCheckBox.checked
property alias balanceError: balanceErrorCheckBox.checked
property alias marketDataError: marketDataErrorCheckBox.checked
}
} }
// category: Views // category: Views
// https://www.figma.com/file/FkFClTCYKf83RJWoifWgoX/Wallet-v2?type=design&node-id=17159-67977&mode=design&t=s5EXsh6Vi4nTNYUh-0
// https://www.figma.com/file/FkFClTCYKf83RJWoifWgoX/Wallet-v2?type=design&node-id=17171-285559&mode=design&t=s5EXsh6Vi4nTNYUh-0

View File

@ -14,7 +14,7 @@ SplitView {
SplitView.fillWidth: true SplitView.fillWidth: true
SplitView.fillHeight: true SplitView.fillHeight: true
TokenDelegateNew { TokenDelegate {
anchors.centerIn: parent anchors.centerIn: parent
name: nameTextFiled.text name: nameTextFiled.text

View File

@ -206,12 +206,14 @@ Dialog {
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
currentIndex: walletTabBar.currentIndex currentIndex: walletTabBar.currentIndex
AssetsView { // Disable because the refactored version of AssetView requires specific
id: assetsTab // integration but the old version was not working properly neither.
controller: popup.assetsStore.assetsController //AssetsView {
currencyStore: popup.currencyStore // id: assetsTab
tokensStore: popup.tokensStore // controller: popup.assetsStore.assetsController
} // currencyStore: popup.currencyStore
// tokensStore: popup.tokensStore
//}
HistoryView { HistoryView {
id: historyTab id: historyTab
overview: WalletStore.dappBrowserAccount overview: WalletStore.dappBrowserAccount

View File

@ -143,8 +143,7 @@ RightTabBaseView {
Component { Component {
id: assetsView id: assetsView
AssetsViewNew { AssetsView {
AssetsViewAdaptor { AssetsViewAdaptor {
id: assetsViewAdaptor id: assetsViewAdaptor

View File

@ -1,27 +1,16 @@
import QtQuick 2.15
import StatusQ.Core.Theme 0.1
import utils 1.0 import utils 1.0
TokenDelegate { TokenDelegate {
id: root
title: Constants.dummyText title: Constants.dummyText
subTitle: Constants.dummyText subTitle: Constants.dummyText
asset.name: Constants.dummyText asset.name: Constants.dummyText
currencyBalance.text: Constants.dummyText
currencyBalance.loading: true
change24HourPercentage.text: Constants.dummyText
change24HourPercentage.loading: true
currencyPrice.text: Constants.dummyText
currencyPrice.loading: true
statusListItemSubTitle.loading: true statusListItemSubTitle.loading: true
statusListItemTitle.loading: true statusListItemTitle.loading: true
statusListItemIcon.loading: true statusListItemIcon.loading: true
textColor: Theme.palette.baseColor1 marketDetailsAvailable: true
marketDetailsLoading: true
enabled: false enabled: false
} }

View File

@ -1,16 +0,0 @@
import utils 1.0
TokenDelegateNew {
title: Constants.dummyText
subTitle: Constants.dummyText
asset.name: Constants.dummyText
statusListItemSubTitle.loading: true
statusListItemTitle.loading: true
statusListItemIcon.loading: true
marketDetailsAvailable: true
marketDetailsLoading: true
enabled: false
}

View File

@ -7,67 +7,63 @@ import StatusQ.Core 0.1
import StatusQ.Controls 0.1 import StatusQ.Controls 0.1
import AppLayouts.Wallet.controls 1.0 import AppLayouts.Wallet.controls 1.0
import utils 1.0 import utils 1.0
StatusListItem { StatusListItem {
id: root id: root
// expected roles: name, symbol, currencyPrice, changePct24hour, communityId, communityName, communityImage property string name
property url icon
property string balance
property alias currencyBalance: currencyBalance property bool marketDetailsAvailable: false
property alias change24HourPercentage: change24HourPercentageText property string marketBalance
property alias currencyPrice: currencyPrice property bool marketDetailsLoading: false
property string marketCurrencyPrice
property real marketChangePct24hour
property string communityId
property string communityName
property url communityIcon
property string currentCurrencySymbol
property string textColor: {
if (!modelData || !modelData.marketDetails) {
return Theme.palette.successColor1
}
return modelData.marketDetails.changePct24hour === undefined ?
Theme.palette.baseColor1 :
modelData.marketDetails.changePct24hour === 0 ?
Theme.palette.baseColor1 :
modelData.marketDetails.changePct24hour < 0 ?
Theme.palette.dangerColor1 :
Theme.palette.successColor1
}
property string errorTooltipText_1 property string errorTooltipText_1
property string errorTooltipText_2 property string errorTooltipText_2
readonly property bool isCommunityToken: !!modelData && !!modelData.communityId signal communityClicked(string communityId)
readonly property string symbolUrl: {
if (!modelData)
return ""
if (modelData.image)
return modelData.image
if (modelData.symbol)
return Constants.tokenIcon(modelData.symbol, false)
return ""
}
readonly property string upDownTriangle: {
if (!modelData || !modelData.marketDetails)
return ""
if (modelData.marketDetails.changePct24hour < 0)
return "▾"
if (modelData.marketDetails.changePct24hour > 0)
return "▴"
return ""
}
readonly property bool isUndefined: modelData && !modelData.marketDetailsLoading && title === ""
signal switchToCommunityRequested(string communityId) QtObject {
id: d
title: modelData ? modelData.name : "" readonly property bool isCommunityToken: !!root.communityId
subTitle: LocaleUtils.currencyAmountToLocaleString(modelData.enabledNetworkBalance)
asset.name: symbolUrl readonly property string textColor: {
if (!root.marketDetailsAvailable)
return Theme.palette.successColor1
if (root.marketChangePct24hour === 0)
return Theme.palette.baseColor1
return root.marketChangePct24hour < 0
? Theme.palette.dangerColor1
: Theme.palette.successColor1
}
readonly property string upDownTriangle: {
if (root.marketChangePct24hour === 0)
return ""
return root.marketChangePct24hour < 0 ? "▾" : "▴"
}
}
title: root.name
subTitle: root.balance
asset.name: root.icon
asset.isImage: true asset.isImage: true
asset.width: 32 asset.width: 32
asset.height: 32 asset.height: 32
errorIcon.tooltip.maxWidth: 300 errorIcon.tooltip.maxWidth: 300
height: isUndefined ? 0 : implicitHeight height: implicitHeight
visible: !isUndefined
statusListItemTitleIcons.sourceComponent: StatusFlatRoundButton { statusListItemTitleIcons.sourceComponent: StatusFlatRoundButton {
width: 14 width: 14
@ -94,26 +90,32 @@ StatusListItem {
icon.color: Theme.palette.dangerColor1 icon.color: Theme.palette.dangerColor1
tooltip.text: root.errorTooltipText_2 tooltip.text: root.errorTooltipText_2
tooltip.maxWidth: 200 tooltip.maxWidth: 200
visible: !!tooltip.text visible: root.marketDetailsAvailable && !!tooltip.text
} }
StatusTextWithLoadingState { StatusTextWithLoadingState {
id: currencyBalance id: currencyBalance
anchors.right: parent.right anchors.right: parent.right
loading: modelData && modelData.marketDetailsLoading visible: !errorIcon.visible && root.marketDetailsAvailable
visible: !errorIcon.visible && !root.isCommunityToken
loading: root.marketDetailsLoading
text: loading ? Constants.dummyText : root.marketBalance
} }
Row { Row {
anchors.right: parent.right anchors.right: parent.right
spacing: 6 spacing: 6
visible: !errorIcon.visible && !root.isCommunityToken visible: !errorIcon.visible && root.marketDetailsAvailable
StatusTextWithLoadingState { StatusTextWithLoadingState {
id: change24HourPercentageText id: change24HourPercentageText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
customColor: root.textColor customColor: d.textColor
font.pixelSize: 13 font.pixelSize: 13
loading: modelData && modelData.marketDetailsLoading loading: root.marketDetailsLoading
text: modelData && modelData.marketDetails && modelData.marketDetails.changePct24hour !== undefined ? "%1 %2%".arg(root.upDownTriangle).arg(LocaleUtils.numberToLocaleString(modelData.marketDetails.changePct24hour, 2))
: "---" text: qsTr("%1 %2%", "[up/down/none character depending on value sign] [localized percentage value]%")
.arg(d.upDownTriangle).arg(LocaleUtils.numberToLocaleString(root.marketChangePct24hour, 2))
} }
Rectangle { Rectangle {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@ -123,39 +125,45 @@ StatusListItem {
} }
StatusTextWithLoadingState { StatusTextWithLoadingState {
id: currencyPrice id: currencyPrice
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
customColor: root.textColor customColor: d.textColor
font.pixelSize: 13 font.pixelSize: 13
loading: modelData && modelData.marketDetailsLoading loading: root.marketDetailsLoading
text: modelData && modelData.marketDetails ? LocaleUtils.currencyAmountToLocaleString(modelData.marketDetails.currencyPrice) : "" text: loading ? Constants.dummyText : root.marketCurrencyPrice
} }
} }
ManageTokensCommunityTag {
anchors.right: parent.right Loader {
communityImage: !!modelData ? modelData.communityImage : "" active: d.isCommunityToken
communityName: !!modelData && !!modelData.communityName ? modelData.communityName: ""
communityId: !!modelData && !!modelData.communityId ? modelData.communityId : "" sourceComponent: ManageTokensCommunityTag {
asset.letterSize: 12 anchors.right: parent.right
visible: root.isCommunityToken
communityImage: root.communityIcon
TapHandler { communityName: root.communityName
acceptedButtons: Qt.LeftButton communityId: root.communityId
onSingleTapped: root.switchToCommunityRequested(modelData.communityId)
asset.letterSize: 12
TapHandler {
acceptedButtons: Qt.LeftButton
onSingleTapped: root.communityClicked(root.communityId)
}
} }
} }
} }
] ]
states: [ states: State {
State { name: "unknownToken"
name: "unknownToken" when: !root.icon.toString()
when: !root.symbolUrl
PropertyChanges { PropertyChanges {
target: root.asset target: root.asset
isLetterIdenticon: true isLetterIdenticon: true
color: Theme.palette.miscColor5 color: Theme.palette.miscColor5
name: !!modelData && modelData.symbol ? modelData.symbol : "" name: root.name
}
} }
] }
} }

View File

@ -1,169 +0,0 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import StatusQ.Core.Theme 0.1
import StatusQ.Components 0.1
import StatusQ.Core 0.1
import StatusQ.Controls 0.1
import AppLayouts.Wallet.controls 1.0
import utils 1.0
StatusListItem {
id: root
property string name
property url icon
property string balance
property bool marketDetailsAvailable: false
property string marketBalance
property bool marketDetailsLoading: false
property string marketCurrencyPrice
property real marketChangePct24hour
property string communityId
property string communityName
property url communityIcon
property string errorTooltipText_1
property string errorTooltipText_2
signal communityClicked(string communityId)
QtObject {
id: d
readonly property bool isCommunityToken: !!root.communityId
readonly property string textColor: {
if (!root.marketDetailsAvailable)
return Theme.palette.successColor1
if (root.marketChangePct24hour === 0)
return Theme.palette.baseColor1
return root.marketChangePct24hour < 0
? Theme.palette.dangerColor1
: Theme.palette.successColor1
}
readonly property string upDownTriangle: {
if (root.marketChangePct24hour === 0)
return ""
return root.marketChangePct24hour < 0 ? "▾" : "▴"
}
}
title: root.name
subTitle: root.balance
asset.name: root.icon
asset.isImage: true
asset.width: 32
asset.height: 32
errorIcon.tooltip.maxWidth: 300
height: implicitHeight
statusListItemTitleIcons.sourceComponent: StatusFlatRoundButton {
width: 14
height: visible ? 14 : 0
icon.width: 14
icon.height: 14
icon.name: "tiny/warning"
icon.color: Theme.palette.dangerColor1
tooltip.text: root.errorTooltipText_1
tooltip.maxWidth: 300
visible: !!tooltip.text
}
components: [
Column {
anchors.verticalCenter: parent.verticalCenter
StatusFlatRoundButton {
id: errorIcon
width: 14
height: visible ? 14 : 0
icon.width: 14
icon.height: 14
icon.name: "tiny/warning"
icon.color: Theme.palette.dangerColor1
tooltip.text: root.errorTooltipText_2
tooltip.maxWidth: 200
visible: root.marketDetailsAvailable && !!tooltip.text
}
StatusTextWithLoadingState {
id: currencyBalance
anchors.right: parent.right
visible: !errorIcon.visible && root.marketDetailsAvailable
loading: root.marketDetailsLoading
text: loading ? Constants.dummyText : root.marketBalance
}
Row {
anchors.right: parent.right
spacing: 6
visible: !errorIcon.visible && root.marketDetailsAvailable
StatusTextWithLoadingState {
id: change24HourPercentageText
anchors.verticalCenter: parent.verticalCenter
customColor: d.textColor
font.pixelSize: 13
loading: root.marketDetailsLoading
text: qsTr("%1 %2%", "[up/down/none character depending on value sign] [localized percentage value]%")
.arg(d.upDownTriangle).arg(LocaleUtils.numberToLocaleString(root.marketChangePct24hour, 2))
}
Rectangle {
anchors.verticalCenter: parent.verticalCenter
width: 1
height: 12
color: Theme.palette.directColor9
}
StatusTextWithLoadingState {
id: currencyPrice
anchors.verticalCenter: parent.verticalCenter
customColor: d.textColor
font.pixelSize: 13
loading: root.marketDetailsLoading
text: loading ? Constants.dummyText : root.marketCurrencyPrice
}
}
Loader {
active: d.isCommunityToken
sourceComponent: ManageTokensCommunityTag {
anchors.right: parent.right
communityImage: root.communityIcon
communityName: root.communityName
communityId: root.communityId
asset.letterSize: 12
TapHandler {
acceptedButtons: Qt.LeftButton
onSingleTapped: root.communityClicked(root.communityId)
}
}
}
}
]
states: State {
name: "unknownToken"
when: !root.icon.toString()
PropertyChanges {
target: root.asset
isLetterIdenticon: true
color: Theme.palette.miscColor5
name: root.name
}
}
}

View File

@ -22,7 +22,6 @@ InformationTag 1.0 InformationTag.qml
InformationTile 1.0 InformationTile.qml InformationTile 1.0 InformationTile.qml
Input 1.0 Input.qml Input 1.0 Input.qml
LoadingTokenDelegate 1.0 LoadingTokenDelegate.qml LoadingTokenDelegate 1.0 LoadingTokenDelegate.qml
LoadingTokenDelegateNew 1.0 LoadingTokenDelegateNew.qml
LinkPreviewDebugView 1.0 LinkPreviewDebugView.qml LinkPreviewDebugView 1.0 LinkPreviewDebugView.qml
ProfilePerspectiveSelector 1.0 ProfilePerspectiveSelector.qml ProfilePerspectiveSelector 1.0 ProfilePerspectiveSelector.qml
RadioButtonSelector 1.0 RadioButtonSelector.qml RadioButtonSelector 1.0 RadioButtonSelector.qml
@ -44,7 +43,6 @@ StyledTextEditWithLoadingState 1.0 StyledTextEditWithLoadingState.qml
StyledTextField 1.0 StyledTextField.qml StyledTextField 1.0 StyledTextField.qml
Timer 1.0 Timer.qml Timer 1.0 Timer.qml
TokenDelegate 1.0 TokenDelegate.qml TokenDelegate 1.0 TokenDelegate.qml
TokenDelegateNew 1.0 TokenDelegateNew.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 TransactionDataTile 1.0 TransactionDataTile.qml

View File

@ -1,315 +1,258 @@
import QtQuick 2.15 import QtQuick 2.15
import QtQuick.Controls 2.15 import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15 import QtQuick.Layouts 1.15
import Qt.labs.settings 1.1
import QtQml.Models 2.15
import StatusQ 0.1 import StatusQ 0.1
import StatusQ.Core 0.1 import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1 import StatusQ.Core.Theme 0.1
import StatusQ.Core.Utils 0.1 as SQUtils
import StatusQ.Controls 0.1
import StatusQ.Components 0.1
import StatusQ.Popups 0.1
import StatusQ.Popups.Dialog 0.1 import StatusQ.Popups.Dialog 0.1
import StatusQ.Models 0.1
import StatusQ.Internal 0.1 import AppLayouts.Wallet.controls 1.0
import shared.controls 1.0
import shared.popups 1.0
import utils 1.0
import SortFilterProxyModel 0.2 import SortFilterProxyModel 0.2
import utils 1.0
import shared.stores 1.0 Control {
import shared.controls 1.0
import shared.popups 1.0
import AppLayouts.Wallet.controls 1.0
ColumnLayout {
id: root id: root
// expected roles: name, symbol, balances, currencyPrice, changePct24hour, communityId, communityName, communityImage /**
required property var controller Expected model structure:
property var currencyStore key [string] - unique identifier of a token, e.g "0x3234235"
property var networkConnectionStore symbol [string] - token's symbol e.g. "ETH" or "SNT"
required property var tokensStore name [string] - token's name e.g. "Ether" or "Dai"
property var overview icon [url] - token's icon url
property bool assetDetailsLaunched: false balance [double] - tokens balance is the commonly used unit, e.g. 1.2 for 1.2 ETH, used
property bool filterVisible for sorting and computing market value
property bool areAssetsLoading: false balanceText [string] - formatted and localized balance. This is not done internally because
property string addressFilters it may depend on many external factors
property string networkFilters error [string] - error message related to balance
signal assetClicked(var token) marketDetailsAvailable [bool] - specifies if market datails are available for given token
signal sendRequested(string symbol) marketDetailsLoading [bool] - specifies if market datails are available for given token
signal receiveRequested(string symbol) marketPrice [double] - specifies market price in currently used currency
signal launchSwapModal(string tokensKey) marketChangePct24hour [double] - percentage price change in last 24 hours, e.g. 0.5 for 0.5% of price change
signal switchToCommunityRequested(string communityId)
signal manageTokensRequested()
spacing: 0 communityId [string] - for community assets, unique identifier of a community, e.g. "0x6734235"
communityName [string] - for community assets, name of a community e.g. "Crypto Kitties"
communityIcon [url] - for community assets, community's icon url
position [int] - if custom order available, display position defined by the user via token management
canBeHidden [bool] - specifies if given token can be hidden (e.g. ETH should be always visible)
**/
property var model
// enables global loading state useful when real data are not yet available
property bool loading
// shows/hides list sorter
property bool sorterVisible
// allows/disables choosing custom sort order from a sorter
property bool customOrderAvailable
// switches configuring right click menu
property bool sendEnabled: true
property bool swapEnabled: true
property bool swapVisible: true
property string balanceError
// global market data error, presented for all tokens expecting market data
property string marketDataError
// formatting function for fiat currency values
property var formatFiat: balance => `${balance.toLocaleString(Qt.locale())} XYZ`
signal sendRequested(string key)
signal receiveRequested(string key)
signal swapRequested(string key)
signal assetClicked(string key)
signal communityClicked(string communityKey)
signal hideRequested(string key)
signal hideCommunityAssets(string communityKey)
signal manageTokensRequested
QtObject { QtObject {
id: d id: d
property int selectedAssetIndex: -1
readonly property int loadingItemsCount: 25 readonly property int loadingItemsCount: 25
}
readonly property bool isCustomView: cmbTokenOrder.currentValue === SortOrderComboBox.TokenOrderCustom SortFilterProxyModel {
id: sfpm
function tokenIsVisible(symbol, currentCurrencyBalance, isCommunityAsset) { sourceModel: root.model ?? null
// NOTE Backend returns ETH, SNT, STT and DAI by default
if (!root.controller.filterAcceptsSymbol(symbol)) // explicitely hidden proxyRoles: [
return false // helper role for rendering section delegate
if (isCommunityAsset) FastExpressionRole {
return true name: "isCommunity"
// Received tokens can have 0 balance, which indicate previously owned token expression: !!communityId ? "community" : ""
if (root.tokensStore.displayAssetsBelowBalance) { expectedRoles: ["communityId"]
const threshold = root.tokensStore.getDisplayAssetsBelowBalanceThresholdDisplayAmount() },
if (threshold > 0) FastExpressionRole {
return currentCurrencyBalance > threshold name: "marketBalance"
expression: balance * marketPrice
expectedRoles: ["balance", "marketPrice"]
},
FastExpressionRole {
name: "change1DayFiat"
expression: marketBalance * (1 - (1 / (marketChangePct24hour / 100 + 1)))
expectedRoles: ["marketBalance", "marketChangePct24hour"]
} }
return true ]
}
function getTotalBalance(balances, decimals, key) { sorters: [
let totalBalance = 0 RoleSorter {
let nwFilters = root.networkFilters.split(":") roleName: "isCommunity"
let addrFilters = root.addressFilters.split(":") },
for(let i=0; i<balances.count; i++) { RoleSorter {
let balancePerAddressPerChain = SQUtils.ModelUtils.get(balances, i) roleName: sortOrderComboBox.currentSortRoleName
if (nwFilters.includes(balancePerAddressPerChain.chainId+"") && sortOrder: sortOrderComboBox.currentSortOrder
addrFilters.includes(balancePerAddressPerChain.account)) {
totalBalance+=SQUtils.AmountsArithmetic.toNumber(balancePerAddressPerChain[key], decimals)
}
} }
return totalBalance ]
}
property SortFilterProxyModel customSFPM: SortFilterProxyModel {
sourceModel: root.controller.sourceModel
proxyRoles: [
FastExpressionRole {
name: "currentBalance"
expression: d.getTotalBalance(model.balances, model.decimals, "balance")
expectedRoles: ["balances", "decimals"]
},
FastExpressionRole {
name: "currentCurrencyBalance"
expression: {
if(!model.communityId) {
if (!!model.marketDetails) {
return model.currentBalance * model.marketDetails.currencyPrice.amount
}
return 0
}
return model.currentBalance
}
expectedRoles: ["marketDetails", "communityId", "currentBalance"]
},
FastExpressionRole {
name: "tokenPrice"
expression: model.marketDetails.currencyPrice.amount
expectedRoles: ["marketDetails"]
},
FastExpressionRole {
name: "change1DayFiat"
expression: {
if (!model.isCommunityAsset && !!model.marketDetails) {
const balance1DayAgo = d.getTotalBalance(model.balances, model.decimals, "balance1DayAgo")
const change = (model.currentBalance * model.marketDetails.currencyPrice.amount) - (balance1DayAgo * (model.marketDetails.currencyPrice.amount - model.marketDetails.change24hour))
return change
}
return 0
}
expectedRoles: ["marketDetails", "balances", "decimals", "currentBalance", "isCommunityAsset"]
},
FastExpressionRole {
name: "isCommunityAsset"
expression: !!model.communityId
expectedRoles: ["communityId"]
}
]
filters: [
FastExpressionFilter {
expression: {
root.controller.revision
root.tokensStore.displayAssetsBelowBalance
return d.tokenIsVisible(model.symbol, model.currentCurrencyBalance, model.isCommunityAsset)
}
expectedRoles: ["symbol", "currentCurrencyBalance", "isCommunityAsset"]
}
]
sorters: [
RoleSorter {
roleName: "isCommunityAsset"
},
FastExpressionSorter {
expression: {
root.controller.revision
return root.controller.compareTokens(modelLeft.symbol, modelRight.symbol)
}
enabled: d.isCustomView
expectedRoles: ["symbol"]
},
RoleSorter {
roleName: cmbTokenOrder.currentSortRoleName
sortOrder: cmbTokenOrder.currentSortOrder
enabled: !d.isCustomView
}
]
}
} }
Settings { contentItem: ColumnLayout {
id: settings ColumnLayout {
category: "AssetsViewSortSettings" Layout.fillHeight: false
property int currentSortValue: SortOrderComboBox.TokenOrderCurrencyBalance Layout.preferredHeight: root.sorterVisible ? implicitHeight : 0
property alias currentSortOrder: cmbTokenOrder.currentSortOrder
}
Component.onCompleted: { opacity: root.sorterVisible ? 1 : 0
settings.sync() spacing: 20
cmbTokenOrder.currentIndex = cmbTokenOrder.indexOfValue(settings.currentSortValue) visible: opacity > 0
}
Component.onDestruction: { Behavior on Layout.preferredHeight {
settings.currentSortValue = cmbTokenOrder.currentValue NumberAnimation { duration: 200; easing.type: Easing.InOutQuad }
}
ColumnLayout {
Layout.fillWidth: true
Layout.preferredHeight: root.filterVisible ? implicitHeight : 0
spacing: 20
opacity: root.filterVisible ? 1 : 0
visible: opacity > 0
Behavior on Layout.preferredHeight { NumberAnimation { duration: 200; easing.type: Easing.InOutQuad } }
Behavior on opacity { NumberAnimation { duration: 200; easing.type: Easing.InOutQuad } }
StatusDialogDivider {
Layout.fillWidth: true
}
RowLayout {
Layout.fillWidth: true
spacing: Style.current.halfPadding
StatusBaseText {
color: Theme.palette.baseColor1
font.pixelSize: Style.current.additionalTextSize
text: qsTr("Sort by:")
} }
SortOrderComboBox { Behavior on opacity {
id: cmbTokenOrder NumberAnimation { duration: 200; easing.type: Easing.InOutQuad }
objectName: "cmbTokenOrder" }
hasCustomOrderDefined: root.controller.hasSettings
model: [ StatusDialogDivider { Layout.fillWidth: true }
{ value: SortOrderComboBox.TokenOrderCurrencyBalance, text: qsTr("Asset balance value"), icon: "", sortRoleName: "currentCurrencyBalance" }, // custom SFPM ExpressionRole on "enabledNetworkCurrencyBalance" amount
{ value: SortOrderComboBox.TokenOrderBalance, text: qsTr("Asset balance"), icon: "", sortRoleName: "currentBalance" }, // custom SFPM ExpressionRole on "enabledNetworkBalance" amount RowLayout {
{ value: SortOrderComboBox.TokenOrderCurrencyPrice, text: qsTr("Asset value"), icon: "", sortRoleName: "tokenPrice" }, // custom SFPM ExpressionRole on "currencyPrice" amount Layout.fillWidth: true
{ value: SortOrderComboBox.TokenOrder1DChange, text: qsTr("1d change: balance value"), icon: "", sortRoleName: "change1DayFiat" }, // custom SFPM ExpressionRole Layout.fillHeight: false
{ value: SortOrderComboBox.TokenOrderAlpha, text: qsTr("Asset name"), icon: "", sortRoleName: "name" },
{ value: SortOrderComboBox.TokenOrderCustom, text: qsTr("Custom order"), icon: "", sortRoleName: "" }, spacing: Style.current.halfPadding
{ value: SortOrderComboBox.TokenOrderNone, text: "---", icon: "", sortRoleName: "" }, // separator
{ value: SortOrderComboBox.TokenOrderCreateCustom, text: hasCustomOrderDefined ? qsTr("Edit custom order →") : qsTr("Create custom order →"), StatusBaseText {
icon: "", sortRoleName: "" } color: Theme.palette.baseColor1
] font.pixelSize: Style.current.additionalTextSize
onCreateOrEditRequested: { text: qsTr("Sort by:")
root.manageTokensRequested() }
SortOrderComboBox {
id: sortOrderComboBox
objectName: "cmbTokenOrder"
hasCustomOrderDefined: root.customOrderAvailable
model: [
{ value: SortOrderComboBox.TokenOrderCurrencyBalance,
text: qsTr("Asset balance value"), icon: "", sortRoleName: "marketBalance" },
{ value: SortOrderComboBox.TokenOrderBalance,
text: qsTr("Asset balance"), icon: "", sortRoleName: "balance" },
{ value: SortOrderComboBox.TokenOrderCurrencyPrice,
text: qsTr("Asset value"), icon: "", sortRoleName: "marketPrice" },
{ value: SortOrderComboBox.TokenOrder1DChange,
text: qsTr("1d change: balance value"), icon: "", sortRoleName: "change1DayFiat" },
{ value: SortOrderComboBox.TokenOrderAlpha,
text: qsTr("Asset name"), icon: "", sortRoleName: "name" },
{ value: SortOrderComboBox.TokenOrderCustom,
text: qsTr("Custom order"), icon: "", sortRoleName: "position" },
{ value: SortOrderComboBox.TokenOrderNone,
text: "---", icon: "", sortRoleName: "" }, // separator
{ value: SortOrderComboBox.TokenOrderCreateCustom,
text: hasCustomOrderDefined ? qsTr("Edit custom order →") : qsTr("Create custom order →"),
icon: "", sortRoleName: "" }
]
onCreateOrEditRequested: {
root.manageTokensRequested()
}
} }
} }
StatusDialogDivider { Layout.fillWidth: true }
} }
StatusDialogDivider { DelegateModel {
Layout.fillWidth: true id: regularModel
}
} model: sfpm
delegate: TokenDelegate {
objectName: `AssetView_TokenListItem_${model.symbol}`
StatusListView {
id: assetsListView
Layout.fillWidth: true
Layout.topMargin: Style.current.padding
Layout.preferredHeight: contentHeight
Layout.fillHeight: true
objectName: "assetViewStatusListView"
model: root.areAssetsLoading ? d.loadingItemsCount : d.customSFPM
delegate: delegateLoader
section {
property: "isCommunityAsset"
delegate: Loader {
width: ListView.view.width width: ListView.view.width
required property string section
sourceComponent: section === "true" ? sectionDelegate : null
}
}
}
Component { name: model.name
id: sectionDelegate icon: model.icon
AssetsSectionDelegate { balance: model.balanceText
width: parent.width marketBalance: root.formatFiat(model.marketBalance)
text: qsTr("Community minted")
onInfoButtonClicked: Global.openPopup(communityInfoPopupCmp)
}
}
Component { marketDetailsAvailable: model.marketDetailsAvailable
id: delegateLoader marketDetailsLoading: model.marketDetailsLoading
Loader { marketCurrencyPrice: root.formatFiat(model.change1DayFiat)
property var modelData: model marketChangePct24hour: model.marketChangePct24hour
property int delegateIndex: index
width: ListView.view.width
sourceComponent: root.areAssetsLoading ? loadingTokenDelegate : tokenDelegate
}
}
Component { communityId: model.communityId
id: loadingTokenDelegate communityName: model.communityName ?? ""
LoadingTokenDelegate { communityIcon: model.communityIcon ?? ""
objectName: "AssetView_LoadingTokenDelegate_" + delegateIndex
}
}
Component { errorTooltipText_1: model.error
id: tokenDelegate errorTooltipText_2: root.marketDataError
TokenDelegate {
objectName: "AssetView_TokenListItem_" + (!!modelData ? modelData.symbol : "") errorMode: !!root.balanceError
readonly property string balance: !!modelData && !!modelData.currentBalance ? "%1".arg(modelData.currentBalance) : "" // Needed for the tests errorIcon.tooltip.text: root.balanceError
errorTooltipText_1: !!modelData && !!networkConnectionStore ? networkConnectionStore.getBlockchainNetworkDownTextForToken(modelData.balances) : ""
errorTooltipText_2: !!networkConnectionStore ? networkConnectionStore.getMarketNetworkDownText() : "" onClicked: {
subTitle: { if (mouse.button === Qt.LeftButton)
if (!modelData || !modelData.symbol) { root.assetClicked(model.key)
return "" else if (mouse.button === Qt.RightButton)
tokenContextMenu.createObject(this, { model }).popup(mouse)
} }
if (networkConnectionStore && networkConnectionStore.noTokenBalanceAvailable) {
return "" onCommunityClicked: root.communityClicked(model.communityId)
}
return LocaleUtils.currencyAmountToLocaleString(root.currencyStore.getCurrencyAmount(modelData.currentBalance, modelData.symbol))
} }
currencyBalance.text: { }
let totalCurrencyBalance = modelData && modelData.currentCurrencyBalance ? modelData.currentCurrencyBalance : 0
return currencyStore.formatCurrencyAmount(totalCurrencyBalance, currencyStore.currentCurrency) DelegateModel {
id: loadingModel
model: d.loadingItemsCount
delegate: LoadingTokenDelegate {
objectName: `AssetView_LoadingTokenDelegate_${model.index}`
width: ListView.view.width
} }
errorMode: !!networkConnectionStore ? networkConnectionStore.noBlockchainConnectionAndNoCache && !networkConnectionStore.noMarketConnectionAndNoCache : false }
errorIcon.tooltip.text: !!networkConnectionStore ? networkConnectionStore.noBlockchainConnectionAndNoCacheText : ""
onClicked: (itemId, mouse) => { StatusListView {
if (mouse.button === Qt.LeftButton) { id: listView
RootStore.getHistoricalDataForToken(modelData.symbol, root.currencyStore.currentCurrency)
d.selectedAssetIndex = delegateIndex objectName: "assetViewStatusListView"
assetClicked(assetsListView.model.get(delegateIndex))
} else if (mouse.button === Qt.RightButton) { Layout.fillWidth: true
Global.openMenu(tokenContextMenu, this, Layout.fillHeight: true
{symbol: modelData.symbol, assetName: modelData.name, assetImage: symbolUrl,
communityId: modelData.communityId, communityName: modelData.communityName, model: root.loading ? loadingModel : regularModel
communityImage: modelData.communityImage, tokensKey: modelData.tokensKey})
} section {
} property: "isCommunity"
onSwitchToCommunityRequested: root.switchToCommunityRequested(communityId) delegate: AssetsSectionDelegate {
Component.onCompleted: { width: parent.width
// on Model reset if the detail view is shown, update the data in background. text: qsTr("Community minted")
if(root.assetDetailsLaunched && delegateIndex === d.selectedAssetIndex) { onInfoButtonClicked: communityInfoPopup.createObject(this).open()
assetClicked(assetsListView.model.get(delegateIndex))
} }
} }
} }
@ -317,92 +260,73 @@ ColumnLayout {
Component { Component {
id: tokenContextMenu id: tokenContextMenu
StatusMenu {
AssetContextMenu {
required property var model
readonly property string key: model.key
readonly property string communityKey: model.communityId
onClosed: destroy() onClosed: destroy()
property string tokensKey sendEnabled: root.sendEnabled
property string symbol swapEnabled: root.swapEnabled
property string assetName swapVisible: root.swapVisible
property string assetImage hideVisible: model.canBeHidden
property string communityId communityHideVisible: !!model.isCommunity
property string communityName
property string communityImage
StatusAction { onSendRequested: root.sendRequested(key)
enabled: root.networkConnectionStore.sendBuyBridgeEnabled && !root.overview.isWatchOnlyAccount && root.overview.canSend onReceiveRequested: root.receiveRequested(key)
visibleOnDisabled: true onSwapRequested: root.swapRequested(key)
icon.name: "send"
text: qsTr("Send") onHideRequested:
onTriggered: root.sendRequested(symbol) confirmHideAssetPopup.createObject(parent, { model }).open()
} onCommunityHideRequested:
StatusAction { confirmHideCommunityAssetsPopup.createObject(parent, { model }).open()
icon.name: "receive"
text: qsTr("Receive") onManageTokensRequested: root.manageTokensRequested()
onTriggered: root.receiveRequested(symbol)
}
StatusAction {
icon.name: "swap"
text: qsTr("Swap")
enabled: Global.featureFlags.swapEnabled && !root.overview.isWatchOnlyAccount
visibleOnDisabled: Global.featureFlags.swapEnabled
onTriggered: root.launchSwapModal(tokensKey)
}
StatusMenuSeparator {}
StatusAction {
icon.name: "settings"
text: qsTr("Manage tokens")
onTriggered: root.manageTokensRequested()
}
StatusAction {
enabled: symbol !== Constants.ethToken
type: StatusAction.Type.Danger
icon.name: "hide"
text: qsTr("Hide asset")
onTriggered: Global.openConfirmHideAssetPopup(symbol, assetName, assetImage, !!communityId)
}
StatusAction {
enabled: !!communityId
type: StatusAction.Type.Danger
icon.name: "hide"
text: qsTr("Hide all assets from this community")
onTriggered: Global.openPopup(confirmHideCommunityAssetsPopup, {communityId, communityName, communityImage})
}
} }
} }
Component { Component {
id: communityInfoPopupCmp id: communityInfoPopup
CommunityAssetsInfoPopup {}
CommunityAssetsInfoPopup {
destroyOnClose: true
}
}
Component {
id: confirmHideAssetPopup
ConfirmHideAssetPopup {
destroyOnClose: true
required property var model
symbol: model.symbol
name: model.name
icon: model.icon
onConfirmButtonClicked: {
root.hideRequested(model.key)
close()
}
}
} }
Component { Component {
id: confirmHideCommunityAssetsPopup id: confirmHideCommunityAssetsPopup
ConfirmationDialog {
property string communityId
property string communityName
property string communityImage
width: 520 ConfirmHideCommunityAssetsPopup {
destroyOnClose: true required property var model
confirmButtonLabel: qsTr("Hide '%1' assets").arg(communityName)
cancelBtnType: "" name: model.communityName
showCancelButton: true icon: model.communityIcon
headerSettings.title: qsTr("Hide %1 community assets").arg(communityName)
headerSettings.asset.name: communityImage
confirmationText: qsTr("Are you sure you want to hide all community assets minted by %1? You will no longer see or be able to interact with these assets anywhere inside Status.").arg(communityName)
onCancelButtonClicked: close()
onConfirmButtonClicked: { onConfirmButtonClicked: {
root.controller.showHideGroup(communityId, false) root.hideCommunityAssets(model.communityId)
close() close();
Global.displayToastMessage(
qsTr("%1 community assets were successfully hidden. You can toggle asset visibility via %2.").arg(communityName)
.arg(`<a style="text-decoration:none" href="#${Constants.appSection.profile}/${Constants.settingsSubsection.wallet}/${Constants.walletSettingsSubsection.manageAssets}">` + qsTr("Settings", "Go to Settings") + "</a>"),
"",
"checkmark-circle",
false,
Constants.ephemeralNotificationType.success,
""
)
} }
} }
} }

View File

@ -1,333 +0,0 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import QtQml.Models 2.15
import StatusQ 0.1
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Popups.Dialog 0.1
import AppLayouts.Wallet.controls 1.0
import shared.controls 1.0
import shared.popups 1.0
import utils 1.0
import SortFilterProxyModel 0.2
Control {
id: root
/**
Expected model structure:
key [string] - unique identifier of a token, e.g "0x3234235"
symbol [string] - token's symbol e.g. "ETH" or "SNT"
name [string] - token's name e.g. "Ether" or "Dai"
icon [url] - token's icon url
balance [double] - tokens balance is the commonly used unit, e.g. 1.2 for 1.2 ETH, used
for sorting and computing market value
balanceText [string] - formatted and localized balance. This is not done internally because
it may depend on many external factors
error [string] - error message related to balance
marketDetailsAvailable [bool] - specifies if market datails are available for given token
marketDetailsLoading [bool] - specifies if market datails are available for given token
marketPrice [double] - specifies market price in currently used currency
marketChangePct24hour [double] - percentage price change in last 24 hours, e.g. 0.5 for 0.5% of price change
communityId [string] - for community assets, unique identifier of a community, e.g. "0x6734235"
communityName [string] - for community assets, name of a community e.g. "Crypto Kitties"
communityIcon [url] - for community assets, community's icon url
position [int] - if custom order available, display position defined by the user via token management
canBeHidden [bool] - specifies if given token can be hidden (e.g. ETH should be always visible)
**/
property var model
// enables global loading state useful when real data are not yet available
property bool loading
// shows/hides list sorter
property bool sorterVisible
// allows/disables choosing custom sort order from a sorter
property bool customOrderAvailable
// switches configuring right click menu
property bool sendEnabled: true
property bool swapEnabled: true
property bool swapVisible: true
property string balanceError
// global market data error, presented for all tokens expecting market data
property string marketDataError
// formatting function for fiat currency values
property var formatFiat: balance => `${balance.toLocaleString(Qt.locale())} XYZ`
signal sendRequested(string key)
signal receiveRequested(string key)
signal swapRequested(string key)
signal assetClicked(string key)
signal communityClicked(string communityKey)
signal hideRequested(string key)
signal hideCommunityAssets(string communityKey)
signal manageTokensRequested
QtObject {
id: d
readonly property int loadingItemsCount: 25
}
SortFilterProxyModel {
id: sfpm
sourceModel: root.model
proxyRoles: [
// helper role for rendering section delegate
FastExpressionRole {
name: "isCommunity"
expression: !!communityId ? "community" : ""
expectedRoles: ["communityId"]
},
FastExpressionRole {
name: "marketBalance"
expression: balance * marketPrice
expectedRoles: ["balance", "marketPrice"]
},
FastExpressionRole {
name: "change1DayFiat"
expression: marketBalance * (1 - (1 / (marketChangePct24hour / 100 + 1)))
expectedRoles: ["marketBalance", "marketChangePct24hour"]
}
]
sorters: [
RoleSorter {
roleName: "isCommunity"
},
RoleSorter {
roleName: sortOrderComboBox.currentSortRoleName
sortOrder: sortOrderComboBox.currentSortOrder
}
]
}
contentItem: ColumnLayout {
ColumnLayout {
Layout.fillHeight: false
Layout.preferredHeight: root.sorterVisible ? implicitHeight : 0
opacity: root.sorterVisible ? 1 : 0
spacing: 20
visible: opacity > 0
Behavior on Layout.preferredHeight {
NumberAnimation { duration: 200; easing.type: Easing.InOutQuad }
}
Behavior on opacity {
NumberAnimation { duration: 200; easing.type: Easing.InOutQuad }
}
StatusDialogDivider { Layout.fillWidth: true }
RowLayout {
Layout.fillWidth: true
Layout.fillHeight: false
spacing: Style.current.halfPadding
StatusBaseText {
color: Theme.palette.baseColor1
font.pixelSize: Style.current.additionalTextSize
text: qsTr("Sort by:")
}
SortOrderComboBox {
id: sortOrderComboBox
objectName: "cmbTokenOrder"
hasCustomOrderDefined: root.customOrderAvailable
model: [
{ value: SortOrderComboBox.TokenOrderCurrencyBalance,
text: qsTr("Asset balance value"), icon: "", sortRoleName: "marketBalance" },
{ value: SortOrderComboBox.TokenOrderBalance,
text: qsTr("Asset balance"), icon: "", sortRoleName: "balance" },
{ value: SortOrderComboBox.TokenOrderCurrencyPrice,
text: qsTr("Asset value"), icon: "", sortRoleName: "marketPrice" },
{ value: SortOrderComboBox.TokenOrder1DChange,
text: qsTr("1d change: balance value"), icon: "", sortRoleName: "change1DayFiat" },
{ value: SortOrderComboBox.TokenOrderAlpha,
text: qsTr("Asset name"), icon: "", sortRoleName: "name" },
{ value: SortOrderComboBox.TokenOrderCustom,
text: qsTr("Custom order"), icon: "", sortRoleName: "position" },
{ value: SortOrderComboBox.TokenOrderNone,
text: "---", icon: "", sortRoleName: "" }, // separator
{ value: SortOrderComboBox.TokenOrderCreateCustom,
text: hasCustomOrderDefined ? qsTr("Edit custom order →") : qsTr("Create custom order →"),
icon: "", sortRoleName: "" }
]
onCreateOrEditRequested: {
root.manageTokensRequested()
}
}
}
StatusDialogDivider { Layout.fillWidth: true }
}
DelegateModel {
id: regularModel
model: sfpm
delegate: TokenDelegateNew {
objectName: `AssetView_TokenListItem_${model.symbol}`
width: ListView.view.width
name: model.name
icon: model.icon
balance: model.balanceText
marketBalance: root.formatFiat(model.marketBalance)
marketDetailsAvailable: model.marketDetailsAvailable
marketDetailsLoading: model.marketDetailsLoading
marketCurrencyPrice: root.formatFiat(model.change1DayFiat)
marketChangePct24hour: model.marketChangePct24hour
communityId: model.communityId
communityName: model.communityName ?? ""
communityIcon: model.communityIcon ?? ""
errorTooltipText_1: model.error
errorTooltipText_2: root.marketDataError
errorMode: !!root.balanceError
errorIcon.tooltip.text: root.balanceError
onClicked: {
if (mouse.button === Qt.LeftButton)
root.assetClicked(model.key)
else if (mouse.button === Qt.RightButton)
tokenContextMenu.createObject(this, { model }).popup(mouse)
}
onCommunityClicked: root.communityClicked(model.communityId)
}
}
DelegateModel {
id: loadingModel
model: d.loadingItemsCount
delegate: LoadingTokenDelegateNew {
objectName: `AssetView_LoadingTokenDelegate_${model.index}`
width: ListView.view.width
}
}
StatusListView {
id: listView
objectName: "assetViewStatusListView"
Layout.fillWidth: true
Layout.fillHeight: true
model: root.loading ? loadingModel : regularModel
section {
property: "isCommunity"
delegate: AssetsSectionDelegate {
width: parent.width
text: qsTr("Community minted")
onInfoButtonClicked: communityInfoPopup.createObject(this).open()
}
}
}
}
Component {
id: tokenContextMenu
AssetContextMenu {
required property var model
readonly property string key: model.key
readonly property string communityKey: model.communityId
onClosed: destroy()
sendEnabled: root.sendEnabled
swapEnabled: root.swapEnabled
swapVisible: root.swapVisible
hideVisible: model.canBeHidden
communityHideVisible: !!model.isCommunity
onSendRequested: root.sendRequested(key)
onReceiveRequested: root.receiveRequested(key)
onSwapRequested: root.swapRequested(key)
onHideRequested:
confirmHideAssetPopup.createObject(parent, { model }).open()
onCommunityHideRequested:
confirmHideCommunityAssetsPopup.createObject(parent, { model }).open()
onManageTokensRequested: root.manageTokensRequested()
}
}
Component {
id: communityInfoPopup
CommunityAssetsInfoPopup {
destroyOnClose: true
}
}
Component {
id: confirmHideAssetPopup
ConfirmHideAssetPopup {
destroyOnClose: true
required property var model
symbol: model.symbol
name: model.name
icon: model.icon
onConfirmButtonClicked: {
root.hideRequested(model.key)
close()
}
}
}
Component {
id: confirmHideCommunityAssetsPopup
ConfirmHideCommunityAssetsPopup {
required property var model
name: model.communityName
icon: model.communityIcon
onConfirmButtonClicked: {
root.hideCommunityAssets(model.communityId)
close();
}
}
}
}

View File

@ -1,7 +1,6 @@
AssetContextMenu 1.0 AssetContextMenu.qml AssetContextMenu 1.0 AssetContextMenu.qml
AssetsView 1.0 AssetsView.qml AssetsView 1.0 AssetsView.qml
AssetsViewAdaptor 1.0 AssetsViewAdaptor.qml AssetsViewAdaptor 1.0 AssetsViewAdaptor.qml
AssetsViewNew 1.0 AssetsViewNew.qml
ConfirmHideAssetPopup 1.0 ConfirmHideAssetPopup.qml ConfirmHideAssetPopup 1.0 ConfirmHideAssetPopup.qml
ConfirmHideCommunityAssetsPopup 1.0 ConfirmHideCommunityAssetsPopup.qml ConfirmHideCommunityAssetsPopup 1.0 ConfirmHideCommunityAssetsPopup.qml
EnsResolver 1.0 EnsResolver.qml EnsResolver 1.0 EnsResolver.qml