TokenSelectorNew decomposed into smaller, reusable subcomponents

This commit is contained in:
Michał Cieślak 2024-08-30 17:31:53 +02:00 committed by Michał
parent afd16d6f6c
commit 01a24e1b19
10 changed files with 366 additions and 305 deletions

View File

@ -0,0 +1,9 @@
import SortFilterProxyModel 0.2
RegExpFilter {
required property string searchPhrase
pattern: `*${searchPhrase}*`
caseSensitivity: Qt.CaseInsensitive
syntax: RegExpFilter.Wildcard
}

View File

@ -9,6 +9,7 @@ ModelChangeGuard 0.1 ModelChangeGuard.qml
ModelChangeTracker 0.1 ModelChangeTracker.qml ModelChangeTracker 0.1 ModelChangeTracker.qml
ModelsComparator 0.1 ModelsComparator.qml ModelsComparator 0.1 ModelsComparator.qml
QObject 0.1 QObject.qml QObject 0.1 QObject.qml
SearchFilter 0.1 SearchFilter.qml
StackViewStates 0.1 StackViewStates.qml StackViewStates 0.1 StackViewStates.qml
StatesStack 0.1 StatesStack.qml StatesStack 0.1 StatesStack.qml
Subscription 0.1 Subscription.qml Subscription 0.1 Subscription.qml

View File

@ -195,7 +195,6 @@
<file>StatusQ/Core/Utils/ClippingWrapper.qml</file> <file>StatusQ/Core/Utils/ClippingWrapper.qml</file>
<file>StatusQ/Core/Utils/DoubleFlickable.qml</file> <file>StatusQ/Core/Utils/DoubleFlickable.qml</file>
<file>StatusQ/Core/Utils/DoubleFlickableWithFolding.qml</file> <file>StatusQ/Core/Utils/DoubleFlickableWithFolding.qml</file>
<file>StatusQ/Core/Utils/internal/HeaderWrapper.qml</file>
<file>StatusQ/Core/Utils/Emoji.qml</file> <file>StatusQ/Core/Utils/Emoji.qml</file>
<file>StatusQ/Core/Utils/JSONListModel.qml</file> <file>StatusQ/Core/Utils/JSONListModel.qml</file>
<file>StatusQ/Core/Utils/ModelChangeGuard.qml</file> <file>StatusQ/Core/Utils/ModelChangeGuard.qml</file>
@ -204,6 +203,7 @@
<file>StatusQ/Core/Utils/ModelsComparator.qml</file> <file>StatusQ/Core/Utils/ModelsComparator.qml</file>
<file>StatusQ/Core/Utils/OperatorsUtils.qml</file> <file>StatusQ/Core/Utils/OperatorsUtils.qml</file>
<file>StatusQ/Core/Utils/QObject.qml</file> <file>StatusQ/Core/Utils/QObject.qml</file>
<file>StatusQ/Core/Utils/SearchFilter.qml</file>
<file>StatusQ/Core/Utils/StackViewStates.qml</file> <file>StatusQ/Core/Utils/StackViewStates.qml</file>
<file>StatusQ/Core/Utils/StatesStack.qml</file> <file>StatusQ/Core/Utils/StatesStack.qml</file>
<file>StatusQ/Core/Utils/StringUtils.qml</file> <file>StatusQ/Core/Utils/StringUtils.qml</file>
@ -212,6 +212,7 @@
<file>StatusQ/Core/Utils/Utils.qml</file> <file>StatusQ/Core/Utils/Utils.qml</file>
<file>StatusQ/Core/Utils/big.min.mjs</file> <file>StatusQ/Core/Utils/big.min.mjs</file>
<file>StatusQ/Core/Utils/emojiList.js</file> <file>StatusQ/Core/Utils/emojiList.js</file>
<file>StatusQ/Core/Utils/internal/HeaderWrapper.qml</file>
<file>StatusQ/Core/Utils/qmldir</file> <file>StatusQ/Core/Utils/qmldir</file>
<file>StatusQ/Core/Utils/xss.js</file> <file>StatusQ/Core/Utils/xss.js</file>
<file>StatusQ/Core/qmldir</file> <file>StatusQ/Core/qmldir</file>

