status-desktop/ui/app/AppLayouts/Chat/controls/community/InDropdown.qml

413 lines
14 KiB
QML
Raw Normal View History

import QtQuick 2.14
import QtQuick.Layouts 1.14
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Components 0.1
import StatusQ.Controls 0.1
import StatusQ.Popups 0.1
import shared.controls 1.0
import SortFilterProxyModel 0.2
StatusDropdown {
id: root
property bool allowChoosingEntireCommunity: false
property bool showAddChannelButton: false
property string communityName
property string communityImage
property string communityColor
property var model
property int acceptMode: InDropdown.AcceptMode.Add
enum AcceptMode {
Add, Update
}
width: 289
padding: 8
// force keeping within the bounds of the enclosing window
margins: 0
signal addChannelClicked
signal communitySelected
signal channelsSelected(var channels)
function setSelectedChannels(channels) {
d.setSelectedChannels(channels)
}
onAboutToHide: searcher.text = ""
onAboutToShow: scrollView.Layout.preferredHeight = Math.min(
scrollView.implicitHeight, 420)
QtObject {
id: d
readonly property int defaultVMargin: 9
readonly property int maxHeightCountNo: 5
readonly property int itemStandardHeight: 44
readonly property var selectedChannels: new Set()
signal setSelectedChannels(var channels)
function search(text, searcherText) {
return text.toLowerCase().includes(searcherText.toLowerCase())
}
function resolveEmoji(emoji) {
return !!emoji ? emoji : ""
}
function resolveColor(color, colorId) {
return !!color ? color : Theme.palette.userCustomizationColors[colorId]
}
function addToSelectedChannels(model) {
selectedChannels.add(model.itemId)
}
function removeFromSelectedChannels(model) {
selectedChannels.delete(model.itemId)
}
}
contentItem: ColumnLayout {
spacing: 0
SearchBox {
id: searcher
Layout.fillWidth: true
topPadding: 0
bottomPadding: 0
minimumHeight: 36
maximumHeight: 36
}
StatusListItem {
Layout.fillWidth: true
Layout.topMargin: d.defaultVMargin
Layout.preferredHeight: d.itemStandardHeight
visible: root.allowChoosingEntireCommunity
title: root.communityName
subTitle: qsTr("Community")
asset.name: root.communityImage
asset.color: root.communityColor
asset.isImage: true
asset.width: 32
asset.height: 32
asset.isLetterIdenticon: !asset.name
asset.charactersLen: 2
asset.letterSize: 15
leftPadding: 8
rightPadding: 6
statusListItemTitleArea.anchors.leftMargin: 8
statusListItemTitle.font.pixelSize: 13
statusListItemTitle.font.weight: Font.Medium
statusListItemSubTitle.font.pixelSize: 12
components: [
StatusRadioButton {
id: radioButton
size: StatusRadioButton.Size.Small
rightPadding: 0
}
]
// using MouseArea instead of build-in 'clicked' signal to avoid
// intercepting event by the StatusRadioButton
MouseArea {
anchors.fill: parent
onClicked: {
radioButton.toggle()
radioButton.toggled()
}
cursorShape: Qt.PointingHandCursor
}
}
StatusMenuSeparator {
Layout.fillWidth: true
Layout.topMargin: 4 - implicitHeight / 2
Layout.bottomMargin: 4 - implicitHeight / 2
visible: root.allowChoosingEntireCommunity
}
StatusScrollView {
id: scrollView
Layout.fillWidth: true
Layout.minimumHeight: Math.min(d.maxHeightCountNo, topRepeater.count) * d.itemStandardHeight
Layout.maximumHeight: Layout.minimumHeight
contentHeight: scrollableColumn.implicitHeight
Layout.bottomMargin: d.defaultVMargin
Layout.topMargin:
!root.allowChoosingEntireCommunity && !root.allowChoosingEntireCommunity ? d.defaultVMargin : 0
padding: 0
ColumnLayout {
id: scrollableColumn
width: scrollView.width
spacing: 0
StatusIconTextButton {
Layout.preferredHeight: 36
visible: root.showAddChannelButton
leftPadding: 8
spacing: 8
statusIcon: "add"
icon.width: 16
icon.height: 16
iconRotation: 0
text: qsTr("Add channel")
onClicked: root.addChannelClicked()
}
Repeater {
id: topRepeater
model: SortFilterProxyModel {
id: topLevelModel
sourceModel: root.model
filters: AnyOf {
ValueFilter {
roleName: "categoryId"
value: ""
}
ValueFilter {
roleName: "isCategory"
value: true
}
}
sorters: [
RoleSorter {
roleName: "categoryPosition"
priority: 2 // Higher number === higher priority
},
RoleSorter {
roleName: "position"
priority: 1
}
]
}
ColumnLayout {
id: column
readonly property var topModel: model
readonly property alias checkBox: loader.item
property int checkedCount: 0
readonly property bool isCategory: model.isCategory
readonly property string categoryId: model.categoryId
Layout.fillWidth: true
spacing: 0
visible: {
if (!isCategory)
return d.search(model.name, searcher.text)
|| checkBox.checked
if (checkedCount > 0)
return true
const subItemsCount = subItemsRepeater.count
for (let i = 0; i < subItemsCount; i++)
if (subItemsRepeater.itemAt(i).show)
return true
return false
}
Loader {
id: loader
Layout.fillWidth: true
Layout.preferredHeight: d.itemStandardHeight
Layout.topMargin: isCategory ? d.defaultVMargin : 0
sourceComponent: isCategory
? communityCategoryDelegate
: communityDelegate
Connections {
target: radioButton
function onToggled() {
const checkBox = loader.item.checkBox
checkBox.checked = false
checkBox.onToggled()
}
}
Component {
id: communityDelegate
CommunityListItem {
id: communityItem
title: "#" + model.name
asset.name: model.icon ?? ""
asset.emoji: d.resolveEmoji(model.emoji)
asset.color: d.resolveColor(model.color,
model.colorId)
checkBox.onToggled: {
if (checked)
radioButton.checked = false
}
checkBox.onCheckedChanged: {
if (checkBox.checked)
d.addToSelectedChannels(model)
else
d.removeFromSelectedChannels(model)
}
Connections {
target: d
function onSetSelectedChannels(channels) {
communityItem.checked = channels.includes(
model.itemId)
}
}
}
}
Component {
id: communityCategoryDelegate
CommunityCategoryListItem {
title: model.name
checkState: {
if (checkedCount === subItems.count)
return Qt.Checked
else if (checkedCount === 0)
return Qt.Unchecked
return Qt.PartiallyChecked
}
checkBox.onToggled: {
if (checked)
radioButton.checked = false
subItemsRepeater.setAll(checkState)
}
}
}
}
SortFilterProxyModel {
id: subItems
sourceModel: isCategory ? root.model : null
filters: AllOf {
ValueFilter {
roleName: "categoryId"
value: column.categoryId
}
ValueFilter {
roleName: "isCategory"
value: false
}
}
sorters: RoleSorter {
roleName: "position"
}
}
Repeater {
id: subItemsRepeater
model: subItems
function setAll(checkState) {
const subItemsCount = count
for (let i = 0; i < subItemsCount; i++) {
itemAt(i).checkState = checkState
}
}
CommunityListItem {
id: communitySubItem
readonly property bool show:
d.search(model.name, searcher.text) || checked
Layout.fillWidth: true
visible: show
title: "#" + model.name
asset.name: model.icon ?? ""
asset.emoji: d.resolveEmoji(model.emoji)
asset.color: d.resolveColor(model.color,
model.colorId)
onCheckedChanged: {
if (checked) {
radioButton.checked = false
d.addToSelectedChannels(model)
} else {
d.removeFromSelectedChannels(model)
}
Qt.callLater(() => checkedCount += checked ? 1 : -1)
}
Connections {
target: d
function onSetSelectedChannels(channels) {
communitySubItem.checked = channels.includes(
model.itemId)
}
}
}
}
}
}
}
}
StatusButton {
Layout.fillWidth: true
text: root.acceptMode === InDropdown.AcceptMode.Add
? qsTr("Add") : qsTr("Update")
onClicked: {
if (radioButton.checked) {
root.communitySelected()
return
}
root.channelsSelected(Array.from(d.selectedChannels.values()))
}
}
}
}