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
ModelsComparator 0.1 ModelsComparator.qml
QObject 0.1 QObject.qml
SearchFilter 0.1 SearchFilter.qml
StackViewStates 0.1 StackViewStates.qml
StatesStack 0.1 StatesStack.qml
Subscription 0.1 Subscription.qml

View File

@ -195,7 +195,6 @@
<file>StatusQ/Core/Utils/ClippingWrapper.qml</file>
<file>StatusQ/Core/Utils/DoubleFlickable.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/JSONListModel.qml</file>
<file>StatusQ/Core/Utils/ModelChangeGuard.qml</file>
@ -204,6 +203,7 @@
<file>StatusQ/Core/Utils/ModelsComparator.qml</file>
<file>StatusQ/Core/Utils/OperatorsUtils.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/StatesStack.qml</file>
<file>StatusQ/Core/Utils/StringUtils.qml</file>
@ -212,6 +212,7 @@
<file>StatusQ/Core/Utils/Utils.qml</file>
<file>StatusQ/Core/Utils/big.min.mjs</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/xss.js</file>
<file>StatusQ/Core/qmldir</file>

View File

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

View File

@ -11,10 +11,10 @@ import utils 1.0
Control {
id: root
/** Expected model structure: see TokenSelectorPanel::assetsModel **/
/** Expected model structure: see SearchableAssetsPanel::model **/
property alias assetsModel: tokenSelectorPanel.assetsModel
/** Expected model structure: see TokenSelectorPanel::collectiblesModel **/
/** Expected model structure: see SearchableCollectiblesPanel::model **/
property alias collectiblesModel: tokenSelectorPanel.collectiblesModel
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.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
@ -37,33 +25,11 @@ Control {
Collectibles = 1
}
/**
Expected model structure:
/** Expected model structure: see SearchableAssetsPanel::model **/
property alias assetsModel: searchableAssetsPanel.model
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
/** Expected model structure: see SearchableCollectiblesPanel::model **/
property alias collectiblesModel: searchableCollectiblesPanel.model
// Index of the current tab, indexes correspond to the Tabs enum values.
property alias currentTab: tabBar.currentIndex
@ -74,47 +40,6 @@ Control {
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
@ -154,230 +79,21 @@ Control {
visible: !!root.assetsModel || !!root.collectiblesModel
currentIndex: tabBar.currentIndex
ColumnLayout {
SearchableAssetsPanel {
id: searchableAssetsPanel
Layout.preferredHeight: visible ? implicitHeight : 0
spacing: 0
Search {
id: assetsSearchBox
Layout.fillWidth: true
placeholderText: qsTr("Search assets")
onSelected: root.assetSelected(key)
}
StatusDialogDivider {
Layout.fillWidth: true
visible: assetsListView.count
}
SearchableCollectiblesPanel {
id: searchableCollectiblesPanel
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 {
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)
onCollectibleSelected: root.collectibleSelected(key)
onCollectionSelected: root.collectionSelected(key)
}
}
}

View File

@ -1,14 +1,16 @@
ActivityFilterPanel 1.0 ActivityFilterPanel.qml
BuyCryptoProvidersListPanel 1.0 BuyCryptoProvidersListPanel.qml
ContractInfoButtonWithMenu 1.0 ContractInfoButtonWithMenu.qml
DAppsWorkflow 1.0 DAppsWorkflow.qml
ManageAssetsPanel 1.0 ManageAssetsPanel.qml
ManageCollectiblesPanel 1.0 ManageCollectiblesPanel.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
SwapInputPanel 1.0 SwapInputPanel.qml
TokenSelectorPanel 1.0 TokenSelectorPanel.qml
WalletHeader 1.0 WalletHeader.qml
WalletNftPreview 1.0 WalletNftPreview.qml
WalletTxProgressBlock 1.0 WalletTxProgressBlock.qml
BuyCryptoProvidersListPanel 1.0 BuyCryptoProvidersListPanel.qml
SelectParamsForBuyCryptoPanel 1.0 SelectParamsForBuyCryptoPanel.qml