status-desktop/ui/app/AppLayouts/Communities/popups/InDropdown.qml

451 lines
15 KiB
QML

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
import AppLayouts.Communities.controls 1.0
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)
selectedChannelsChanged()
}
function removeFromSelectedChannels(model) {
selectedChannels.delete(model.itemId)
selectedChannelsChanged()
}
}
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 * d.itemStandardHeight, contentHeight)
Layout.maximumHeight: Layout.minimumHeight
contentWidth: availableWidth
Layout.bottomMargin: d.defaultVMargin
Layout.topMargin:
!root.allowChoosingEntireCommunity && !root.allowChoosingEntireCommunity ? d.defaultVMargin : 0
padding: 0
ColumnLayout {
id: scrollableColumn
width: scrollView.availableWidth
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)
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
CategoryListItem {
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)
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)
}
}
}
}
}
}
StatusBaseText {
id: noContactsText
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
visible: {
for (let i = 0; i < topRepeater.count; i++) {
const item = topRepeater.itemAt(i)
if (item && item.visible)
return false
}
return true
}
text: qsTr("No channels found")
color: Theme.palette.baseColor1
font.pixelSize: Theme.tertiaryTextFontSize
elide: Text.ElideRight
lineHeight: 1.2
}
}
}
StatusButton {
Layout.fillWidth: true
enabled: root.acceptMode === InDropdown.AcceptMode.Update
|| d.selectedChannels.size > 0
text: {
if (root.acceptMode === InDropdown.AcceptMode.Update)
return qsTr("Update")
if (radioButton.checked)
return qsTr("Add community")
if (d.selectedChannels.size === 0)
return qsTr("Add")
return qsTr("Add %n channel(s)", "", d.selectedChannels.size)
}
onClicked: {
if (radioButton.checked) {
root.communitySelected()
return
}
root.channelsSelected(Array.from(d.selectedChannels.values()))
}
}
}
}