status-desktop/ui/app/AppLayouts/Profile/panels/ProfileShowcasePanel.qml

355 lines
12 KiB
QML

import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import Qt.labs.settings 1.0
import StatusQ.Core 0.1
import StatusQ.Controls 0.1
import StatusQ.Components 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Popups 0.1
import shared.controls 1.0
import utils 1.0
import AppLayouts.Profile.controls 1.0
Control {
id: root
property var baseModel
readonly property alias settings: settings
readonly property alias showcaseModel: showcaseModel
// to override
property string settingsKey
property string keyRole
property var roleNames: []
property var filterFunc: (modelData) => true
property string hiddenPlaceholderBanner
property string showcasePlaceholderBanner
property Component draggableDelegateComponent
property Component showcaseDraggableDelegateComponent
background: null
component VisibilityDropArea: AbstractButton {
id: visibilityDropAreaLocal
property int showcaseVisibility: Constants.ShowcaseVisibility.NoOne
readonly property alias containsDrag: dropArea.containsDrag
padding: Style.current.halfPadding
spacing: padding/2
icon.color: Theme.palette.primaryColor1
background: ShapeRectangle {
path.strokeColor: dropArea.containsDrag ? "transparent" : Theme.palette.directColor7
path.fillColor: dropArea.containsDrag ? Theme.palette.white : "transparent"
DropArea {
id: dropArea
anchors.fill: parent
keys: ["x-status-draggable-showcase-item-hidden"]
onEntered: function(drag) {
drag.accept()
}
onDropped: function(drop) {
var showcaseObj = drop.source.showcaseObj
var tmpObj = Object()
root.roleNames.forEach(role => tmpObj[role] = showcaseObj[role])
tmpObj.showcaseVisibility = visibilityDropAreaLocal.showcaseVisibility
showcaseModel.append(tmpObj)
}
}
}
contentItem: Item {
RowLayout {
width: Math.min(parent.width, implicitWidth)
anchors.centerIn: parent
spacing: visibilityDropAreaLocal.spacing
StatusIcon {
width: 20
height: 20
icon: ProfileUtils.visibilityIcon(visibilityDropAreaLocal.showcaseVisibility)
color: visibilityDropAreaLocal.icon.color
}
StatusBaseText {
Layout.fillWidth: true
font.pixelSize: 13
font.weight: Font.Medium
elide: Text.ElideRight
color: visibilityDropAreaLocal.icon.color
text: visibilityDropAreaLocal.text
}
}
}
}
Component.onCompleted: showcaseModel.load()
Component.onDestruction: showcaseModel.save()
// NB temporary model until the backend knows the extra roles: "showcaseVisibility" and "order"
ListModel {
id: showcaseModel
function hasItem(itemId) {
for (let i = 0; i < count; i++) {
let item = get(i)
if (!!item && item[root.keyRole] === itemId)
return true
}
return false
}
function save() {
var result = []
for (let i = 0; i < count; i++) {
let item = get(i)
result.push(item)
}
settings.setValue(root.settingsKey, JSON.stringify(result))
}
function load() {
const data = settings.value(root.settingsKey)
try {
const arr = JSON.parse(data)
for (const i in arr)
showcaseModel.append(arr[i])
} catch (e) {
console.warn(e)
}
}
}
Settings {
id: settings
category: "Showcase"
function reset() {
showcaseModel.clear()
settings.setValue(root.settingsKey, "")
settings.sync()
}
}
QtObject {
id: d
readonly property int defaultDelegateHeight: 60
readonly property int contentSpacing: 12
readonly property int strokeMargin: 2
}
contentItem: ColumnLayout {
spacing: d.contentSpacing
StatusBaseText {
Layout.fillWidth: true
text: qsTr("In showcase")
color: Theme.palette.baseColor1
font.pixelSize: 13
font.weight: Font.Medium
}
StatusListView {
id: showcaseItemsListView
Layout.fillWidth: true
Layout.minimumHeight: Math.floor(targetDropArea.height + targetDropArea.anchors.bottomMargin)
model: showcaseModel
implicitHeight: contentHeight
interactive: false
displaced: Transition {
NumberAnimation { properties: "x,y"; easing.type: Easing.OutQuad }
}
delegate: DropArea {
id: showcaseDelegateRoot
property int visualIndex: index
ListView.onRemove: SequentialAnimation {
PropertyAction { target: showcaseDelegateRoot; property: "ListView.delayRemove"; value: true }
NumberAnimation { target: showcaseDelegateRoot; property: "scale"; to: 0; easing.type: Easing.InOutQuad }
PropertyAction { target: showcaseDelegateRoot; property: "ListView.delayRemove"; value: false }
}
width: ListView.view.width
height: showcaseDraggableDelegateLoader.item ? showcaseDraggableDelegateLoader.item.height : 0
keys: ["x-status-draggable-showcase-item"]
onEntered: function(drag) {
const from = drag.source.visualIndex
const to = showcaseDraggableDelegateLoader.item.visualIndex
if (to === from)
return
showcaseModel.move(from, to, 1)
drag.accept()
}
Loader {
id: showcaseDraggableDelegateLoader
width: parent.width
sourceComponent: root.showcaseDraggableDelegateComponent
property var modelData: model
property var dragParentData: root
property int visualIndexData: index
}
}
// overlaid at the bottom of the listview
DropArea {
id: targetDropArea
width: parent.width
height: d.defaultDelegateHeight
anchors.bottom: parent.bottom
anchors.bottomMargin: showcaseModel.count ? Style.current.halfPadding : 0
keys: ["x-status-draggable-showcase-item-hidden"]
ShapeRectangle {
anchors.fill: parent
anchors.margins: d.strokeMargin
visible: !showcaseModel.count && !showcaseCombinedDropArea.visible
text: root.hiddenPlaceholderBanner
}
Rectangle {
id: showcaseCombinedDropArea
width: parent.width
height: parent.height + d.strokeMargin
anchors.centerIn: parent
color: Theme.palette.baseColor5
radius: Style.current.radius
visible: parent.containsDrag || dropAreaEveryone.containsDrag || dropAreaContacts.containsDrag || dropAreaVerified.containsDrag
RowLayout {
width: parent.width - spacing*2
anchors.centerIn: parent
spacing: d.contentSpacing
VisibilityDropArea {
id: dropAreaEveryone
Layout.fillWidth: true
showcaseVisibility: Constants.ShowcaseVisibility.Everyone
text: qsTr("Everyone")
}
VisibilityDropArea {
id: dropAreaContacts
Layout.fillWidth: true
showcaseVisibility: Constants.ShowcaseVisibility.Contacts
text: qsTr("Contacts")
}
VisibilityDropArea {
id: dropAreaVerified
Layout.fillWidth: true
showcaseVisibility: Constants.ShowcaseVisibility.IdVerifiedContacts
text: qsTr("Verified")
}
}
}
}
}
StatusBaseText {
Layout.fillWidth: true
Layout.topMargin: Style.current.halfPadding
text: qsTr("Hidden")
color: Theme.palette.baseColor1
font.pixelSize: 13
font.weight: Font.Medium
}
StatusListView {
id: hiddenItemsListView
Layout.fillWidth: true
Layout.minimumHeight: empty ? Math.floor(hiddenTargetDropArea.height + hiddenTargetDropArea.anchors.topMargin)
: d.defaultDelegateHeight * Math.min(count, 4)
model: root.baseModel
readonly property bool empty: !contentHeight
displaced: Transition {
NumberAnimation { properties: "x,y"; easing.type: Easing.OutQuad }
}
delegate: DropArea {
id: delegateRoot
property int visualIndex: index
visible: root.filterFunc(model)
width: ListView.view.width
height: visible && draggableDelegateLoader.item ? draggableDelegateLoader.item.height : 0
keys: ["x-status-draggable-showcase-item"]
onEntered: function(drag) {
drag.accept()
}
onDropped: function(drop) {
showcaseModel.remove(drop.source.visualIndex)
}
Rectangle {
width: parent.width
height: d.defaultDelegateHeight
anchors.centerIn: parent
color: Theme.palette.baseColor5
radius: Style.current.radius
visible: draggableDelegateLoader.item && draggableDelegateLoader.item.dragActive
}
Loader {
id: draggableDelegateLoader
width: parent.width
sourceComponent: root.draggableDelegateComponent
property var modelData: model
property var dragParentData: root
property int visualIndexData: delegateRoot.visualIndex
}
}
// overlaid at the top of the listview
DropArea {
id: hiddenTargetDropArea
width: parent.width
height: d.defaultDelegateHeight
anchors.top: parent.top
anchors.topMargin: !hiddenItemsListView.empty ? Style.current.halfPadding : 0
keys: ["x-status-draggable-showcase-item"]
ShapeRectangle {
readonly property bool stroked: hiddenItemsListView.empty && !parent.containsDrag
anchors.fill: parent
anchors.margins: d.strokeMargin
visible: hiddenItemsListView.empty || parent.containsDrag
path.fillColor: stroked ? "transparent" : Theme.palette.baseColor5
path.strokeColor: stroked ? Theme.palette.baseColor2 : "transparent"
text: root.showcasePlaceholderBanner
}
onEntered: function(drag) {
drag.accept()
}
onDropped: function(drop) {
showcaseModel.remove(drop.source.visualIndex)
}
}
}
}
}