341 lines
12 KiB
QML
341 lines
12 KiB
QML
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 communitySendEnabled: false
|
|
property bool swapEnabled: true
|
|
property bool swapVisible: true
|
|
property bool communitySwapVisible: false
|
|
|
|
property string balanceError
|
|
// banner component to be displayed on top of the list
|
|
property alias bannerComponent: banner.sourceComponent
|
|
|
|
// 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 hideCommunityAssetsRequested(string communityKey)
|
|
signal manageTokensRequested
|
|
|
|
function setSortOrder(order) {
|
|
d.sortOrder = order
|
|
}
|
|
|
|
function getSortOrder() {
|
|
return d.sortOrder
|
|
}
|
|
|
|
function getSortValue() {
|
|
return d.sortValue
|
|
}
|
|
|
|
function sortByValue(value) {
|
|
d.sortValue = value
|
|
}
|
|
|
|
QtObject {
|
|
id: d
|
|
|
|
readonly property int loadingItemsCount: 25
|
|
property int sortOrder: Qt.DescendingOrder
|
|
property int sortValue: -1
|
|
}
|
|
|
|
SortFilterProxyModel {
|
|
id: sfpm
|
|
|
|
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"]
|
|
}
|
|
]
|
|
|
|
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: Theme.halfPadding
|
|
|
|
StatusBaseText {
|
|
color: Theme.palette.baseColor1
|
|
font.pixelSize: Theme.additionalTextSize
|
|
text: qsTr("Sort by:")
|
|
}
|
|
|
|
SortOrderComboBox {
|
|
id: sortOrderComboBox
|
|
|
|
objectName: "cmbTokenOrder"
|
|
hasCustomOrderDefined: root.customOrderAvailable
|
|
Binding on currentIndex {
|
|
value: {
|
|
sortOrderComboBox.count
|
|
let id = sortOrderComboBox.indexOfValue(d.sortValue)
|
|
if (id === -1)
|
|
id = sortOrderComboBox.indexOfValue(SortOrderComboBox.TokenOrderAlpha)
|
|
return id
|
|
}
|
|
when: sortOrderComboBox.count > 0
|
|
}
|
|
onCurrentValueChanged: d.sortValue = sortOrderComboBox.currentValue
|
|
Binding on currentSortOrder {
|
|
value: d.sortOrder
|
|
}
|
|
onCurrentSortOrderChanged: d.sortOrder = sortOrderComboBox.currentSortOrder
|
|
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 }
|
|
}
|
|
|
|
Loader {
|
|
id: banner
|
|
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 {
|
|
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
|
|
|
|
readonly property bool isCommunity: !!model.isCommunity
|
|
|
|
onClosed: destroy()
|
|
|
|
sendEnabled: root.sendEnabled
|
|
&& (!isCommunity || root.communitySendEnabled)
|
|
swapEnabled: root.swapEnabled
|
|
swapVisible: root.swapVisible && (!isCommunity || root.communitySwapVisible)
|
|
hideVisible: model.canBeHidden
|
|
communityHideVisible: isCommunity
|
|
|
|
onSendRequested: root.sendRequested(key)
|
|
onReceiveRequested: root.receiveRequested(key)
|
|
onSwapRequested: root.swapRequested(key)
|
|
|
|
onHideRequested: root.hideRequested(key)
|
|
onCommunityHideRequested: root.hideCommunityAssetsRequested(communityKey)
|
|
|
|
onManageTokensRequested: root.manageTokensRequested()
|
|
}
|
|
}
|
|
|
|
Component {
|
|
id: communityInfoPopup
|
|
|
|
CommunityAssetsInfoPopup {
|
|
destroyOnClose: true
|
|
}
|
|
}
|
|
}
|