379 lines
12 KiB
QML
379 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.Controls 0.1
|
|||
|
import StatusQ.Core 0.1
|
|||
|
import StatusQ.Core.Theme 0.1
|
|||
|
import StatusQ.Core.Utils 0.1
|
|||
|
import StatusQ.Popups.Dialog 0.1
|
|||
|
|
|||
|
import AppLayouts.Wallet.views 1.0
|
|||
|
import shared.controls 1.0
|
|||
|
import utils 1.0
|
|||
|
|
|||
|
import SortFilterProxyModel 0.2
|
|||
|
|
|||
|
/**
|
|||
|
Two-tabs panel holding searchable lists of assets (single level) and
|
|||
|
collectibles (two levels).
|
|||
|
|
|||
|
Structure:
|
|||
|
|
|||
|
TabBar (assets, collectibles)
|
|||
|
StackLayout (current index bound to tab bar's current index)
|
|||
|
Assets List (assets part)
|
|||
|
StackView (collectibles part)
|
|||
|
Collectibles List (top level - groups by collection/community)
|
|||
|
Collectibles List (nested level, on demand)
|
|||
|
*/
|
|||
|
Control {
|
|||
|
id: root
|
|||
|
|
|||
|
enum Tabs {
|
|||
|
Assets = 0,
|
|||
|
Collectibles = 1
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
Expected model structure:
|
|||
|
|
|||
|
tokensKey [string] - unique asset's identifier
|
|||
|
name [string] - asset's name
|
|||
|
symbol [string] - asset's symbol
|
|||
|
iconSource [url] - asset's icon
|
|||
|
currencyBalanceAsString [string] - formatted balance
|
|||
|
balances [model] - submodel of balances per chain
|
|||
|
balanceAsString [string] - formatted balance per chain
|
|||
|
iconUrl [url] - chain's icon
|
|||
|
**/
|
|||
|
property alias assetsModel: assetsSfpm.sourceModel
|
|||
|
|
|||
|
/**
|
|||
|
Expected model structure:
|
|||
|
|
|||
|
groupName [string] - group name
|
|||
|
icon [url] - icon image of a group
|
|||
|
type [string] - group type, can be "community" or "other"
|
|||
|
subitems [model] - submodel of collectibles/collections of the group
|
|||
|
key [string] - balance
|
|||
|
name [string] - name of the subitem
|
|||
|
balance [int] - balance of the subitem
|
|||
|
icon [url] - icon of the subitem
|
|||
|
**/
|
|||
|
property alias collectiblesModel: collectiblesSfpm.sourceModel
|
|||
|
|
|||
|
// Index of the current tab, indexes correspond to the Tabs enum values.
|
|||
|
property alias currentTab: tabBar.currentIndex
|
|||
|
|
|||
|
signal assetSelected(string key)
|
|||
|
signal collectionSelected(string key)
|
|||
|
signal collectibleSelected(string key)
|
|||
|
|
|||
|
property string highlightedKey: ""
|
|||
|
|
|||
|
SortFilterProxyModel {
|
|||
|
id: assetsSfpm
|
|||
|
|
|||
|
filters: AnyOf {
|
|||
|
SearchFilter {
|
|||
|
roleName: "name"
|
|||
|
searchPhrase: assetsSearchBox.text
|
|||
|
}
|
|||
|
SearchFilter {
|
|||
|
roleName: "symbol"
|
|||
|
searchPhrase: assetsSearchBox.text
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
SortFilterProxyModel {
|
|||
|
id: collectiblesSfpm
|
|||
|
|
|||
|
filters: SearchFilter {
|
|||
|
roleName: "groupName"
|
|||
|
searchPhrase: collectiblesSearchBox.text
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
component SearchFilter: RegExpFilter {
|
|||
|
required property string searchPhrase
|
|||
|
|
|||
|
pattern: `*${searchPhrase}*`
|
|||
|
caseSensitivity : Qt.CaseInsensitive
|
|||
|
syntax: RegExpFilter.Wildcard
|
|||
|
}
|
|||
|
|
|||
|
component Search: SearchBox {
|
|||
|
input.leftPadding: root.leftPadding
|
|||
|
input.rightPadding: root.leftPadding
|
|||
|
minimumHeight: 56
|
|||
|
maximumHeight: 56
|
|||
|
input.showBackground: false
|
|||
|
focus: visible
|
|||
|
}
|
|||
|
|
|||
|
contentItem: ColumnLayout {
|
|||
|
StatusTabBar {
|
|||
|
id: tabBar
|
|||
|
|
|||
|
visible: !!root.assetsModel && !!root.collectiblesModel
|
|||
|
|
|||
|
currentIndex: !!root.assetsModel
|
|||
|
? TokenSelectorPanel.Tabs.Assets
|
|||
|
: TokenSelectorPanel.Tabs.Collectibles
|
|||
|
|
|||
|
StatusTabButton {
|
|||
|
text: qsTr("Assets")
|
|||
|
width: implicitWidth
|
|||
|
|
|||
|
visible: !!root.assetsModel
|
|||
|
}
|
|||
|
|
|||
|
StatusTabButton {
|
|||
|
text: qsTr("Collectibles")
|
|||
|
width: implicitWidth
|
|||
|
|
|||
|
visible: !!root.collectiblesModel
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
StackLayout {
|
|||
|
Layout.maximumHeight: 400
|
|||
|
|
|||
|
visible: !!root.assetsModel || !!root.collectiblesModel
|
|||
|
currentIndex: tabBar.currentIndex
|
|||
|
|
|||
|
ColumnLayout {
|
|||
|
Layout.preferredHeight: visible ? implicitHeight : 0
|
|||
|
spacing: 0
|
|||
|
|
|||
|
Search {
|
|||
|
id: assetsSearchBox
|
|||
|
|
|||
|
Layout.fillWidth: true
|
|||
|
placeholderText: qsTr("Search assets")
|
|||
|
}
|
|||
|
|
|||
|
StatusDialogDivider {
|
|||
|
Layout.fillWidth: true
|
|||
|
visible: assetsListView.count
|
|||
|
}
|
|||
|
|
|||
|
StatusListView {
|
|||
|
id: assetsListView
|
|||
|
|
|||
|
clip: true
|
|||
|
|
|||
|
Layout.fillWidth: true
|
|||
|
Layout.fillHeight: true
|
|||
|
Layout.preferredHeight: contentHeight
|
|||
|
|
|||
|
model: assetsSfpm
|
|||
|
|
|||
|
delegate: TokenSelectorAssetDelegate {
|
|||
|
required property var model
|
|||
|
required property int index
|
|||
|
|
|||
|
highlighted: tokensKey === root.highlightedKey
|
|||
|
|
|||
|
tokensKey: model.tokensKey
|
|||
|
name: model.name
|
|||
|
symbol: model.symbol
|
|||
|
currencyBalanceAsString: model.currencyBalanceAsString
|
|||
|
iconSource: model.iconSource
|
|||
|
balancesModel: model.balances
|
|||
|
|
|||
|
onClicked: root.assetSelected(model.tokensKey)
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
StackView {
|
|||
|
id: collectiblesStackView
|
|||
|
|
|||
|
Layout.fillWidth: true
|
|||
|
Layout.fillHeight: true
|
|||
|
Layout.preferredHeight: visible ? currentItem.implicitHeight : 0
|
|||
|
|
|||
|
initialItem: ColumnLayout {
|
|||
|
spacing: 0
|
|||
|
|
|||
|
Search {
|
|||
|
id: collectiblesSearchBox
|
|||
|
|
|||
|
Layout.fillWidth: true
|
|||
|
placeholderText: qsTr("Search collectibles")
|
|||
|
}
|
|||
|
|
|||
|
StatusDialogDivider {
|
|||
|
Layout.fillWidth: true
|
|||
|
visible: collectiblesListView.count
|
|||
|
}
|
|||
|
|
|||
|
StatusListView {
|
|||
|
id: collectiblesListView
|
|||
|
|
|||
|
Layout.fillWidth: true
|
|||
|
Layout.fillHeight: true
|
|||
|
Layout.preferredHeight: contentHeight
|
|||
|
|
|||
|
clip: true
|
|||
|
model: collectiblesSfpm
|
|||
|
|
|||
|
delegate: TokenSelectorCollectibleDelegate {
|
|||
|
required property var model
|
|||
|
|
|||
|
readonly property int subitemsCount:
|
|||
|
model.subitems.ModelCount.count
|
|||
|
|
|||
|
readonly property bool isCommunity:
|
|||
|
model.type === "community"
|
|||
|
|
|||
|
readonly property bool showCount:
|
|||
|
subitemsCount > 1 || isCommunity
|
|||
|
|
|||
|
name: model.groupName
|
|||
|
balance: showCount ? subitemsCount : ""
|
|||
|
image: model.icon
|
|||
|
goDeeperIconVisible: subitemsCount > 1
|
|||
|
|| isCommunity
|
|||
|
highlighted: subitemsCount === 1 && !isCommunity
|
|||
|
? ModelUtils.get(model.subitems, 0, "key")
|
|||
|
=== root.highlightedKey
|
|||
|
: false
|
|||
|
|
|||
|
onClicked: {
|
|||
|
if (subitemsCount === 1 && !isCommunity) {
|
|||
|
const key = ModelUtils.get(model.subitems, 0, "key")
|
|||
|
root.collectibleSelected(key)
|
|||
|
return
|
|||
|
}
|
|||
|
|
|||
|
const parameters = {
|
|||
|
index: collectiblesSfpm.index(model.index, 0),
|
|||
|
model: model.subitems,
|
|||
|
isCommunity: isCommunity
|
|||
|
}
|
|||
|
|
|||
|
collectiblesStackView.push(
|
|||
|
collectiblesSublistComponent,
|
|||
|
parameters,
|
|||
|
StackView.Immediate)
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
section.property: "type"
|
|||
|
section.delegate: StatusBaseText {
|
|||
|
id: sectionTitle
|
|||
|
|
|||
|
color: Theme.palette.baseColor1
|
|||
|
topPadding: Style.current.padding
|
|||
|
|
|||
|
text: section === "community"
|
|||
|
? qsTr("Community minted")
|
|||
|
: qsTr("Other")
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
Component {
|
|||
|
id: collectiblesSublistComponent
|
|||
|
|
|||
|
ColumnLayout {
|
|||
|
property var index
|
|||
|
property alias model: sublistSfpm.sourceModel
|
|||
|
property bool isCommunity
|
|||
|
|
|||
|
spacing: 0
|
|||
|
|
|||
|
SortFilterProxyModel {
|
|||
|
id: sublistSfpm
|
|||
|
|
|||
|
filters: SearchFilter {
|
|||
|
roleName: "name"
|
|||
|
searchPhrase: collectiblesSublistSearchBox.text
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
StatusIconTextButton {
|
|||
|
id: backButton
|
|||
|
|
|||
|
statusIcon: "previous"
|
|||
|
icon.width: 12
|
|||
|
icon.height: 12
|
|||
|
text: qsTr("Back")
|
|||
|
|
|||
|
onClicked: collectiblesStackView.pop(StackView.Immediate)
|
|||
|
}
|
|||
|
|
|||
|
StatusDialogDivider {
|
|||
|
Layout.fillWidth: true
|
|||
|
visible: collectiblesListView.count
|
|||
|
}
|
|||
|
|
|||
|
Search {
|
|||
|
id: collectiblesSublistSearchBox
|
|||
|
|
|||
|
Layout.fillWidth: true
|
|||
|
placeholderText: qsTr("Search collectibles")
|
|||
|
}
|
|||
|
|
|||
|
StatusDialogDivider {
|
|||
|
Layout.fillWidth: true
|
|||
|
visible: collectiblesListView.count
|
|||
|
}
|
|||
|
|
|||
|
StatusListView {
|
|||
|
id: sublist
|
|||
|
|
|||
|
Layout.fillWidth: true
|
|||
|
Layout.fillHeight: true
|
|||
|
Layout.preferredHeight: contentHeight
|
|||
|
|
|||
|
model: sublistSfpm
|
|||
|
|
|||
|
clip: true
|
|||
|
|
|||
|
delegate: TokenSelectorCollectibleDelegate {
|
|||
|
required property var model
|
|||
|
|
|||
|
name: model.name
|
|||
|
balance: model.balance > 1 ? model.balance : ""
|
|||
|
image: model.icon
|
|||
|
goDeeperIconVisible: false
|
|||
|
highlighted: model.key === root.highlightedKey
|
|||
|
|
|||
|
onClicked: {
|
|||
|
if (isCommunity)
|
|||
|
root.collectionSelected(model.key)
|
|||
|
else
|
|||
|
root.collectibleSelected(model.key)
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// Detection if the related model entry has been removed.
|
|||
|
// Using model.Component.destruction.connect is not reliable because
|
|||
|
// is not called for submodels maintained in c++ by the parent model.
|
|||
|
ItemSelectionModel {
|
|||
|
id: selection
|
|||
|
|
|||
|
model: collectiblesSfpm
|
|||
|
|
|||
|
onHasSelectionChanged: {
|
|||
|
if (!hasSelection)
|
|||
|
collectiblesStackView.pop(StackView.Immediate)
|
|||
|
}
|
|||
|
|
|||
|
Component.onCompleted: select(index, ItemSelectionModel.Select)
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|