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

View File

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

View File

@ -15,7 +15,9 @@ StatusListView {
property var checkedKeys: []
property var headerModel
property bool isHeaderVisible: true
property bool areHeaderButtonsVisible: true
property bool searchMode: false
property int maxHeight: 381 // default by design
signal headerItemClicked(string key)
@ -25,36 +27,56 @@ StatusListView {
implicitHeight: Math.min(contentHeight, root.maxHeight)
currentIndex: -1
clip: true
header: Rectangle {
visible: root.isHeaderVisible
z: 3 // Above delegate (z=1) and above section.delegate (z = 2)
color: Theme.palette.statusMenu.backgroundColor
header: ColumnLayout {
width: root.width
height: root.isHeaderVisible ? columnHeader.implicitHeight + 2 * columnHeader.anchors.topMargin : 0
ColumnLayout {
id: columnHeader
anchors.top: parent.top
anchors.left: parent.left
anchors.leftMargin: 6
anchors.rightMargin: anchors.leftMargin
anchors.topMargin: 8
anchors.bottomMargin: 2 * anchors.topMargin
spacing: 20
Repeater {
model: root.headerModel
delegate: StatusIconTextButton {
z: 3 // Above delegate (z=1) and above section.delegate (z = 2)
spacing: model.spacing
statusIcon: model.icon
icon.width: model.iconSize
icon.height: model.iconSize
iconRotation: model.rotation
text: model.description
onClicked: root.headerItemClicked(model.index)
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 {
id: columnHeader
anchors.top: parent.top
anchors.left: parent.left
anchors.leftMargin: 6
anchors.rightMargin: anchors.leftMargin
anchors.topMargin: 8
anchors.bottomMargin: 2 * anchors.topMargin
spacing: 20
Repeater {
model: root.headerModel
delegate: StatusIconTextButton {
z: 3 // Above delegate (z=1) and above section.delegate (z = 2)
spacing: model.spacing
statusIcon: model.icon
icon.width: model.iconSize
icon.height: model.iconSize
iconRotation: model.rotation
text: model.description
onClicked: root.headerItemClicked(model.index)
}
}
}
}
}// End of Header
Loader {
Layout.preferredHeight: visible ? d.sectionHeight : 0
Layout.fillWidth: true
visible: root.searchMode
sourceComponent: sectionComponent
}
}
delegate: TokenItem {
width: ListView.view.width
key: model.key
@ -70,19 +92,52 @@ StatusListView {
model.iconSource,
model.subItems)
}
section.property: "category"
section.property: root.searchMode ? "" : "category"
section.criteria: ViewSection.FullString
section.delegate: Item {
width: ListView.view.width
height: 34 // by design
StatusBaseText {
anchors.leftMargin: 8
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
text: section
color: Theme.palette.baseColor1
font.pixelSize: 12
elide: Text.ElideRight
height: d.sectionHeight
Loader {
id: loader
anchors.fill: parent
sourceComponent: sectionComponent
Binding {
target: loader.item
property: "section"
value: section
}
}
}// End of Category item
}// End of Root
}
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 {
anchors.leftMargin: 8
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
text: sectionDelegateRoot.section
color: Theme.palette.baseColor1
font.pixelSize: 12
elide: Text.ElideRight
}
}
}
}

View File

@ -12,7 +12,7 @@ StatusScrollView {
property string title: ""
property url titleImage: ""
property string subtitle: ""
property ListModel model
property var model
property var checkedKeys: []
property int maxHeight: 381 // default by design
@ -30,64 +30,82 @@ StatusScrollView {
clip: true
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
GridLayout {
id: grid
Layout.alignment: Qt.AlignHCenter
ColumnLayout {
Layout.fillWidth: true
columnSpacing: 8
rowSpacing: 12
columns: d.columns
Repeater {
model: root.model
delegate: ColumnLayout {
spacing: 4
Rectangle {
Layout.preferredWidth: 133
Layout.preferredHeight: 133
color: "transparent"
Image {
source: model.imageSource ? model.imageSource : ""
anchors.fill: parent
StatusBaseText {
Layout.leftMargin: 8
Layout.topMargin: 8
Rectangle {
width: 32
height: 32
visible: !!model ? model.count === 0 : false
anchors.bottom: parent.bottom
anchors.right: parent.right
anchors.margins: 8
Layout.fillWidth: true
radius: width / 2
visible: root.checkedKeys.includes(model.key)
// TODO: use color from theme when defined properly in the design
color: "#F5F6F8"
text: qsTr("No results")
color: Theme.palette.baseColor1
font.pixelSize: 12
wrapMode: Text.Wrap
}
StatusIcon {
anchors.centerIn: parent
icon: "checkmark"
GridLayout {
id: grid
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
columnSpacing: 8
rowSpacing: 12
columns: d.columns
color: Theme.palette.baseColor1
width: 16
height: 16
Repeater {
model: root.model
delegate: ColumnLayout {
spacing: 4
Rectangle {
Layout.preferredWidth: 133
Layout.preferredHeight: 133
color: "transparent"
Image {
source: model.imageSource ? model.imageSource : ""
anchors.fill: parent
Rectangle {
width: 32
height: 32
anchors.bottom: parent.bottom
anchors.right: parent.right
anchors.margins: 8
radius: width / 2
visible: root.checkedKeys.includes(model.key)
// TODO: use color from theme when defined properly in the design
color: "#F5F6F8"
StatusIcon {
anchors.centerIn: parent
icon: "checkmark"
color: Theme.palette.baseColor1
width: 16
height: 16
}
}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
hoverEnabled: true
onClicked: { root.itemClicked(model.key, model.name, model.iconSource) }
}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
hoverEnabled: true
onClicked: { root.itemClicked(model.key, model.name, model.iconSource) }
StatusBaseText {
Layout.alignment: Qt.AlignLeft
Layout.leftMargin: 8
text: model.name
color: Theme.palette.directColor1
font.pixelSize: 13
elide: Text.ElideRight
}
}
StatusBaseText {
Layout.alignment: Qt.AlignLeft
Layout.leftMargin: 8
text: model.name
color: Theme.palette.directColor1
font.pixelSize: 13
elide: Text.ElideRight
}
}
}
}