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.Layouts 1.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import Qt.labs.settings 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 shared.views 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 Models 1.0
import AppLayouts.Wallet.views 1.0
import AppLayouts.Wallet.stores 1.0
import Qt.labs.settings 1.1
SplitView {
id: root
Logs { id: logs }
ListModel {
id: assetsModel
orientation: Qt.Horizontal
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(":")
function format(amount, symbol) {
return `${amount.toLocaleString(Qt.locale())} ${symbol}`
}
readonly property string addressesSelected: {
let supportedAddresses = []
for (let i = 0; i< accountsRepeater.count; i++) {
if (accountsRepeater.itemAt(i).checked && accountsRepeater.itemAt(i).visible)
supportedAddresses.push(accountsRepeater.itemAt(i).address)
}
return supportedAddresses.join(":")
}
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: "",
readonly property var currencyStore: CurrenciesStore {}
marketDetailsAvailable: true,
marketDetailsLoading: true,
marketPrice: 0,
marketChangePct24hour: 0,
property WalletAssetsStore walletAssetStore: WalletAssetsStore {
assetsWithFilteredBalances: d.assetsWithFilteredBalances
assetsController: assetsView.controller
}
communityId: "",
communityName: "",
communityIcon: Qt.resolvedUrl(""),
// Added this here simply because the network and address filtering wont work in Storybook applied in AssetsView
readonly property SubmodelProxyModel assetsWithFilteredBalances: SubmodelProxyModel {
sourceModel: d.walletAssetStore.groupedAccountsAssetsModel
submodelRoleName: "balances"
delegateModel: SortFilterProxyModel {
sourceModel: submodel
filters: FastExpressionFilter {
expression: {
d.networksChainsCurrentlySelected
return d.networksChainsCurrentlySelected.split(":").includes(model.chainId+"")
}
expectedRoles: ["chainId"]
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)
}
}
Popups {
popupParent: root
rootStore: QtObject {}
communityTokensStore: QtObject {}
walletAssetsStore: d.walletAssetStore
}
StackLayout {
id: stack
SplitView {
SplitView.fillWidth: true
SplitView.fillHeight: true
currentIndex: 0
orientation: Qt.Vertical
AssetsView {
id: assetsView
Layout.fillHeight: true
Layout.fillWidth: true
areAssetsLoading: loadingCheckbox.checked
controller: ManageTokensController {
sourceModel: d.walletAssetStore.groupedAccountAssetsModel
settingsKey: "WalletAssets"
serializeAsCollectibles: false
Pane {
SplitView.fillWidth: true
SplitView.fillHeight: true
onRequestSaveSettings: (jsonData) => {
savingStarted()
settingsStore.setValue(settingsKey, jsonData)
savingFinished()
}
onRequestLoadSettings: {
loadingStarted()
const jsonData = settingsStore.value(settingsKey, null)
loadingFinished(jsonData)
}
onRequestClearSettings: {
settingsStore.setValue(settingsKey, null)
}
AssetsView {
anchors.fill: parent
onTokenHidden: (symbol, name) => Global.displayToastMessage(
qsTr("%1 (%2) was successfully hidden").arg(name).arg(symbol), "", "checkmark-circle",
false, Constants.ephemeralNotificationType.success, "")
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")
loading: loadingCheckBox.checked
sorterVisible: sorterVisibleCheckBox.checked
customOrderAvailable: customOrderAvailableCheckBox.checked
Settings {
id: settingsStore
category: "ManageTokens-" + assetsView.controller.settingsKey
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`)
}
}
ColumnLayout {
Layout.fillHeight: true
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
}
Logs {
id: logs
}
LogsView {
clip: true
SplitView.preferredHeight: 150
SplitView.fillWidth: true
logText: logs.logText
}
}
Pane {
SplitView.preferredWidth: 250
SplitView.preferredWidth: 300
ColumnLayout {
spacing: 12
anchors.fill: parent
Switch {
id: ctrlFilterVisible
text: "Filter visible"
checked: true
}
CheckBox {
id: loadingCheckbox
checked: false
id: loadingCheckBox
text: "loading"
}
CheckBox {
id: sorterVisibleCheckBox
ColumnLayout {
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
}
}
}
text: "sorter visible"
}
CheckBox {
id: customOrderAvailableCheckBox
ColumnLayout {
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
}
}
text: "custom order available"
}
CheckBox {
id: sendEnabledCheckBox
ColumnLayout {
Layout.fillWidth: true
Switch {
id: ctrlBalanceThresholdSwitch
text: qsTr("Currency balance threshold")
checked: false
}
CurrencyAmountInput {
id: ctrlBalanceThreshold
value: 10.1
}
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
// 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.fillHeight: true
TokenDelegateNew {
TokenDelegate {
anchors.centerIn: parent
name: nameTextFiled.text

View File

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

View File

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

View File

@ -1,27 +1,16 @@
import QtQuick 2.15
import StatusQ.Core.Theme 0.1
import utils 1.0
TokenDelegate {
id: root
title: Constants.dummyText
subTitle: 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
statusListItemTitle.loading: true
statusListItemIcon.loading: true
textColor: Theme.palette.baseColor1
marketDetailsAvailable: true
marketDetailsLoading: true
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 AppLayouts.Wallet.controls 1.0
import utils 1.0
StatusListItem {
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 alias change24HourPercentage: change24HourPercentageText
property alias currencyPrice: currencyPrice
property bool marketDetailsAvailable: false
property string marketBalance
property bool marketDetailsLoading: false
property string marketCurrencyPrice
property real marketChangePct24hour
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 communityId
property string communityName
property url communityIcon
property string errorTooltipText_1
property string errorTooltipText_2
readonly property bool isCommunityToken: !!modelData && !!modelData.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 communityClicked(string communityId)
signal switchToCommunityRequested(string communityId)
QtObject {
id: d
title: modelData ? modelData.name : ""
subTitle: LocaleUtils.currencyAmountToLocaleString(modelData.enabledNetworkBalance)
asset.name: symbolUrl
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: isUndefined ? 0 : implicitHeight
visible: !isUndefined
height: implicitHeight
statusListItemTitleIcons.sourceComponent: StatusFlatRoundButton {
width: 14
@ -94,26 +90,32 @@ StatusListItem {
icon.color: Theme.palette.dangerColor1
tooltip.text: root.errorTooltipText_2
tooltip.maxWidth: 200
visible: !!tooltip.text
visible: root.marketDetailsAvailable && !!tooltip.text
}
StatusTextWithLoadingState {
id: currencyBalance
anchors.right: parent.right
loading: modelData && modelData.marketDetailsLoading
visible: !errorIcon.visible && !root.isCommunityToken
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.isCommunityToken
visible: !errorIcon.visible && root.marketDetailsAvailable
StatusTextWithLoadingState {
id: change24HourPercentageText
anchors.verticalCenter: parent.verticalCenter
customColor: root.textColor
customColor: d.textColor
font.pixelSize: 13
loading: modelData && modelData.marketDetailsLoading
text: modelData && modelData.marketDetails && modelData.marketDetails.changePct24hour !== undefined ? "%1 %2%".arg(root.upDownTriangle).arg(LocaleUtils.numberToLocaleString(modelData.marketDetails.changePct24hour, 2))
: "---"
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
@ -123,39 +125,45 @@ StatusListItem {
}
StatusTextWithLoadingState {
id: currencyPrice
anchors.verticalCenter: parent.verticalCenter
customColor: root.textColor
customColor: d.textColor
font.pixelSize: 13
loading: modelData && modelData.marketDetailsLoading
text: modelData && modelData.marketDetails ? LocaleUtils.currencyAmountToLocaleString(modelData.marketDetails.currencyPrice) : ""
loading: root.marketDetailsLoading
text: loading ? Constants.dummyText : root.marketCurrencyPrice
}
}
ManageTokensCommunityTag {
anchors.right: parent.right
communityImage: !!modelData ? modelData.communityImage : ""
communityName: !!modelData && !!modelData.communityName ? modelData.communityName: ""
communityId: !!modelData && !!modelData.communityId ? modelData.communityId : ""
asset.letterSize: 12
visible: root.isCommunityToken
TapHandler {
acceptedButtons: Qt.LeftButton
onSingleTapped: root.switchToCommunityRequested(modelData.communityId)
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.symbolUrl
PropertyChanges {
target: root.asset
isLetterIdenticon: true
color: Theme.palette.miscColor5
name: !!modelData && modelData.symbol ? modelData.symbol : ""
}
states: State {
name: "unknownToken"
when: !root.icon.toString()
PropertyChanges {
target: root.asset
isLetterIdenticon: true
color: Theme.palette.miscColor5
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
Input 1.0 Input.qml
LoadingTokenDelegate 1.0 LoadingTokenDelegate.qml
LoadingTokenDelegateNew 1.0 LoadingTokenDelegateNew.qml
LinkPreviewDebugView 1.0 LinkPreviewDebugView.qml
ProfilePerspectiveSelector 1.0 ProfilePerspectiveSelector.qml
RadioButtonSelector 1.0 RadioButtonSelector.qml
@ -44,7 +43,6 @@ StyledTextEditWithLoadingState 1.0 StyledTextEditWithLoadingState.qml
StyledTextField 1.0 StyledTextField.qml
Timer 1.0 Timer.qml
TokenDelegate 1.0 TokenDelegate.qml
TokenDelegateNew 1.0 TokenDelegateNew.qml
TransactionAddress 1.0 TransactionAddress.qml
TransactionAddressTile 1.0 TransactionAddressTile.qml
TransactionDataTile 1.0 TransactionDataTile.qml

View File

@ -1,315 +1,258 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import Qt.labs.settings 1.1
import QtQml.Models 2.15
import StatusQ 0.1
import StatusQ.Core 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.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 utils 1.0
import shared.stores 1.0
import shared.controls 1.0
import shared.popups 1.0
import AppLayouts.Wallet.controls 1.0
ColumnLayout {
Control {
id: root
// expected roles: name, symbol, balances, currencyPrice, changePct24hour, communityId, communityName, communityImage
required property var controller
/**
Expected model structure:
property var currencyStore
property var networkConnectionStore
required property var tokensStore
property var overview
property bool assetDetailsLaunched: false
property bool filterVisible
property bool areAssetsLoading: false
property string addressFilters
property string networkFilters
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
signal assetClicked(var token)
signal sendRequested(string symbol)
signal receiveRequested(string symbol)
signal launchSwapModal(string tokensKey)
signal switchToCommunityRequested(string communityId)
signal manageTokensRequested()
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
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 {
id: d
property int selectedAssetIndex: -1
readonly property int loadingItemsCount: 25
}
readonly property bool isCustomView: cmbTokenOrder.currentValue === SortOrderComboBox.TokenOrderCustom
SortFilterProxyModel {
id: sfpm
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
sourceModel: root.model ?? null
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"]
}
return true
}
]
function getTotalBalance(balances, decimals, key) {
let totalBalance = 0
let nwFilters = root.networkFilters.split(":")
let addrFilters = root.addressFilters.split(":")
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)
}
sorters: [
RoleSorter {
roleName: "isCommunity"
},
RoleSorter {
roleName: sortOrderComboBox.currentSortRoleName
sortOrder: sortOrderComboBox.currentSortOrder
}
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 {
id: settings
category: "AssetsViewSortSettings"
property int currentSortValue: SortOrderComboBox.TokenOrderCurrencyBalance
property alias currentSortOrder: cmbTokenOrder.currentSortOrder
}
contentItem: ColumnLayout {
ColumnLayout {
Layout.fillHeight: false
Layout.preferredHeight: root.sorterVisible ? implicitHeight : 0
Component.onCompleted: {
settings.sync()
cmbTokenOrder.currentIndex = cmbTokenOrder.indexOfValue(settings.currentSortValue)
}
opacity: root.sorterVisible ? 1 : 0
spacing: 20
visible: opacity > 0
Component.onDestruction: {
settings.currentSortValue = cmbTokenOrder.currentValue
}
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:")
Behavior on Layout.preferredHeight {
NumberAnimation { duration: 200; easing.type: Easing.InOutQuad }
}
SortOrderComboBox {
id: cmbTokenOrder
objectName: "cmbTokenOrder"
hasCustomOrderDefined: root.controller.hasSettings
model: [
{ 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
{ value: SortOrderComboBox.TokenOrderCurrencyPrice, text: qsTr("Asset value"), icon: "", sortRoleName: "tokenPrice" }, // custom SFPM ExpressionRole on "currencyPrice" amount
{ value: SortOrderComboBox.TokenOrder1DChange, text: qsTr("1d change: balance value"), icon: "", sortRoleName: "change1DayFiat" }, // custom SFPM ExpressionRole
{ value: SortOrderComboBox.TokenOrderAlpha, text: qsTr("Asset name"), icon: "", sortRoleName: "name" },
{ value: SortOrderComboBox.TokenOrderCustom, text: qsTr("Custom order"), icon: "", sortRoleName: "" },
{ 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()
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 }
}
StatusDialogDivider {
Layout.fillWidth: true
}
}
DelegateModel {
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
required property string section
sourceComponent: section === "true" ? sectionDelegate : null
}
}
}
Component {
id: sectionDelegate
AssetsSectionDelegate {
width: parent.width
text: qsTr("Community minted")
onInfoButtonClicked: Global.openPopup(communityInfoPopupCmp)
}
}
name: model.name
icon: model.icon
balance: model.balanceText
marketBalance: root.formatFiat(model.marketBalance)
Component {
id: delegateLoader
Loader {
property var modelData: model
property int delegateIndex: index
width: ListView.view.width
sourceComponent: root.areAssetsLoading ? loadingTokenDelegate : tokenDelegate
}
}
marketDetailsAvailable: model.marketDetailsAvailable
marketDetailsLoading: model.marketDetailsLoading
marketCurrencyPrice: root.formatFiat(model.change1DayFiat)
marketChangePct24hour: model.marketChangePct24hour
Component {
id: loadingTokenDelegate
LoadingTokenDelegate {
objectName: "AssetView_LoadingTokenDelegate_" + delegateIndex
}
}
communityId: model.communityId
communityName: model.communityName ?? ""
communityIcon: model.communityIcon ?? ""
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 ""
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)
}
if (networkConnectionStore && networkConnectionStore.noTokenBalanceAvailable) {
return ""
}
return LocaleUtils.currencyAmountToLocaleString(root.currencyStore.getCurrencyAmount(modelData.currentBalance, modelData.symbol))
onCommunityClicked: root.communityClicked(model.communityId)
}
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) => {
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))
}
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()
}
}
}
@ -317,92 +260,73 @@ ColumnLayout {
Component {
id: tokenContextMenu
StatusMenu {
AssetContextMenu {
required property var model
readonly property string key: model.key
readonly property string communityKey: model.communityId
onClosed: destroy()
property string tokensKey
property string symbol
property string assetName
property string assetImage
property string communityId
property string communityName
property string communityImage
sendEnabled: root.sendEnabled
swapEnabled: root.swapEnabled
swapVisible: root.swapVisible
hideVisible: model.canBeHidden
communityHideVisible: !!model.isCommunity
StatusAction {
enabled: root.networkConnectionStore.sendBuyBridgeEnabled && !root.overview.isWatchOnlyAccount && root.overview.canSend
visibleOnDisabled: true
icon.name: "send"
text: qsTr("Send")
onTriggered: root.sendRequested(symbol)
}
StatusAction {
icon.name: "receive"
text: qsTr("Receive")
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})
}
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: communityInfoPopupCmp
CommunityAssetsInfoPopup {}
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
ConfirmationDialog {
property string communityId
property string communityName
property string communityImage
width: 520
destroyOnClose: true
confirmButtonLabel: qsTr("Hide '%1' assets").arg(communityName)
cancelBtnType: ""
showCancelButton: true
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()
ConfirmHideCommunityAssetsPopup {
required property var model
name: model.communityName
icon: model.communityIcon
onConfirmButtonClicked: {
root.controller.showHideGroup(communityId, false)
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,
""
)
root.hideCommunityAssets(model.communityId)
close();
}
}
}

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
AssetsView 1.0 AssetsView.qml
AssetsViewAdaptor 1.0 AssetsViewAdaptor.qml
AssetsViewNew 1.0 AssetsViewNew.qml
ConfirmHideAssetPopup 1.0 ConfirmHideAssetPopup.qml
ConfirmHideCommunityAssetsPopup 1.0 ConfirmHideCommunityAssetsPopup.qml
EnsResolver 1.0 EnsResolver.qml