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
Pane {
SplitView.fillWidth: true
SplitView.fillHeight: true
AssetsView { AssetsView {
id: assetsView anchors.fill: parent
Layout.fillHeight: true
Layout.fillWidth: true
areAssetsLoading: loadingCheckbox.checked
controller: ManageTokensController {
sourceModel: d.walletAssetStore.groupedAccountAssetsModel
settingsKey: "WalletAssets"
serializeAsCollectibles: false
onRequestSaveSettings: (jsonData) => { loading: loadingCheckBox.checked
savingStarted() sorterVisible: sorterVisibleCheckBox.checked
settingsStore.setValue(settingsKey, jsonData) customOrderAvailable: customOrderAvailableCheckBox.checked
savingFinished()
}
onRequestLoadSettings: {
loadingStarted()
const jsonData = settingsStore.value(settingsKey, null)
loadingFinished(jsonData)
}
onRequestClearSettings: {
settingsStore.setValue(settingsKey, null)
}
onTokenHidden: (symbol, name) => Global.displayToastMessage( sendEnabled: sendEnabledCheckBox.checked
qsTr("%1 (%2) was successfully hidden").arg(name).arg(symbol), "", "checkmark-circle", swapEnabled: swapEnabledCheckBox.checked
false, Constants.ephemeralNotificationType.success, "") swapVisible: swapVisibleCheckBox.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 { balanceError: balanceErrorCheckBox.checked
id: settingsStore ? "Balance error!" : ""
category: "ManageTokens-" + assetsView.controller.settingsKey
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"
onClicked: stack.currentIndex = 0
}
AssetsDetailView {
id: detailsView
Layout.fillHeight: true
Layout.fillWidth: true
currencyStore: d.currencyStore
allNetworksModel: NetworksModel.flatNetworks
networkFilters: d.networksChainsCurrentlySelected
} }
LogsView {
clip: true
SplitView.preferredHeight: 150
SplitView.fillWidth: true
logText: logs.logText
} }
} }
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 { CheckBox {
id: networksRepeater id: customOrderAvailableCheckBox
model: NetworksModel.flatNetworks
delegate: CheckBox { text: "custom order available"
readonly property int chainID: chainId
width: parent.width
text: chainName
visible: isTest
checked: true
onToggled: {
isEnabled = checked
} }
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"
} }
} }
} }
ColumnLayout { Settings {
Layout.fillWidth: true property alias loading: loadingCheckBox.checked
Text { property alias filterVisible: sorterVisibleCheckBox.checked
text: "select account(s)" property alias customOrderAvailable: customOrderAvailableCheckBox.checked
} property alias sendEnabled: sendEnabledCheckBox.checked
Repeater { property alias swapEnabled: swapEnabledCheckBox.checked
id: accountsRepeater property alias swapVisible: swapVisibleCheckBox.checked
model: WalletAccountsModel {} property alias balanceError: balanceErrorCheckBox.checked
delegate: CheckBox { property alias marketDataError: marketDataErrorCheckBox.checked
readonly property string address: model.address
checked: true
visible: index<2
width: parent.width
text: name
}
}
}
ColumnLayout {
Layout.fillWidth: true
Switch {
id: ctrlBalanceThresholdSwitch
text: qsTr("Currency balance threshold")
checked: false
}
CurrencyAmountInput {
id: ctrlBalanceThreshold
value: 10.1
}
}
}
} }
} }
// 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 currentCurrencySymbol property string communityId
property string textColor: { property string communityName
if (!modelData || !modelData.marketDetails) { property url communityIcon
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) QtObject {
return "" id: d
if (modelData.image)
return modelData.image readonly property bool isCommunityToken: !!root.communityId
if (modelData.symbol)
return Constants.tokenIcon(modelData.symbol, false) readonly property string textColor: {
return "" 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: { readonly property string upDownTriangle: {
if (!modelData || !modelData.marketDetails) if (root.marketChangePct24hour === 0)
return ""
if (modelData.marketDetails.changePct24hour < 0)
return "▾"
if (modelData.marketDetails.changePct24hour > 0)
return "▴"
return "" return ""
return root.marketChangePct24hour < 0 ? "▾" : "▴"
}
} }
readonly property bool isUndefined: modelData && !modelData.marketDetailsLoading && title === ""
signal switchToCommunityRequested(string communityId) title: root.name
subTitle: root.balance
title: modelData ? modelData.name : "" asset.name: root.icon
subTitle: LocaleUtils.currencyAmountToLocaleString(modelData.enabledNetworkBalance)
asset.name: symbolUrl
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 {
Loader {
active: d.isCommunityToken
sourceComponent: ManageTokensCommunityTag {
anchors.right: parent.right anchors.right: parent.right
communityImage: !!modelData ? modelData.communityImage : ""
communityName: !!modelData && !!modelData.communityName ? modelData.communityName: "" communityImage: root.communityIcon
communityId: !!modelData && !!modelData.communityId ? modelData.communityId : "" communityName: root.communityName
communityId: root.communityId
asset.letterSize: 12 asset.letterSize: 12
visible: root.isCommunityToken
TapHandler { TapHandler {
acceptedButtons: Qt.LeftButton acceptedButtons: Qt.LeftButton
onSingleTapped: root.switchToCommunityRequested(modelData.communityId) onSingleTapped: root.communityClicked(root.communityId)
}
} }
} }
} }
] ]
states: [ states: State {
State {
name: "unknownToken" name: "unknownToken"
when: !root.symbolUrl when: !root.icon.toString()
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,197 +1,147 @@
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
function tokenIsVisible(symbol, currentCurrencyBalance, isCommunityAsset) {
// NOTE Backend returns ETH, SNT, STT and DAI by default
if (!root.controller.filterAcceptsSymbol(symbol)) // explicitely hidden
return false
if (isCommunityAsset)
return true
// Received tokens can have 0 balance, which indicate previously owned token
if (root.tokensStore.displayAssetsBelowBalance) {
const threshold = root.tokensStore.getDisplayAssetsBelowBalanceThresholdDisplayAmount()
if (threshold > 0)
return currentCurrencyBalance > threshold
}
return true
} }
function getTotalBalance(balances, decimals, key) { SortFilterProxyModel {
let totalBalance = 0 id: sfpm
let nwFilters = root.networkFilters.split(":")
let addrFilters = root.addressFilters.split(":") sourceModel: root.model ?? null
for(let i=0; i<balances.count; i++) {
let balancePerAddressPerChain = SQUtils.ModelUtils.get(balances, i)
if (nwFilters.includes(balancePerAddressPerChain.chainId+"") &&
addrFilters.includes(balancePerAddressPerChain.account)) {
totalBalance+=SQUtils.AmountsArithmetic.toNumber(balancePerAddressPerChain[key], decimals)
}
}
return totalBalance
}
property SortFilterProxyModel customSFPM: SortFilterProxyModel {
sourceModel: root.controller.sourceModel
proxyRoles: [ proxyRoles: [
// helper role for rendering section delegate
FastExpressionRole { FastExpressionRole {
name: "currentBalance" name: "isCommunity"
expression: d.getTotalBalance(model.balances, model.decimals, "balance") expression: !!communityId ? "community" : ""
expectedRoles: ["balances", "decimals"] expectedRoles: ["communityId"]
}, },
FastExpressionRole { FastExpressionRole {
name: "currentCurrencyBalance" name: "marketBalance"
expression: { expression: balance * marketPrice
if(!model.communityId) { expectedRoles: ["balance", "marketPrice"]
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 { FastExpressionRole {
name: "change1DayFiat" name: "change1DayFiat"
expression: { expression: marketBalance * (1 - (1 / (marketChangePct24hour / 100 + 1)))
if (!model.isCommunityAsset && !!model.marketDetails) { expectedRoles: ["marketBalance", "marketChangePct24hour"]
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: [ sorters: [
RoleSorter { RoleSorter {
roleName: "isCommunityAsset" roleName: "isCommunity"
},
FastExpressionSorter {
expression: {
root.controller.revision
return root.controller.compareTokens(modelLeft.symbol, modelRight.symbol)
}
enabled: d.isCustomView
expectedRoles: ["symbol"]
}, },
RoleSorter { RoleSorter {
roleName: cmbTokenOrder.currentSortRoleName roleName: sortOrderComboBox.currentSortRoleName
sortOrder: cmbTokenOrder.currentSortOrder sortOrder: sortOrderComboBox.currentSortOrder
enabled: !d.isCustomView
} }
] ]
} }
}
Settings {
id: settings
category: "AssetsViewSortSettings"
property int currentSortValue: SortOrderComboBox.TokenOrderCurrencyBalance
property alias currentSortOrder: cmbTokenOrder.currentSortOrder
}
Component.onCompleted: {
settings.sync()
cmbTokenOrder.currentIndex = cmbTokenOrder.indexOfValue(settings.currentSortValue)
}
Component.onDestruction: {
settings.currentSortValue = cmbTokenOrder.currentValue
}
contentItem: ColumnLayout {
ColumnLayout { ColumnLayout {
Layout.fillWidth: true Layout.fillHeight: false
Layout.preferredHeight: root.filterVisible ? implicitHeight : 0 Layout.preferredHeight: root.sorterVisible ? implicitHeight : 0
opacity: root.sorterVisible ? 1 : 0
spacing: 20 spacing: 20
opacity: root.filterVisible ? 1 : 0
visible: opacity > 0 visible: opacity > 0
Behavior on Layout.preferredHeight { NumberAnimation { duration: 200; easing.type: Easing.InOutQuad } } Behavior on Layout.preferredHeight {
Behavior on opacity { NumberAnimation { duration: 200; easing.type: Easing.InOutQuad } } NumberAnimation { duration: 200; easing.type: Easing.InOutQuad }
StatusDialogDivider {
Layout.fillWidth: true
} }
Behavior on opacity {
NumberAnimation { duration: 200; easing.type: Easing.InOutQuad }
}
StatusDialogDivider { Layout.fillWidth: true }
RowLayout { RowLayout {
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: false
spacing: Style.current.halfPadding spacing: Style.current.halfPadding
StatusBaseText { StatusBaseText {
@ -201,18 +151,28 @@ ColumnLayout {
} }
SortOrderComboBox { SortOrderComboBox {
id: cmbTokenOrder id: sortOrderComboBox
objectName: "cmbTokenOrder" objectName: "cmbTokenOrder"
hasCustomOrderDefined: root.controller.hasSettings hasCustomOrderDefined: root.customOrderAvailable
model: [ model: [
{ value: SortOrderComboBox.TokenOrderCurrencyBalance, text: qsTr("Asset balance value"), icon: "", sortRoleName: "currentCurrencyBalance" }, // custom SFPM ExpressionRole on "enabledNetworkCurrencyBalance" amount { value: SortOrderComboBox.TokenOrderCurrencyBalance,
{ value: SortOrderComboBox.TokenOrderBalance, text: qsTr("Asset balance"), icon: "", sortRoleName: "currentBalance" }, // custom SFPM ExpressionRole on "enabledNetworkBalance" amount text: qsTr("Asset balance value"), icon: "", sortRoleName: "marketBalance" },
{ value: SortOrderComboBox.TokenOrderCurrencyPrice, text: qsTr("Asset value"), icon: "", sortRoleName: "tokenPrice" }, // custom SFPM ExpressionRole on "currencyPrice" amount { value: SortOrderComboBox.TokenOrderBalance,
{ value: SortOrderComboBox.TokenOrder1DChange, text: qsTr("1d change: balance value"), icon: "", sortRoleName: "change1DayFiat" }, // custom SFPM ExpressionRole text: qsTr("Asset balance"), icon: "", sortRoleName: "balance" },
{ value: SortOrderComboBox.TokenOrderAlpha, text: qsTr("Asset name"), icon: "", sortRoleName: "name" }, { value: SortOrderComboBox.TokenOrderCurrencyPrice,
{ value: SortOrderComboBox.TokenOrderCustom, text: qsTr("Custom order"), icon: "", sortRoleName: "" }, text: qsTr("Asset value"), icon: "", sortRoleName: "marketPrice" },
{ value: SortOrderComboBox.TokenOrderNone, text: "---", icon: "", sortRoleName: "" }, // separator { value: SortOrderComboBox.TokenOrder1DChange,
{ value: SortOrderComboBox.TokenOrderCreateCustom, text: hasCustomOrderDefined ? qsTr("Edit custom order →") : qsTr("Create custom order →"), 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: "" } icon: "", sortRoleName: "" }
] ]
onCreateOrEditRequested: { onCreateOrEditRequested: {
@ -221,95 +181,78 @@ ColumnLayout {
} }
} }
StatusDialogDivider { StatusDialogDivider { Layout.fillWidth: true }
Layout.fillWidth: true }
DelegateModel {
id: regularModel
model: sfpm
delegate: TokenDelegate {
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: LoadingTokenDelegate {
objectName: `AssetView_LoadingTokenDelegate_${model.index}`
width: ListView.view.width
} }
} }
StatusListView { StatusListView {
id: assetsListView id: listView
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
required property string section
sourceComponent: section === "true" ? sectionDelegate : null
}
}
}
Component { objectName: "assetViewStatusListView"
id: sectionDelegate
AssetsSectionDelegate { Layout.fillWidth: true
Layout.fillHeight: true
model: root.loading ? loadingModel : regularModel
section {
property: "isCommunity"
delegate: AssetsSectionDelegate {
width: parent.width width: parent.width
text: qsTr("Community minted") text: qsTr("Community minted")
onInfoButtonClicked: Global.openPopup(communityInfoPopupCmp) onInfoButtonClicked: communityInfoPopup.createObject(this).open()
}
}
Component {
id: delegateLoader
Loader {
property var modelData: model
property int delegateIndex: index
width: ListView.view.width
sourceComponent: root.areAssetsLoading ? loadingTokenDelegate : tokenDelegate
}
}
Component {
id: loadingTokenDelegate
LoadingTokenDelegate {
objectName: "AssetView_LoadingTokenDelegate_" + delegateIndex
}
}
Component {
id: tokenDelegate
TokenDelegate {
objectName: "AssetView_TokenListItem_" + (!!modelData ? modelData.symbol : "")
readonly property string balance: !!modelData && !!modelData.currentBalance ? "%1".arg(modelData.currentBalance) : "" // Needed for the tests
errorTooltipText_1: !!modelData && !!networkConnectionStore ? networkConnectionStore.getBlockchainNetworkDownTextForToken(modelData.balances) : ""
errorTooltipText_2: !!networkConnectionStore ? networkConnectionStore.getMarketNetworkDownText() : ""
subTitle: {
if (!modelData || !modelData.symbol) {
return ""
}
if (networkConnectionStore && networkConnectionStore.noTokenBalanceAvailable) {
return ""
}
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)
}
errorMode: !!networkConnectionStore ? networkConnectionStore.noBlockchainConnectionAndNoCache && !networkConnectionStore.noMarketConnectionAndNoCache : false
errorIcon.tooltip.text: !!networkConnectionStore ? networkConnectionStore.noBlockchainConnectionAndNoCacheText : ""
onClicked: (itemId, mouse) => {
if (mouse.button === Qt.LeftButton) {
RootStore.getHistoricalDataForToken(modelData.symbol, root.currencyStore.currentCurrency)
d.selectedAssetIndex = delegateIndex
assetClicked(assetsListView.model.get(delegateIndex))
} else if (mouse.button === Qt.RightButton) {
Global.openMenu(tokenContextMenu, this,
{symbol: modelData.symbol, assetName: modelData.name, assetImage: symbolUrl,
communityId: modelData.communityId, communityName: modelData.communityName,
communityImage: modelData.communityImage, tokensKey: modelData.tokensKey})
}
}
onSwitchToCommunityRequested: root.switchToCommunityRequested(communityId)
Component.onCompleted: {
// on Model reset if the detail view is shown, update the data in background.
if(root.assetDetailsLaunched && delegateIndex === d.selectedAssetIndex) {
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