2023-08-22 15:34:37 +00:00
|
|
|
import QtQuick 2.15
|
|
|
|
import QtQuick.Layouts 1.15
|
|
|
|
import QtQuick.Controls 2.15
|
2023-07-05 15:17:51 +00:00
|
|
|
|
|
|
|
import StatusQ.Core 0.1
|
|
|
|
import StatusQ.Core.Theme 0.1
|
|
|
|
import StatusQ.Controls 0.1
|
|
|
|
import StatusQ.Components 0.1
|
|
|
|
|
|
|
|
import SortFilterProxyModel 0.2
|
|
|
|
|
|
|
|
import utils 1.0
|
|
|
|
import shared.controls 1.0
|
|
|
|
|
2023-08-22 15:34:37 +00:00
|
|
|
import "../controls"
|
|
|
|
|
2023-07-05 15:17:51 +00:00
|
|
|
/*!
|
|
|
|
\qmltype TokenHoldersList
|
|
|
|
\inherits StatusListView
|
|
|
|
\brief Shows list of users or addresses with corrensponding numbers of
|
|
|
|
messages and holding amounts.
|
|
|
|
|
|
|
|
Expected roles: name, walletAddress, imageSource, amount
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
Item {
|
|
|
|
id: root
|
|
|
|
implicitHeight: (listView.contentHeight+header.height+12)//initial height plus top margin
|
|
|
|
|
|
|
|
property alias model: listView.model
|
|
|
|
property bool isSelectorMode: false
|
|
|
|
readonly property alias sortBy: d.sortBy
|
|
|
|
readonly property alias sortOrder: d.sorting
|
|
|
|
readonly property bool bottomSeparatorVisible: ((listView.contentY > 0) &&
|
2023-08-10 09:39:52 +00:00
|
|
|
(listView.contentY < (listView.contentHeight - listView.height)))
|
2023-07-05 15:17:51 +00:00
|
|
|
|
|
|
|
signal selfDestructAmountChanged(string walletAddress, int amount)
|
|
|
|
signal selfDestructRemoved(string walletAddress)
|
|
|
|
|
|
|
|
QtObject {
|
|
|
|
id: d
|
|
|
|
property int sortBy: TokenHoldersProxyModel.SortBy.None
|
|
|
|
property int sorting: Qt.DescendingOrder
|
|
|
|
property var selectedTokenAmount: new Map();
|
|
|
|
property var selectedTokenChecked: new Map();
|
|
|
|
property int delegateHeight: 64
|
|
|
|
|
|
|
|
signal resetOtherHeaders(var header)
|
|
|
|
}
|
|
|
|
|
|
|
|
clip: true
|
|
|
|
|
|
|
|
Control {
|
|
|
|
id: header
|
|
|
|
width: parent.width
|
|
|
|
height: 40
|
|
|
|
readonly property alias usernameHeaderWidth: usernameHeader.width
|
|
|
|
readonly property alias holdingHeaderWidth: holdingHeader.width
|
|
|
|
background: Rectangle {
|
|
|
|
id: scrollingSeparator
|
|
|
|
width: parent.width
|
|
|
|
height: 4
|
|
|
|
anchors.bottom: parent.bottom
|
|
|
|
color: Theme.palette.baseColor2
|
|
|
|
visible: (listView.contentY > 0)
|
|
|
|
}
|
|
|
|
contentItem: Item {
|
|
|
|
anchors.fill: parent
|
2024-10-15 19:26:12 +00:00
|
|
|
anchors.leftMargin: Theme.padding
|
|
|
|
anchors.rightMargin: Theme.padding
|
2023-07-05 15:17:51 +00:00
|
|
|
clip: true
|
|
|
|
RowLayout {
|
|
|
|
id: row
|
|
|
|
anchors.fill: parent
|
2024-10-15 19:26:12 +00:00
|
|
|
spacing: Theme.padding
|
2023-07-05 15:17:51 +00:00
|
|
|
RowLayout {
|
|
|
|
id: usernameHeader
|
|
|
|
|
|
|
|
ColumnHeader {
|
|
|
|
text: qsTr("Username")
|
|
|
|
|
|
|
|
traversalOrder: [
|
|
|
|
StatusSortableColumnHeader.Sorting.NoSorting,
|
|
|
|
StatusSortableColumnHeader.Sorting.Ascending,
|
|
|
|
StatusSortableColumnHeader.Sorting.Descending
|
|
|
|
]
|
|
|
|
|
|
|
|
onClicked: {
|
|
|
|
if (sorting !== StatusSortableColumnHeader.Sorting.NoSorting)
|
|
|
|
d.sortBy = TokenHoldersProxyModel.SortBy.Username
|
|
|
|
else
|
|
|
|
d.sortBy = TokenHoldersProxyModel.SortBy.None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Item { Layout.fillWidth: true }
|
|
|
|
}
|
|
|
|
|
|
|
|
ColumnHeader {
|
|
|
|
id: holdingHeader
|
|
|
|
text: qsTr("Hodling")
|
|
|
|
onClicked: {
|
|
|
|
if (sorting !== StatusSortableColumnHeader.Sorting.NoSorting)
|
|
|
|
d.sortBy = TokenHoldersProxyModel.SortBy.Holding
|
|
|
|
else
|
|
|
|
d.sortBy = TokenHoldersProxyModel.SortBy.None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Item {
|
|
|
|
Layout.preferredWidth: 233
|
2024-10-15 19:26:12 +00:00
|
|
|
Layout.rightMargin: Theme.halfPadding
|
2023-07-05 15:17:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
StatusListView {
|
|
|
|
id: listView
|
|
|
|
anchors.fill: parent
|
|
|
|
anchors.topMargin: header.height
|
|
|
|
currentIndex: -1
|
|
|
|
component ColumnHeader: StatusSortableColumnHeader {
|
|
|
|
id: columnHeader
|
|
|
|
|
|
|
|
leftPadding: 0
|
|
|
|
rightPadding: 0
|
|
|
|
Connections {
|
|
|
|
target: d
|
|
|
|
|
|
|
|
function onResetOtherHeaders(header) {
|
|
|
|
if (header !== columnHeader)
|
|
|
|
columnHeader.reset()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
onClicked: {
|
|
|
|
d.resetOtherHeaders(this)
|
|
|
|
|
|
|
|
if (sorting === StatusSortableColumnHeader.Sorting.Ascending)
|
|
|
|
d.sorting = Qt.AscendingOrder
|
|
|
|
else if (sorting === StatusSortableColumnHeader.Sorting.Descending)
|
|
|
|
d.sorting = Qt.DescendingOrder
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
delegate: ItemDelegate {
|
|
|
|
id: delegate
|
|
|
|
width: ListView.view.width
|
|
|
|
height: d.delegateHeight
|
|
|
|
padding: 0
|
|
|
|
readonly property string name: model.name
|
|
|
|
|
|
|
|
readonly property bool isFirstRowAddress: {
|
|
|
|
if (model.name !== "")
|
|
|
|
return false
|
|
|
|
|
|
|
|
const item = listView.itemAtIndex(index - 1)
|
|
|
|
return item && item.name
|
|
|
|
}
|
|
|
|
|
|
|
|
readonly property bool showSeparator: isFirstRowAddress
|
|
|
|
&& root.sortBy === TokenHoldersProxyModel.SortBy.Username
|
|
|
|
|
|
|
|
|
|
|
|
background: Item {
|
|
|
|
Rectangle {
|
|
|
|
anchors.fill: parent
|
2024-10-15 19:26:12 +00:00
|
|
|
radius: Theme.radius
|
2023-07-05 15:17:51 +00:00
|
|
|
color: (delegate.hovered || delegate.ListView.isCurrentItem)
|
|
|
|
? Theme.palette.baseColor2 : "transparent"
|
|
|
|
}
|
|
|
|
|
|
|
|
Rectangle {
|
|
|
|
visible: delegate.showSeparator
|
|
|
|
anchors.top: parent.top
|
|
|
|
anchors.left: parent.left
|
|
|
|
anchors.right: parent.right
|
|
|
|
anchors.topMargin: delegate.topPadding / 2
|
|
|
|
|
|
|
|
height: 1
|
|
|
|
color: Theme.palette.baseColor2
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
contentItem: Item {
|
|
|
|
anchors.left: parent.left
|
|
|
|
anchors.right: parent.right
|
2024-10-15 19:26:12 +00:00
|
|
|
anchors.leftMargin: Theme.padding
|
|
|
|
anchors.rightMargin: Theme.padding
|
2023-07-05 15:17:51 +00:00
|
|
|
RowLayout {
|
2024-10-15 19:26:12 +00:00
|
|
|
spacing: Theme.halfPadding
|
2023-07-05 15:17:51 +00:00
|
|
|
|
|
|
|
StatusListItem {
|
|
|
|
readonly property bool unknownHolder: model.name === ""
|
|
|
|
readonly property string formattedTitle: unknownHolder ? "?" : model.name
|
|
|
|
Layout.preferredWidth: header.usernameHeaderWidth
|
|
|
|
color: "transparent"
|
|
|
|
leftPadding: 0
|
|
|
|
rightPadding: 0
|
|
|
|
sensor.enabled: false
|
|
|
|
title: formattedTitle
|
|
|
|
statusListItemTitle.visible: !unknownHolder
|
|
|
|
subTitle: Utils.getElidedPk(model.walletAddress)
|
|
|
|
asset.name: model.imageSource
|
|
|
|
asset.isImage: true
|
|
|
|
asset.isLetterIdenticon: !asset.name
|
|
|
|
asset.color: Theme.palette.getColor("red2")
|
|
|
|
}
|
|
|
|
|
2023-08-22 15:34:37 +00:00
|
|
|
TokenHolderNumberCell {
|
2023-07-05 15:17:51 +00:00
|
|
|
Layout.preferredWidth: header.holdingHeaderWidth
|
2024-10-15 19:26:12 +00:00
|
|
|
Layout.leftMargin: Theme.halfPadding
|
2023-07-05 15:17:51 +00:00
|
|
|
|
|
|
|
text: LocaleUtils.numberToLocaleString(model.amount)
|
|
|
|
}
|
|
|
|
|
|
|
|
Item { Layout.preferredWidth: 100 }
|
|
|
|
|
|
|
|
StatusComboBox {
|
|
|
|
id: combo
|
|
|
|
Layout.preferredWidth: 68
|
|
|
|
Layout.preferredHeight: 44
|
2024-10-15 19:26:12 +00:00
|
|
|
control.spacing: Theme.halfPadding / 2
|
2023-07-05 15:17:51 +00:00
|
|
|
model: amount
|
|
|
|
size: StatusComboBox.Size.Small
|
|
|
|
type: StatusComboBox.Type.Secondary
|
|
|
|
delegate: StatusItemDelegate {
|
|
|
|
width: combo.control.width
|
|
|
|
centerTextHorizontally: true
|
|
|
|
highlighted: combo.control.highlightedIndex === index
|
|
|
|
font: combo.control.font
|
|
|
|
text: Number(modelData) + 1
|
|
|
|
}
|
|
|
|
contentItem: StatusBaseText {
|
|
|
|
id: comboText
|
|
|
|
font: combo.control.font
|
|
|
|
verticalAlignment: Text.AlignVCenter
|
|
|
|
elide: Text.ElideRight
|
|
|
|
color: Theme.palette.baseColor1
|
|
|
|
Component.onCompleted: {
|
|
|
|
if (d.selectedTokenAmount.get(walletAddress) === undefined) {
|
|
|
|
d.selectedTokenAmount.set(walletAddress, amount);
|
|
|
|
}
|
|
|
|
text = d.selectedTokenAmount.get(walletAddress);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
control.onActivated: {
|
|
|
|
d.selectedTokenAmount.set(walletAddress, (index+1));
|
|
|
|
comboText.text = d.selectedTokenAmount.get(walletAddress);
|
|
|
|
if (checkBox.checked) {
|
|
|
|
root.selfDestructAmountChanged(walletAddress, d.selectedTokenAmount.get(walletAddress))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Item { Layout.preferredWidth: 28 }
|
|
|
|
StatusCheckBox {
|
|
|
|
id: checkBox
|
|
|
|
Layout.preferredWidth: 24
|
|
|
|
Layout.preferredHeight: 24
|
|
|
|
Layout.alignment: Qt.AlignRight
|
|
|
|
visible: root.isSelectorMode
|
|
|
|
padding: 0
|
|
|
|
onCheckStateChanged: {
|
|
|
|
if (checked)
|
|
|
|
root.selfDestructAmountChanged(model.walletAddress, d.selectedTokenAmount.get(walletAddress))
|
|
|
|
else
|
|
|
|
root.selfDestructRemoved(model.walletAddress)
|
|
|
|
|
|
|
|
d.selectedTokenChecked.set(walletAddress, checked);
|
|
|
|
}
|
|
|
|
Component.onCompleted: {
|
|
|
|
checked = !!d.selectedTokenChecked.get(walletAddress);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|