View File

@ -122,11 +122,9 @@ StatusDropdown {
sourceModel: joined sourceModel: joined
filters: RegExpFilter { filters: SearchFilter {
roleName: "name" roleName: "name"
pattern: `*${searcher.text}*` searchPhrase: searcher.text
caseSensitivity : Qt.CaseInsensitive
syntax: RegExpFilter.Wildcard
} }
} }

View File

@ -11,10 +11,10 @@ import utils 1.0
Control { Control {
id: root id: root
/** Expected model structure: see TokenSelectorPanel::assetsModel **/ /** Expected model structure: see SearchableAssetsPanel::model **/
property alias assetsModel: tokenSelectorPanel.assetsModel property alias assetsModel: tokenSelectorPanel.assetsModel
/** Expected model structure: see TokenSelectorPanel::collectiblesModel **/ /** Expected model structure: see SearchableCollectiblesPanel::model **/
property alias collectiblesModel: tokenSelectorPanel.collectiblesModel property alias collectiblesModel: tokenSelectorPanel.collectiblesModel
readonly property bool isTokenSelected: d.isTokenSelected readonly property bool isTokenSelected: d.isTokenSelected

View File

@ -0,0 +1,94 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import StatusQ.Core 0.1
import StatusQ.Core.Utils 0.1
import StatusQ.Popups.Dialog 0.1
import AppLayouts.Wallet.views 1.0
import SortFilterProxyModel 0.2
/**
Panel holding search field and lists of assets.
*/
Control {
id: root
/**
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 model: sfpm.sourceModel
signal selected(string key)
property string highlightedKey: ""
SortFilterProxyModel {
id: sfpm
filters: AnyOf {
SearchFilter {
roleName: "name"
searchPhrase: searchBox.text
}
SearchFilter {
roleName: "symbol"
searchPhrase: searchBox.text
}
}
}
contentItem: ColumnLayout {
spacing: 0
TokenSearchBox {
id: searchBox
Layout.fillWidth: true
placeholderText: qsTr("Search assets")
}
StatusDialogDivider {
Layout.fillWidth: true
visible: listView.count
}
StatusListView {
id: listView
clip: true
Layout.fillWidth: true
Layout.fillHeight: true
Layout.preferredHeight: contentHeight
model: sfpm
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.selected(model.tokensKey)
}
}
}
}

View File

@ -0,0 +1,230 @@
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 utils 1.0
import SortFilterProxyModel 0.2
/**
Panel holding search field and two-levels list of collectibles.
*/
Control {
id: root
/**
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 model: sfpm.sourceModel
signal collectionSelected(string key)
signal collectibleSelected(string key)
property string highlightedKey: ""
readonly property alias currentItem: collectiblesStackView.currentItem
SortFilterProxyModel {
id: sfpm
filters: SearchFilter {
roleName: "groupName"
searchPhrase: collectiblesSearchBox.text
}
}
contentItem: StackView {
id: collectiblesStackView
initialItem: ColumnLayout {
spacing: 0
TokenSearchBox {
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: sfpm
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: sfpm.index(model.index, 0),
model: model.subitems,
isCommunity: isCommunity
}
collectiblesStackView.push(
collectiblesSublistComponent,
parameters,
StackView.Immediate)
}
}
section.property: "type"
section.delegate: StatusBaseText {
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
}
TokenSearchBox {
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: sfpm
onHasSelectionChanged: {
if (!hasSelection)
collectiblesStackView.pop(StackView.Immediate)
}
Component.onCompleted: select(index, ItemSelectionModel.Select)
}
}
}
}

View File

@ -0,0 +1,10 @@
import shared.controls 1.0
SearchBox {
input.leftPadding: 0
input.rightPadding: 0
minimumHeight: 56
maximumHeight: 56
input.showBackground: false
focus: visible
}

View File

@ -1,20 +1,8 @@
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 QtQml.Models 2.15
import StatusQ 0.1
import StatusQ.Controls 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 Two-tabs panel holding searchable lists of assets (single level) and
@ -37,33 +25,11 @@ Control {
Collectibles = 1 Collectibles = 1
} }
/** /** Expected model structure: see SearchableAssetsPanel::model **/
Expected model structure: property alias assetsModel: searchableAssetsPanel.model
tokensKey [string] - unique asset's identifier /** Expected model structure: see SearchableCollectiblesPanel::model **/
name [string] - asset's name property alias collectiblesModel: searchableCollectiblesPanel.model
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. // Index of the current tab, indexes correspond to the Tabs enum values.
property alias currentTab: tabBar.currentIndex property alias currentTab: tabBar.currentIndex
@ -74,47 +40,6 @@ Control {
property string highlightedKey: "" 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 { contentItem: ColumnLayout {
StatusTabBar { StatusTabBar {
id: tabBar id: tabBar
@ -154,230 +79,21 @@ Control {
visible: !!root.assetsModel || !!root.collectiblesModel visible: !!root.assetsModel || !!root.collectiblesModel
currentIndex: tabBar.currentIndex currentIndex: tabBar.currentIndex
ColumnLayout { SearchableAssetsPanel {
id: searchableAssetsPanel
Layout.preferredHeight: visible ? implicitHeight : 0 Layout.preferredHeight: visible ? implicitHeight : 0
spacing: 0
Search { onSelected: root.assetSelected(key)
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 { SearchableCollectiblesPanel {
id: collectiblesStackView id: searchableCollectiblesPanel
Layout.fillWidth: true
Layout.fillHeight: true
Layout.preferredHeight: visible ? currentItem.implicitHeight : 0 Layout.preferredHeight: visible ? currentItem.implicitHeight : 0
initialItem: ColumnLayout { onCollectibleSelected: root.collectibleSelected(key)
spacing: 0 onCollectionSelected: root.collectionSelected(key)
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 {
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)
} }
} }
} }

View File

@ -1,14 +1,16 @@
ActivityFilterPanel 1.0 ActivityFilterPanel.qml ActivityFilterPanel 1.0 ActivityFilterPanel.qml
BuyCryptoProvidersListPanel 1.0 BuyCryptoProvidersListPanel.qml
ContractInfoButtonWithMenu 1.0 ContractInfoButtonWithMenu.qml ContractInfoButtonWithMenu 1.0 ContractInfoButtonWithMenu.qml
DAppsWorkflow 1.0 DAppsWorkflow.qml DAppsWorkflow 1.0 DAppsWorkflow.qml
ManageAssetsPanel 1.0 ManageAssetsPanel.qml ManageAssetsPanel 1.0 ManageAssetsPanel.qml
ManageCollectiblesPanel 1.0 ManageCollectiblesPanel.qml ManageCollectiblesPanel 1.0 ManageCollectiblesPanel.qml
ManageHiddenPanel 1.0 ManageHiddenPanel.qml ManageHiddenPanel 1.0 ManageHiddenPanel.qml
SearchableAssetsPanel 1.0 SearchableAssetsPanel.qml
SearchableCollectiblesPanel 1.0 SearchableCollectiblesPanel.qml
SelectParamsForBuyCryptoPanel 1.0 SelectParamsForBuyCryptoPanel.qml
SignInfoBox 1.0 SignInfoBox.qml SignInfoBox 1.0 SignInfoBox.qml
SwapInputPanel 1.0 SwapInputPanel.qml SwapInputPanel 1.0 SwapInputPanel.qml
TokenSelectorPanel 1.0 TokenSelectorPanel.qml TokenSelectorPanel 1.0 TokenSelectorPanel.qml
WalletHeader 1.0 WalletHeader.qml WalletHeader 1.0 WalletHeader.qml
WalletNftPreview 1.0 WalletNftPreview.qml WalletNftPreview 1.0 WalletNftPreview.qml
WalletTxProgressBlock 1.0 WalletTxProgressBlock.qml WalletTxProgressBlock 1.0 WalletTxProgressBlock.qml
BuyCryptoProvidersListPanel 1.0 BuyCryptoProvidersListPanel.qml
SelectParamsForBuyCryptoPanel 1.0 SelectParamsForBuyCryptoPanel.qml