2024-04-05 10:45:50 +00:00
|
|
|
import QtQuick 2.15
|
|
|
|
import QtQuick.Layouts 1.15
|
2022-12-20 00:19:46 +00:00
|
|
|
|
2024-04-05 10:45:50 +00:00
|
|
|
import StatusQ 0.1
|
2022-12-20 00:19:46 +00:00
|
|
|
import StatusQ.Components 0.1
|
|
|
|
import StatusQ.Controls 0.1
|
2024-04-05 10:45:50 +00:00
|
|
|
import StatusQ.Core 0.1
|
|
|
|
import StatusQ.Core.Theme 0.1
|
|
|
|
import StatusQ.Core.Utils 0.1
|
2022-12-20 00:19:46 +00:00
|
|
|
import StatusQ.Popups 0.1
|
|
|
|
|
2024-04-05 10:45:50 +00:00
|
|
|
import AppLayouts.Communities.controls 1.0
|
2022-12-20 00:19:46 +00:00
|
|
|
import shared.controls 1.0
|
|
|
|
|
2024-04-05 10:45:50 +00:00
|
|
|
import SortFilterProxyModel 0.2
|
2023-06-30 12:34:42 +00:00
|
|
|
|
2022-12-20 00:19:46 +00:00
|
|
|
StatusDropdown {
|
|
|
|
id: root
|
|
|
|
|
2024-04-05 10:45:50 +00:00
|
|
|
width: 289
|
|
|
|
padding: 8
|
|
|
|
|
|
|
|
// force keeping within the bounds of the enclosing window
|
|
|
|
margins: 0
|
|
|
|
|
2023-01-20 11:08:20 +00:00
|
|
|
property bool allowChoosingEntireCommunity: false
|
|
|
|
property bool showAddChannelButton: false
|
|
|
|
|
2022-12-20 00:19:46 +00:00
|
|
|
property string communityName
|
|
|
|
property string communityImage
|
2023-03-07 08:47:04 +00:00
|
|
|
property string communityColor
|
2022-12-20 00:19:46 +00:00
|
|
|
|
|
|
|
property var model
|
|
|
|
|
|
|
|
property int acceptMode: InDropdown.AcceptMode.Add
|
|
|
|
|
|
|
|
enum AcceptMode {
|
|
|
|
Add, Update
|
|
|
|
}
|
|
|
|
|
|
|
|
signal addChannelClicked
|
|
|
|
signal communitySelected
|
|
|
|
signal channelsSelected(var channels)
|
|
|
|
|
|
|
|
function setSelectedChannels(channels) {
|
2024-04-05 10:45:50 +00:00
|
|
|
d.selectedChannels.clear()
|
|
|
|
channels.forEach(c => d.selectedChannels.add(c))
|
|
|
|
d.selectedChannelsChanged()
|
|
|
|
}
|
|
|
|
|
|
|
|
onAboutToHide: {
|
|
|
|
searcher.text = ""
|
|
|
|
listView.positionViewAtBeginning()
|
|
|
|
}
|
|
|
|
|
2024-10-10 17:01:47 +00:00
|
|
|
onAboutToShow: {
|
|
|
|
searcher.input.edit.forceActiveFocus()
|
|
|
|
listView.Layout.preferredHeight = Math.min(
|
2024-04-05 10:45:50 +00:00
|
|
|
listView.implicitHeight, 420)
|
2024-10-10 17:01:47 +00:00
|
|
|
}
|
2024-04-05 10:45:50 +00:00
|
|
|
|
|
|
|
// only channels (no entries representing categories), sorted according to
|
|
|
|
// category position and position
|
|
|
|
SortFilterProxyModel {
|
|
|
|
id: onlyChannelsModel
|
|
|
|
|
|
|
|
sourceModel: root.model
|
|
|
|
|
|
|
|
filters: ValueFilter {
|
|
|
|
roleName: "isCategory"
|
|
|
|
value: false
|
|
|
|
}
|
|
|
|
|
|
|
|
sorters: [
|
|
|
|
RoleSorter {
|
|
|
|
roleName: "categoryPosition"
|
|
|
|
priority: 2 // Higher number -> higher priority
|
|
|
|
},
|
|
|
|
RoleSorter {
|
|
|
|
roleName: "position"
|
|
|
|
priority: 1
|
|
|
|
}
|
|
|
|
]
|
2022-12-20 00:19:46 +00:00
|
|
|
}
|
|
|
|
|
2024-04-05 10:45:50 +00:00
|
|
|
// only items representing categories
|
|
|
|
SortFilterProxyModel {
|
|
|
|
id: categoriesModel
|
|
|
|
|
|
|
|
sourceModel: root.model
|
|
|
|
|
|
|
|
filters: ValueFilter {
|
|
|
|
roleName: "isCategory"
|
|
|
|
value: true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// categories, name role renamed to categoryName
|
|
|
|
RolesRenamingModel {
|
|
|
|
id: categoriesModelRenamed
|
|
|
|
|
|
|
|
sourceModel: categoriesModel
|
|
|
|
|
|
|
|
mapping: RoleRename {
|
|
|
|
from: "name"
|
|
|
|
to: "categoryName"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// categories joined to channels model in order to provide channelName,
|
|
|
|
// in order to be used in section.property.
|
|
|
|
LeftJoinModel {
|
|
|
|
id: joined
|
|
|
|
|
|
|
|
leftModel: onlyChannelsModel
|
|
|
|
rightModel: categoriesModelRenamed
|
|
|
|
|
|
|
|
joinRole: "categoryId"
|
|
|
|
rolesToJoin: "categoryName"
|
|
|
|
}
|
|
|
|
|
|
|
|
// final filtering based on user's input in search bar
|
|
|
|
SortFilterProxyModel {
|
|
|
|
id: filtered
|
|
|
|
|
|
|
|
sourceModel: joined
|
|
|
|
|
2024-08-30 15:31:53 +00:00
|
|
|
filters: SearchFilter {
|
2024-04-05 10:45:50 +00:00
|
|
|
roleName: "name"
|
2024-08-30 15:31:53 +00:00
|
|
|
searchPhrase: searcher.text
|
2024-04-05 10:45:50 +00:00
|
|
|
}
|
|
|
|
}
|
2022-12-20 00:19:46 +00:00
|
|
|
|
|
|
|
QtObject {
|
|
|
|
id: d
|
|
|
|
|
2023-02-10 14:46:18 +00:00
|
|
|
readonly property int defaultVMargin: 9
|
|
|
|
readonly property int maxHeightCountNo: 5
|
|
|
|
readonly property int itemStandardHeight: 44
|
2023-02-28 14:10:50 +00:00
|
|
|
readonly property var selectedChannels: new Set()
|
2022-12-20 00:19:46 +00:00
|
|
|
|
|
|
|
function resolveEmoji(emoji) {
|
|
|
|
return !!emoji ? emoji : ""
|
|
|
|
}
|
|
|
|
|
|
|
|
function resolveColor(color, colorId) {
|
|
|
|
return !!color ? color : Theme.palette.userCustomizationColors[colorId]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-10 14:46:18 +00:00
|
|
|
contentItem: ColumnLayout {
|
2022-12-20 00:19:46 +00:00
|
|
|
spacing: 0
|
|
|
|
|
|
|
|
SearchBox {
|
|
|
|
id: searcher
|
|
|
|
|
|
|
|
Layout.fillWidth: true
|
|
|
|
|
|
|
|
topPadding: 0
|
|
|
|
bottomPadding: 0
|
|
|
|
minimumHeight: 36
|
|
|
|
maximumHeight: 36
|
|
|
|
}
|
|
|
|
|
|
|
|
StatusListItem {
|
|
|
|
Layout.fillWidth: true
|
2023-02-10 14:46:18 +00:00
|
|
|
Layout.topMargin: d.defaultVMargin
|
|
|
|
Layout.preferredHeight: d.itemStandardHeight
|
2022-12-20 00:19:46 +00:00
|
|
|
|
2023-01-20 11:08:20 +00:00
|
|
|
visible: root.allowChoosingEntireCommunity
|
2022-12-20 00:19:46 +00:00
|
|
|
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
|
2023-01-20 11:08:20 +00:00
|
|
|
|
|
|
|
visible: root.allowChoosingEntireCommunity
|
2022-12-20 00:19:46 +00:00
|
|
|
}
|
2024-04-05 10:45:50 +00:00
|
|
|
|
|
|
|
StatusListView {
|
|
|
|
id: listView
|
|
|
|
|
|
|
|
model: filtered
|
|
|
|
|
2022-12-20 00:19:46 +00:00
|
|
|
Layout.fillWidth: true
|
2024-04-05 10:45:50 +00:00
|
|
|
Layout.minimumHeight: Math.min(d.maxHeightCountNo * d.itemStandardHeight,
|
|
|
|
contentHeight)
|
2023-02-10 14:46:18 +00:00
|
|
|
Layout.maximumHeight: Layout.minimumHeight
|
|
|
|
Layout.bottomMargin: d.defaultVMargin
|
2024-04-05 10:45:50 +00:00
|
|
|
Layout.topMargin: !root.allowChoosingEntireCommunity
|
|
|
|
&& !root.allowChoosingEntireCommunity ? d.defaultVMargin : 0
|
2022-12-20 00:19:46 +00:00
|
|
|
|
2024-04-05 10:45:50 +00:00
|
|
|
Component {
|
|
|
|
id: addChannelButtonComponent
|
2022-12-20 00:19:46 +00:00
|
|
|
|
|
|
|
StatusIconTextButton {
|
2024-04-05 10:45:50 +00:00
|
|
|
height: 36
|
2022-12-20 00:19:46 +00:00
|
|
|
leftPadding: 8
|
|
|
|
spacing: 8
|
|
|
|
statusIcon: "add"
|
|
|
|
icon.width: 16
|
|
|
|
icon.height: 16
|
|
|
|
iconRotation: 0
|
|
|
|
text: qsTr("Add channel")
|
|
|
|
onClicked: root.addChannelClicked()
|
|
|
|
}
|
2024-04-05 10:45:50 +00:00
|
|
|
}
|
2022-12-20 00:19:46 +00:00
|
|
|
|
2024-04-05 10:45:50 +00:00
|
|
|
header: root.showAddChannelButton ? addChannelButtonComponent : null
|
2022-12-20 00:19:46 +00:00
|
|
|
|
2024-04-05 10:45:50 +00:00
|
|
|
section.delegate: CategoryListItem {
|
|
|
|
title: section
|
2022-12-20 00:19:46 +00:00
|
|
|
|
2024-04-05 10:45:50 +00:00
|
|
|
width: ListView.view.width
|
2022-12-20 00:19:46 +00:00
|
|
|
|
2024-04-05 10:45:50 +00:00
|
|
|
MouseArea {
|
|
|
|
anchors.fill: parent
|
2022-12-20 00:19:46 +00:00
|
|
|
|
2024-04-05 10:45:50 +00:00
|
|
|
onClicked: {
|
|
|
|
const categoryId = ModelUtils.getByKey(
|
|
|
|
categoriesModel, "name", section, "categoryId")
|
|
|
|
const allKeys = ModelUtils.modelToArray(
|
|
|
|
filtered, ["itemId", "categoryId"])
|
|
|
|
const inCategoryKeys = allKeys.filter(
|
|
|
|
e => e.categoryId === categoryId)
|
|
|
|
const allSelected = inCategoryKeys.every(
|
|
|
|
e => d.selectedChannels.has(e.itemId))
|
2022-12-20 00:19:46 +00:00
|
|
|
|
2024-04-05 10:45:50 +00:00
|
|
|
if (allSelected)
|
|
|
|
inCategoryKeys.forEach(
|
|
|
|
e => d.selectedChannels.delete(e.itemId))
|
|
|
|
else
|
|
|
|
inCategoryKeys.forEach(
|
|
|
|
e => d.selectedChannels.add(e.itemId))
|
2022-12-20 00:19:46 +00:00
|
|
|
|
2024-04-05 10:45:50 +00:00
|
|
|
d.selectedChannelsChanged()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-12-20 00:19:46 +00:00
|
|
|
|
2024-04-05 10:45:50 +00:00
|
|
|
section.property: "categoryName"
|
2022-12-20 00:19:46 +00:00
|
|
|
|
2024-04-05 10:45:50 +00:00
|
|
|
delegate: CommunityListItem {
|
|
|
|
id: communitySubItem
|
2022-12-20 00:19:46 +00:00
|
|
|
|
2024-04-08 15:09:20 +00:00
|
|
|
objectName: "communityListItem_" + model.name
|
|
|
|
|
2024-04-05 10:45:50 +00:00
|
|
|
width: ListView.view.width
|
|
|
|
visible: show
|
2023-05-11 14:35:38 +00:00
|
|
|
|
2024-04-05 10:45:50 +00:00
|
|
|
title: "#" + model.name
|
2023-05-11 14:35:38 +00:00
|
|
|
|
2024-04-05 10:45:50 +00:00
|
|
|
asset.name: model.icon ?? ""
|
|
|
|
asset.emoji: d.resolveEmoji(model.emoji)
|
|
|
|
asset.color: d.resolveColor(model.color, model.colorId)
|
2023-05-11 14:35:38 +00:00
|
|
|
|
2024-04-05 10:45:50 +00:00
|
|
|
checked: d.selectedChannels.has(model.itemId)
|
2023-05-11 14:35:38 +00:00
|
|
|
|
2024-04-05 10:45:50 +00:00
|
|
|
checkBox.onToggled: {
|
|
|
|
if (checked)
|
|
|
|
d.selectedChannels.add(model.itemId)
|
|
|
|
else
|
|
|
|
d.selectedChannels.delete(model.itemId)
|
2023-05-11 14:35:38 +00:00
|
|
|
|
2024-04-05 10:45:50 +00:00
|
|
|
d.selectedChannelsChanged()
|
2023-05-11 14:35:38 +00:00
|
|
|
}
|
|
|
|
}
|
2022-12-20 00:19:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
StatusButton {
|
|
|
|
Layout.fillWidth: true
|
2023-05-11 14:35:38 +00:00
|
|
|
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2022-12-20 00:19:46 +00:00
|
|
|
|
|
|
|
onClicked: {
|
|
|
|
if (radioButton.checked) {
|
|
|
|
root.communitySelected()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
root.channelsSelected(Array.from(d.selectedChannels.values()))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|