feat(Community Permissions): Add search option in assets / collectibles dropdown

Closes: #9042
This commit is contained in:
Michał Cieślak 2023-01-26 15:21:35 +01:00 committed by Michał
parent fc5dbf0d2c
commit bd6dc02162
5 changed files with 287 additions and 152 deletions

View File

@ -44,6 +44,18 @@ ListModel {
iconSource: ModelsData.collectibles.kitty5, iconSource: ModelsData.collectibles.kitty5,
imageSource: ModelsData.collectibles.kitty5Big, imageSource: ModelsData.collectibles.kitty5Big,
name: "Magicat-3" name: "Magicat-3"
},
{
key: "Kitty5",
iconSource: ModelsData.collectibles.kitty4,
imageSource: ModelsData.collectibles.kitty4Big,
name: "Furbeard-3"
},
{
key: "Kitty6",
iconSource: ModelsData.collectibles.kitty5,
imageSource: ModelsData.collectibles.kitty5Big,
name: "Magicat-4"
} }
] ]
}, },

View File

@ -1,6 +1,9 @@
import QtQuick 2.13 import QtQuick 2.14
import QtQuick.Controls 2.13 import QtQuick.Controls 2.14
import QtQuick.Layouts 1.14 import QtQuick.Layouts 1.14
import QtQml 2.14
import Qt.labs.settings 1.0
import StatusQ.Controls 0.1 import StatusQ.Controls 0.1
import StatusQ.Core.Theme 0.1 import StatusQ.Core.Theme 0.1
@ -19,7 +22,7 @@ Item {
property var checkedKeys: [] property var checkedKeys: []
property int type: ExtendedDropdownContent.Type.Assets property int type: ExtendedDropdownContent.Type.Assets
readonly property bool canGoBack: root.state !== d.listView_depth1_State readonly property bool canGoBack: root.state !== d.depth1_ListState
signal itemClicked(string key, string name, url iconSource) signal itemClicked(string key, string name, url iconSource)
signal navigateDeep(string key, var subItems) signal navigateDeep(string key, var subItems)
@ -30,7 +33,7 @@ Item {
} }
function goBack() { function goBack() {
root.state = d.listView_depth1_State d.reset()
} }
function goForward(key, itemName, itemSource, subItems) { function goForward(key, itemName, itemSource, subItems) {
@ -38,53 +41,81 @@ Item {
d.currentItemKey = key d.currentItemKey = key
d.currentItemName = itemName d.currentItemName = itemName
d.currentItemSource = itemSource d.currentItemSource = itemSource
root.state = d.listView_depth2_State root.state = d.useThumbnailsOnDepth2
? d.depth2_ThumbnailsState : d.depth2_ListState
}
Settings {
property alias useThumbnailsOnDepth2: d.useThumbnailsOnDepth2
} }
QtObject { QtObject {
id: d id: d
readonly property int filterItemsHeight: 36 readonly property int filterItemsHeight: 36
readonly property int filterPopupWidth: 233 readonly property int filterPopupWidth: 233
readonly property int filterPopupHeight: 205
// Internal management properties // Internal management properties
property bool isFilterOptionVisible: false property bool isFilterOptionVisible: false
readonly property string thumbnailsViewState: "THUMBNAILS" property bool useThumbnailsOnDepth2: false
readonly property string listView_depth1_State: "LIST-DEPTH1"
readonly property string listView_depth2_State: "LIST-DEPTH2" readonly property string depth1_ListState: "DEPTH-1-LIST"
property var currentModel: root.store.collectiblesModel readonly property string depth2_ListState: "DEPTH-2-LIST"
property var currentSubitems readonly property string depth2_ThumbnailsState: "DEPTH-2-THUMBNAILS"
property var currentModel: null
property var currentSubitems: null
property string currentItemKey: "" property string currentItemKey: ""
property string currentItemName: "" property string currentItemName: ""
property url currentItemSource: "" property url currentItemSource: ""
readonly property SortFilterProxyModel filtered: SortFilterProxyModel { readonly property bool searchMode: searcher.text.length > 0
id: collectiblesFilteredModel
sourceModel: root.store.collectiblesModel onCurrentModelChanged: {
// Workaround for a bug in SortFilterProxyModel causing that model
// is rendered incorrectly when sourceModel is changed to a model
// with different set of roles
filteredModel.active = false
filteredModel.active = true
searcher.text = ""
filteredModel.item.sourceModel = currentModel
contentLoader.item.model = filteredModel.item
}
readonly property Loader loader_: Loader {
id: filteredModel
sourceComponent: SortFilterProxyModel {
filters: ExpressionFilter { filters: ExpressionFilter {
expression: { expression: {
searcher.text searcher.text
return name.toLowerCase().includes(searcher.text.toLowerCase())
if (model.shortName && model.shortName.toLowerCase()
.includes(searcher.text.toLowerCase()))
return true
return model.name.toLowerCase().includes(
searcher.text.toLowerCase())
}
} }
} }
} }
function reset() { function reset() {
searcher.text = ""
d.currentItemKey = "" d.currentItemKey = ""
d.currentItemName = "" d.currentItemName = ""
d.currentItemSource = "" d.currentItemSource = ""
d.currentModel = root.store.collectiblesModel d.currentSubitems = null
d.currentSubitems = undefined root.state = d.depth1_ListState
root.state = d.listView_depth1_State
} }
} }
state: d.listView_depth1_State state: d.depth1_ListState
states: [ states: [
State { State {
name: d.listView_depth1_State name: d.depth1_ListState
PropertyChanges { PropertyChanges {
target: contentLoader target: contentLoader
@ -94,20 +125,17 @@ Item {
PropertyChanges { PropertyChanges {
target: d target: d
currentModel: root.type === ExtendedDropdownContent.Type.Assets currentModel: root.type === ExtendedDropdownContent.Type.Assets
? root.store.assetsModel : collectiblesFilteredModel//root.store.collectiblesModel ? root.store.assetsModel : root.store.collectiblesModel
isFilterOptionVisible: false isFilterOptionVisible: false
} }
PropertyChanges { PropertyChanges {
target: tokenGroupItem target: tokenGroupItem
visible: false visible: false
} }
PropertyChanges {
target: searcher
visible: type === ExtendedDropdownContent.Type.Collectibles
}
}, },
State { State {
name: d.listView_depth2_State name: d.depth2_ListState
PropertyChanges { PropertyChanges {
target: contentLoader target: contentLoader
@ -124,7 +152,7 @@ Item {
} }
}, },
State { State {
name: d.thumbnailsViewState name: d.depth2_ThumbnailsState
PropertyChanges { PropertyChanges {
target: contentLoader target: contentLoader
@ -164,23 +192,29 @@ Item {
// Filter options popup: // Filter options popup:
StatusDropdown { StatusDropdown {
id: filterOptionsPopup id: filterOptionsPopup
width: d.filterPopupWidth width: d.filterPopupWidth
height: d.filterPopupHeight
contentItem: ColumnLayout { contentItem: ColumnLayout {
anchors.fill: parent anchors.fill: parent
anchors.topMargin: 8 anchors.topMargin: 8
anchors.bottomMargin: 8 anchors.bottomMargin: 8
ListView {
Layout.fillWidth: true spacing: 0
Layout.preferredHeight: model.count * d.filterItemsHeight
// TODO: it can be simplified by using inline components after
// migration to Qt 5.15 or higher
Repeater {
model: ListModel { model: ListModel {
ListElement { text: qsTr("Most viewed"); selected: true } ListElement { text: qsTr("Most viewed"); selected: true }
ListElement { text: qsTr("Newest first"); selected: false } ListElement { text: qsTr("Newest first"); selected: false }
ListElement { text: qsTr("Oldest first"); selected: false } ListElement { text: qsTr("Oldest first"); selected: false }
} }
delegate: StatusItemPicker { delegate: StatusItemPicker {
width: ListView.view.width Layout.fillWidth: true
height: d.filterItemsHeight Layout.preferredHeight: d.filterItemsHeight
color: sensor1.containsMouse ? Theme.palette.baseColor4 : "transparent" color: sensor1.containsMouse ? Theme.palette.baseColor4 : "transparent"
name: model.text name: model.text
namePixelSize: 13 namePixelSize: 13
@ -201,25 +235,28 @@ Item {
} }
} }
} }
}
// Not visual element to control filter options
ButtonGroup { ButtonGroup {
id: filterRadioBtnGroup id: filterRadioBtnGroup
} }
Separator {
Layout.fillWidth: true
Layout.topMargin: 5
Layout.bottomMargin: 5
} }
Separator { Layout.fillWidth: true } Repeater {
ListView {
Layout.fillWidth: true
Layout.preferredHeight: model.count * d.filterItemsHeight
model: ListModel { model: ListModel {
ListElement { key: "LIST"; text: qsTr("List"); selected: true } ListElement { key: "LIST"; text: qsTr("List"); selected: true }
ListElement { key: "THUMBNAILS"; text: qsTr("Thumbnails"); selected: false } ListElement { key: "THUMBNAILS"; text: qsTr("Thumbnails"); selected: false }
} }
delegate: StatusItemPicker { delegate: StatusItemPicker {
width: ListView.view.width Layout.fillWidth: true
height: d.filterItemsHeight Layout.preferredHeight: d.filterItemsHeight
color: sensor2.containsMouse ? Theme.palette.baseColor4 : "transparent" color: sensor2.containsMouse ? Theme.palette.baseColor4 : "transparent"
name: model.text name: model.text
namePixelSize: 13 namePixelSize: 13
@ -236,23 +273,30 @@ Item {
onClicked: { onClicked: {
selected = !selected selected = !selected
if(model.key === "LIST") { if(model.key === "LIST") {
root.state = d.listView_depth2_State root.state = d.depth2_ListState
} }
else if(model.key === "THUMBNAILS") { else if(model.key === "THUMBNAILS") {
root.state = d.thumbnailsViewState root.state = d.depth2_ThumbnailsState
} }
filterOptionsPopup.close() filterOptionsPopup.close()
} }
} }
Binding {
target: d
when: model.key === "THUMBNAILS" && selected
property: "useThumbnailsOnDepth2"
value: true
restoreMode: Binding.RestoreBindingOrValue
}
}
} }
// Not visual element to control visualization options
ButtonGroup { ButtonGroup {
id: visualizationRadioBtnGroup id: visualizationRadioBtnGroup
} }
} }
} }
}
// List elements content // List elements content
@ -263,12 +307,20 @@ Item {
id: searcher id: searcher
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: root.state === d.depth1_ListState ? 0 : 8
visible: false
topPadding: 0 topPadding: 0
bottomPadding: 0 bottomPadding: 0
minimumHeight: 36 minimumHeight: 36
maximumHeight: 36 maximumHeight: 36
placeholderText: root.type === ExtendedDropdownContent.Type.Assets ?
qsTr("Search assets") : qsTr("Search collectibles")
Binding on placeholderText{
when: d.currentItemName !== ""
value: qsTr("Search %1").arg(d.currentItemName)
}
} }
TokenItem { TokenItem {
@ -277,7 +329,7 @@ Item {
Layout.fillWidth: true Layout.fillWidth: true
key: d.currentItemKey key: d.currentItemKey
name: d.currentItemName name: qsTr("Any %1").arg(d.currentItemName)
iconSource: d.currentItemSource iconSource: d.currentItemSource
selected: root.checkedKeys.includes(key) selected: root.checkedKeys.includes(key)
@ -292,7 +344,6 @@ Item {
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
} }
} }
@ -304,9 +355,9 @@ Item {
ListElement { key: "MINT"; icon: "add"; iconSize: 16; description: qsTr("Mint asset"); rotation: 0; spacing: 8 } ListElement { key: "MINT"; icon: "add"; iconSize: 16; description: qsTr("Mint asset"); rotation: 0; spacing: 8 }
ListElement { key: "IMPORT"; icon: "invite-users"; iconSize: 16; description: qsTr("Import existing asset"); rotation: 180; spacing: 8 } ListElement { key: "IMPORT"; icon: "invite-users"; iconSize: 16; description: qsTr("Import existing asset"); rotation: 180; spacing: 8 }
} }
isHeaderVisible: false // TEMPORARILY hidden. These 2 header options will be implemented after MVP. areHeaderButtonsVisible: false // TEMPORARILY hidden. These 2 header options will be implemented after MVP.
model: d.currentModel
checkedKeys: root.checkedKeys checkedKeys: root.checkedKeys
searchMode: d.searchMode
onHeaderItemClicked: { onHeaderItemClicked: {
if(key === "MINT") console.log("TODO: Mint asset") if(key === "MINT") console.log("TODO: Mint asset")
@ -320,25 +371,26 @@ Item {
id: collectiblesListView id: collectiblesListView
ListDropdownContent { ListDropdownContent {
isHeaderVisible: root.state === d.listView_depth1_State areHeaderButtonsVisible: root.state === d.depth1_ListState
headerModel: ListModel { headerModel: ListModel {
ListElement { key: "MINT"; icon: "add"; iconSize: 16; description: qsTr("Mint collectible"); rotation: 0; spacing: 8 } ListElement { key: "MINT"; icon: "add"; iconSize: 16; description: qsTr("Mint collectible"); rotation: 0; spacing: 8 }
} }
model: d.currentModel
checkedKeys: root.checkedKeys checkedKeys: root.checkedKeys
searchMode: d.searchMode
onHeaderItemClicked: { onHeaderItemClicked: {
if(key === "MINT") console.log("TODO: Mint collectible") if(key === "MINT") console.log("TODO: Mint collectible")
} }
onItemClicked: { onItemClicked: {
if(subItems && root.state === d.listView_depth1_State) { if(subItems && root.state === d.depth1_ListState) {
// One deep navigation // One deep navigation
d.currentSubitems = subItems d.currentSubitems = subItems
d.currentItemKey = key d.currentItemKey = key
d.currentItemName = name d.currentItemName = name
d.currentItemSource = iconSource d.currentItemSource = iconSource
root.state = d.listView_depth2_State root.state = d.useThumbnailsOnDepth2
? d.depth2_ThumbnailsState : d.depth2_ListState
root.navigateDeep(key, subItems) root.navigateDeep(key, subItems)
} }
else { else {
@ -355,12 +407,10 @@ Item {
ThumbnailsDropdownContent { ThumbnailsDropdownContent {
title: d.currentItemName title: d.currentItemName
titleImage: d.currentItemSource titleImage: d.currentItemSource
checkedKeys: root.checkedKeys
padding: 0 padding: 0
model: d.currentModel
checkedKeys: root.checkedKeys
onItemClicked: { onItemClicked: {
d.reset() d.reset()
root.itemClicked(key, name, iconSource) root.itemClicked(key, name, iconSource)

View File

@ -204,7 +204,7 @@ StatusDropdown {
Repeater { Repeater {
id: tabLabelsRepeater id: tabLabelsRepeater
model: [qsTr("Asset"), qsTr("Collectible"), qsTr("ENS")] model: [qsTr("Assets"), qsTr("Collectibles"), qsTr("ENS")]
StatusSwitchTabButton { StatusSwitchTabButton {
text: modelData text: modelData

View File

@ -15,7 +15,9 @@ StatusListView {
property var checkedKeys: [] property var checkedKeys: []
property var headerModel property var headerModel
property bool isHeaderVisible: true property bool areHeaderButtonsVisible: true
property bool searchMode: false
property int maxHeight: 381 // default by design property int maxHeight: 381 // default by design
signal headerItemClicked(string key) signal headerItemClicked(string key)
@ -25,14 +27,24 @@ StatusListView {
implicitHeight: Math.min(contentHeight, root.maxHeight) implicitHeight: Math.min(contentHeight, root.maxHeight)
currentIndex: -1 currentIndex: -1
clip: true clip: true
header: Rectangle {
visible: root.isHeaderVisible header: ColumnLayout {
z: 3 // Above delegate (z=1) and above section.delegate (z = 2)
color: Theme.palette.statusMenu.backgroundColor
width: root.width width: root.width
height: root.isHeaderVisible ? columnHeader.implicitHeight + 2 * columnHeader.anchors.topMargin : 0
spacing: 0
Item {
Layout.fillWidth: true
Layout.preferredHeight: root.areHeaderButtonsVisible
? columnHeader.implicitHeight + 2 * columnHeader.anchors.topMargin
: 0
visible: root.areHeaderButtonsVisible
z: 3 // Above delegate (z=1) and above section.delegate (z = 2)
ColumnLayout { ColumnLayout {
id: columnHeader id: columnHeader
anchors.top: parent.top anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: 6 anchors.leftMargin: 6
@ -54,7 +66,17 @@ StatusListView {
} }
} }
} }
}// End of Header }
Loader {
Layout.preferredHeight: visible ? d.sectionHeight : 0
Layout.fillWidth: true
visible: root.searchMode
sourceComponent: sectionComponent
}
}
delegate: TokenItem { delegate: TokenItem {
width: ListView.view.width width: ListView.view.width
key: model.key key: model.key
@ -70,19 +92,52 @@ StatusListView {
model.iconSource, model.iconSource,
model.subItems) model.subItems)
} }
section.property: "category"
section.property: root.searchMode ? "" : "category"
section.criteria: ViewSection.FullString section.criteria: ViewSection.FullString
section.delegate: Item { section.delegate: Item {
width: ListView.view.width width: ListView.view.width
height: 34 // by design height: d.sectionHeight
Loader {
id: loader
anchors.fill: parent
sourceComponent: sectionComponent
Binding {
target: loader.item
property: "section"
value: section
}
}
}
QtObject {
id: d
readonly property int sectionHeight: 34
}
Component {
id: sectionComponent
Item {
id: sectionDelegateRoot
property string section: root.model && root.model.count ?
qsTr("Search result") :
qsTr("No results")
StatusBaseText { StatusBaseText {
anchors.leftMargin: 8 anchors.leftMargin: 8
anchors.left: parent.left anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
text: section text: sectionDelegateRoot.section
color: Theme.palette.baseColor1 color: Theme.palette.baseColor1
font.pixelSize: 12 font.pixelSize: 12
elide: Text.ElideRight elide: Text.ElideRight
} }
}// End of Category item }
}// End of Root }
}

View File

@ -12,7 +12,7 @@ StatusScrollView {
property string title: "" property string title: ""
property url titleImage: "" property url titleImage: ""
property string subtitle: "" property string subtitle: ""
property ListModel model property var model
property var checkedKeys: [] property var checkedKeys: []
property int maxHeight: 381 // default by design property int maxHeight: 381 // default by design
@ -30,6 +30,23 @@ StatusScrollView {
clip: true clip: true
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
ColumnLayout {
Layout.fillWidth: true
StatusBaseText {
Layout.leftMargin: 8
Layout.topMargin: 8
visible: !!model ? model.count === 0 : false
Layout.fillWidth: true
text: qsTr("No results")
color: Theme.palette.baseColor1
font.pixelSize: 12
wrapMode: Text.Wrap
}
GridLayout { GridLayout {
id: grid id: grid
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
@ -92,3 +109,4 @@ StatusScrollView {
} }
} }
} }
}