From d58dd3331b4e1d6cb702cae7bc89ac17fd556029 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Cie=C5=9Blak?= Date: Tue, 20 Dec 2022 01:19:46 +0100 Subject: [PATCH] feat(CommunityPermissions): Dropdown for channels selection added --- .../community/CommunityCategoryListItem.qml | 13 + .../controls/community/CommunityListItem.qml | 46 +++ .../Chat/controls/community/InDropdown.qml | 376 ++++++++++++++++++ .../AppLayouts/Chat/controls/community/qmldir | 5 +- 4 files changed, 439 insertions(+), 1 deletion(-) create mode 100644 ui/app/AppLayouts/Chat/controls/community/CommunityCategoryListItem.qml create mode 100644 ui/app/AppLayouts/Chat/controls/community/CommunityListItem.qml create mode 100644 ui/app/AppLayouts/Chat/controls/community/InDropdown.qml diff --git a/ui/app/AppLayouts/Chat/controls/community/CommunityCategoryListItem.qml b/ui/app/AppLayouts/Chat/controls/community/CommunityCategoryListItem.qml new file mode 100644 index 0000000000..c8fd447785 --- /dev/null +++ b/ui/app/AppLayouts/Chat/controls/community/CommunityCategoryListItem.qml @@ -0,0 +1,13 @@ +import StatusQ.Core.Theme 0.1 + +CommunityListItem { + implicitHeight: 34 + + statusListItemTitle.font.pixelSize: 12 + statusListItemTitle.color: Theme.palette.baseColor1 + + checkBox.visible: false + + asset.isLetterIdenticon: false + asset.isImage: false +} diff --git a/ui/app/AppLayouts/Chat/controls/community/CommunityListItem.qml b/ui/app/AppLayouts/Chat/controls/community/CommunityListItem.qml new file mode 100644 index 0000000000..7b4dde57ec --- /dev/null +++ b/ui/app/AppLayouts/Chat/controls/community/CommunityListItem.qml @@ -0,0 +1,46 @@ +import QtQuick 2.14 + +import StatusQ.Components 0.1 +import StatusQ.Controls 0.1 + +StatusListItem { + property alias checked: checkBox.checked + property alias checkState: checkBox.checkState + readonly property alias checkBox: checkBox + + implicitHeight: 44 + leftPadding: 8 + rightPadding: 3 + statusListItemTitle.font.pixelSize: 13 + + statusListItemTitleArea.anchors.leftMargin: 8 + + asset.bgWidth: 32 + asset.bgHeight: 32 + + asset.isLetterIdenticon: true + asset.letterSize: 12 + asset.isImage: model.icon.includes("data") + asset.width: 32 + asset.height: 32 + + components: [ + StatusCheckBox { + id: checkBox + + size: StatusCheckBox.Size.Small + rightPadding: 0 + } + ] + + // using MouseArea instead of build-in 'clicked' signal to avoid + // intercepting event by the StatusCheckBox + MouseArea { + anchors.fill: parent + onClicked: { + checkBox.toggle() + checkBox.toggled() + } + cursorShape: Qt.PointingHandCursor + } +} diff --git a/ui/app/AppLayouts/Chat/controls/community/InDropdown.qml b/ui/app/AppLayouts/Chat/controls/community/InDropdown.qml new file mode 100644 index 0000000000..17c49bf00a --- /dev/null +++ b/ui/app/AppLayouts/Chat/controls/community/InDropdown.qml @@ -0,0 +1,376 @@ +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 string communityName + property string communityImage + property color 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 var selectedChannels: new Map() + + 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.set(model.itemId, { + itemId: model.itemId, + name: model.name, + color: d.resolveColor(model.color, model.colorId), + emoji: d.resolveEmoji(model.emoji) + }) + } + + function removeFromSelectedChannels(model) { + selectedChannels.delete(model.itemId) + } + } + + ColumnLayout { + anchors.top: parent.top + anchors.right: parent.right + anchors.left: parent.left + + spacing: 0 + + SearchBox { + id: searcher + + Layout.fillWidth: true + + topPadding: 0 + bottomPadding: 0 + minimumHeight: 36 + maximumHeight: 36 + } + + StatusListItem { + Layout.fillWidth: true + Layout.topMargin: 9 + Layout.preferredHeight: 44 + + + 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 + } + + StatusScrollView { + id: scrollView + + Layout.fillWidth: true + Layout.bottomMargin: 9 + + padding: 0 + + ColumnLayout { + id: scollableColumn + + spacing: 0 + width: scrollView.width + + StatusIconTextButton { + Layout.preferredHeight: 36 + + 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 + + sorters: [ + RoleSorter { roleName: "isCategory" }, + RoleSorter { roleName: "position" } + ] + } + + ColumnLayout { + id: column + + Layout.fillWidth: true + spacing: 0 + + readonly property var topModel: model + readonly property alias checkBox: loader.item + property int checkedCount: 0 + + visible: { + if (!model.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.topMargin: model.isCategory ? 9 : 0 + + sourceComponent: model.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 === model.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) + } + } + } + } + + Repeater { + id: subItemsRepeater + + model: SortFilterProxyModel { + sourceModel: topModel.isCategory ? topModel.subItems : null + sorters: RoleSorter { roleName: "position" } + } + + function setAll(checkState) { + const subItemsCount = count + + for (let i = 0; i < subItemsCount; i++) { + itemAt(i).checkState = checkState + } + } + + CommunityListItem { + id: communitySubItem + + Layout.fillWidth: true + + readonly property bool show: d.search(model.name, searcher.text) + || checked + + 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())) + } + } + } +} diff --git a/ui/app/AppLayouts/Chat/controls/community/qmldir b/ui/app/AppLayouts/Chat/controls/community/qmldir index f72e6e9aca..cd5cb030d8 100644 --- a/ui/app/AppLayouts/Chat/controls/community/qmldir +++ b/ui/app/AppLayouts/Chat/controls/community/qmldir @@ -1,3 +1,6 @@ -HoldingsDropdown 1.0 HoldingsDropdown.qml +CommunityCategoryListItem 1.0 CommunityCategoryListItem.qml +CommunityListItem 1.0 CommunityListItem.qml HoldingTypes 1.0 HoldingTypes.qml +HoldingsDropdown 1.0 HoldingsDropdown.qml +InDropdown 1.0 InDropdown.qml PermissionItem 1.0 PermissionItem.qml