diff --git a/src/app_service/service/community/service.nim b/src/app_service/service/community/service.nim index a18fa98997..cc0acd729e 100644 --- a/src/app_service/service/community/service.nim +++ b/src/app_service/service/community/service.nim @@ -611,6 +611,10 @@ QtObject: pinMessageAllMembersEnabled: bool) = try: var image = singletonInstance.utils.formatImagePath(imageUrl) + var tagsString = tags + if len(tagsString) == 0: + tagsString = "[]" + let response = status_go.createCommunity( name, description, @@ -618,7 +622,7 @@ QtObject: outroMessage, access, color, - tags, + tagsString, image, aX, aY, bX, bY, historyArchiveSupportEnabled, @@ -659,6 +663,9 @@ QtObject: # TODO: refactor status-go to use `CroppedImage` for logo as it does for banner. This is an API breaking change, sync with mobile let logoJson = parseJson(logoJsonStr) let cropRectJson = logoJson["cropRect"] + var tagsString = tags + if len(tagsString) == 0: + tagsString = "[]" let response = status_go.editCommunity( id, name, @@ -667,7 +674,7 @@ QtObject: outroMessage, access, color, - tags, + tagsString, logoJson["imagePath"].getStr(), int(cropRectJson["x"].getFloat()), int(cropRectJson["y"].getFloat()), diff --git a/ui/StatusQ b/ui/StatusQ index fe355eebf8..84c529d58d 160000 --- a/ui/StatusQ +++ b/ui/StatusQ @@ -1 +1 @@ -Subproject commit fe355eebf85e2ec7457a2d92e72ecf6506fa5394 +Subproject commit 84c529d58d80f6cd26455db835f9f4ec2b7d2f76 diff --git a/ui/app/AppLayouts/Chat/panels/communities/CommunityEditSettingsPanel.qml b/ui/app/AppLayouts/Chat/panels/communities/CommunityEditSettingsPanel.qml index 74ee826e96..ed849b1cc6 100644 --- a/ui/app/AppLayouts/Chat/panels/communities/CommunityEditSettingsPanel.qml +++ b/ui/app/AppLayouts/Chat/panels/communities/CommunityEditSettingsPanel.qml @@ -18,6 +18,9 @@ import StatusQ.Controls.Validators 0.1 import "../../controls/community" +import "../../../CommunitiesPortal/controls" +import "../../../CommunitiesPortal/panels" + Flickable { id: root @@ -75,12 +78,47 @@ Flickable { CommunityColorPicker { id: colorPicker + onPick: Global.openPopup(pickColorComponent) Layout.fillWidth: true + + Component { + id: pickColorComponent + + StatusStackModal { + replaceItem: CommunityColorPanel { + Component.onCompleted: color = colorPicker.color + onAccepted: { + colorPicker.color = color; + close(); + } + } + onClosed: destroy() + } + } } CommunityTagsPicker { id: tagsPicker + onPick: Global.openPopup(pickTagsComponent) Layout.fillWidth: true + + Component { + id: pickTagsComponent + + StatusStackModal { + replaceItem: CommunityTagsPanel { + Component.onCompleted: { + tags = tagsPicker.tags; + selectedTags = tagsPicker.selectedTags; + } + onAccepted: { + tagsPicker.selectedTags = selectedTags; + close(); + } + } + onClosed: destroy() + } + } } StatusModalDivider { diff --git a/ui/app/AppLayouts/Chat/popups/community/CommunityTagsPopup.qml b/ui/app/AppLayouts/Chat/popups/community/CommunityTagsPopup.qml deleted file mode 100644 index 0756667bc3..0000000000 --- a/ui/app/AppLayouts/Chat/popups/community/CommunityTagsPopup.qml +++ /dev/null @@ -1,159 +0,0 @@ -import QtQuick 2.14 -import QtQuick.Controls 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 utils 1.0 - -StatusModal { - id: root - - property string tags - property string selectedTags - property int maxSelectedTags: 4 - - signal accepted(string selectedTags) - - function updateSelectedTags() { - var array = selectedTags.length ? JSON.parse(selectedTags) : []; - - d.cntSelectedTags = 0; - for (let i = 0; i < d.tagsModel.count; ++i) { - let item = d.tagsModel.get(i); - if (array.indexOf(item.name) != -1) { - item.selected = true; - d.cntSelectedTags++; - } else { - item.selected = false; - } - d.tagsModel.set(i, item); - } - } - - onTagsChanged: { - var obj = JSON.parse(tags); - - d.cntSelectedTags = 0; - d.tagsModel.clear(); - for (const key of Object.keys(obj)) { - d.tagsModel.append({ name: key, emoji: obj[key], selected: false }); - } - } - onSelectedTagsChanged: updateSelectedTags() - - width: 680 - implicitHeight: 820 - - header.title: qsTr("Community Tags") - - QtObject { - id: d - - property int cntSelectedTags: 0 - property ListModel tagsModel: ListModel {} - } - - rightButtons: [ - StatusButton { - text: qsTr("Confirm Community Tags") - onClicked: { - var selectedTags = []; - for (let i = 0; i < d.tagsModel.count; ++i) { - let item = d.tagsModel.get(i); - if (item.selected) - selectedTags.push(item.name); - } - root.accepted(selectedTags.length ? JSON.stringify(selectedTags) : ""); - root.close(); - } - } - ] - - leftButtons: [ - StatusRoundButton { - id: btnBack - icon.name: "arrow-left" - icon.width: 20 - icon.height: 16 - onClicked: { - root.updateSelectedTags(); - root.close(); - } - } - ] - - contentItem: ScrollView { - id: scroll - width: parent.width - topPadding: 30 - leftPadding: 20 - rightPadding: 20 - bottomPadding: 20 - contentHeight: column.height - - ScrollBar.vertical.policy: ScrollBar.AsNeeded - ScrollBar.horizontal.policy: ScrollBar.AlwaysOff - - clip: true - - ColumnLayout { - id: column - width: scroll.width - scroll.leftPadding - scroll.rightPadding - spacing: 20 - - StatusInput { - id: tagsFilter - leftPadding: 0 - rightPadding: 0 - label: qsTr("Select tags that will fit your Community") - input.icon.name: "search" - input.placeholderText: qsTr("Search tags") - Layout.fillWidth: true - } - - StatusCommunityTags { - filterString: tagsFilter.text - model: d.tagsModel - enabled: d.cntSelectedTags < maxSelectedTags - onClicked: { - d.cntSelectedTags++; - item.selected = true; - } - Layout.fillWidth: true - } - - StatusModalDivider { - Layout.fillWidth: true - } - - RowLayout { - StatusBaseText { - text: qsTr("Selected tags") - font.pixelSize: 15 - Layout.fillWidth: true - } - - StatusBaseText { - text: d.cntSelectedTags + "/" + maxSelectedTags - color: Theme.palette.baseColor1 - font.pixelSize: 13 - } - } - - StatusCommunityTags { - model: d.tagsModel - showOnlySelected: true - onClicked: { - d.cntSelectedTags--; - item.selected = false; - } - Layout.fillWidth: true - } - } - } -} \ No newline at end of file diff --git a/ui/app/AppLayouts/Chat/controls/community/CommunityColorPicker.qml b/ui/app/AppLayouts/CommunitiesPortal/controls/CommunityColorPicker.qml similarity index 64% rename from ui/app/AppLayouts/Chat/controls/community/CommunityColorPicker.qml rename to ui/app/AppLayouts/CommunitiesPortal/controls/CommunityColorPicker.qml index 1a9b8aa9d5..77c3e22459 100644 --- a/ui/app/AppLayouts/Chat/controls/community/CommunityColorPicker.qml +++ b/ui/app/AppLayouts/CommunitiesPortal/controls/CommunityColorPicker.qml @@ -14,6 +14,8 @@ ColumnLayout { property color color: Theme.palette.primaryColor1 + signal pick() + spacing: 8 StatusBaseText { @@ -30,26 +32,12 @@ ColumnLayout { bgColor: root.color contentColor: Theme.palette.indirectColor1 text: root.color.toString() + onClicked: root.pick() - onClicked: { - colorDialog.color = root.color - colorDialog.open() - } onTextChanged: { validationError = Utils.validateAndReturnError( text, Utils.Validate.NoEmpty | Utils.Validate.TextHexColor) } - - StatusColorDialog { - id: colorDialog - anchors.centerIn: parent - header.title: qsTr("Community Colour") - previewText: qsTr("White text should be legable on top of this colour") - acceptText: qsTr("Select Community Colour") - onAccepted: { - root.color = color - } - } } } diff --git a/ui/app/AppLayouts/Chat/controls/community/CommunityTagsPicker.qml b/ui/app/AppLayouts/CommunitiesPortal/controls/CommunityTagsPicker.qml similarity index 78% rename from ui/app/AppLayouts/Chat/controls/community/CommunityTagsPicker.qml rename to ui/app/AppLayouts/CommunitiesPortal/controls/CommunityTagsPicker.qml index 5eb45f0e7b..9f65a2449f 100644 --- a/ui/app/AppLayouts/Chat/controls/community/CommunityTagsPicker.qml +++ b/ui/app/AppLayouts/CommunitiesPortal/controls/CommunityTagsPicker.qml @@ -9,14 +9,14 @@ import StatusQ.Components 0.1 import StatusQ.Popups 0.1 import StatusQ.Controls 0.1 -import "../../popups/community" - ColumnLayout { id: root property string tags property string selectedTags + signal pick() + onSelectedTagsChanged: { var obj = JSON.parse(tags); var array = JSON.parse(selectedTags); @@ -46,6 +46,7 @@ ColumnLayout { StatusPickerButton { bgColor: root.selectedTags == "" ? Theme.palette.baseColor2 : "transparent" text: root.selectedTags == "" ? "Choose tags describing the community" : "" + onClicked: root.pick() Layout.fillWidth: true StatusCommunityTags { @@ -54,17 +55,5 @@ ColumnLayout { active: false width: parent.width } - - onClicked: { - tagsDialog.tags = root.tags; - tagsDialog.selectedTags = root.selectedTags; - tagsDialog.open(); - } - - CommunityTagsPopup { - id: tagsDialog - anchors.centerIn: parent - onAccepted: root.selectedTags = selectedTags - } } } \ No newline at end of file diff --git a/ui/app/AppLayouts/CommunitiesPortal/panels/CommunityColorPanel.qml b/ui/app/AppLayouts/CommunitiesPortal/panels/CommunityColorPanel.qml new file mode 100644 index 0000000000..36b1732541 --- /dev/null +++ b/ui/app/AppLayouts/CommunitiesPortal/panels/CommunityColorPanel.qml @@ -0,0 +1,126 @@ +import QtQuick 2.14 +import QtQuick.Controls 2.14 +import QtQuick.Layouts 1.12 + +import StatusQ.Core 0.1 +import StatusQ.Core.Theme 0.1 +import StatusQ.Controls 0.1 +import StatusQ.Controls.Validators 0.1 +import StatusQ.Components 0.1 +import StatusQ.Popups 0.1 + +ScrollView { + id: root + + property string title: qsTr("Community Colour") + + property var rightButtons: StatusButton { + text: qsTr("Select Community Colour") + onClicked: root.accepted() + } + + property alias color: colorSpace.color + + signal accepted() + + onColorChanged: { + if (!hexInput.locked) + hexInput.text = color.toString(); + + if (colorSelectionGrid.selectedColor != color) + colorSelectionGrid.selectedColorIndex = -1; + } + + Component.onCompleted: { + hexInput.text = color.toString(); + } + + contentHeight: column.height + + ScrollBar.vertical.policy: ScrollBar.AsNeeded + ScrollBar.horizontal.policy: ScrollBar.AlwaysOff + + ColumnLayout { + id: column + width: root.width - root.leftPadding - root.rightPadding + spacing: 12 + + StatusColorSpace { + id: colorSpace + + property real hueFactor: Math.max(rootColor.g + rootColor.b * 0.4, + rootColor.g + rootColor.r * 0.6) + + minSaturate: Math.max(0.4, hueFactor * 0.55) + maxSaturate: 1.0 + minValue: 0.4 + // Curve to pick colors readable with white text + maxValue: Math.min(1.0, 1.65 - hueFactor * 0.5) + Layout.alignment: Qt.AlignHCenter + } + + StatusInput { + id: hexInput + + property color newColor: text + // TODO: editingFinished() signal instead of this crutch + property bool locked: false + + implicitWidth: 256 + validators: [ + StatusRegularExpressionValidator { + regularExpression: /^#(?:[0-9a-fA-F]{3}){1,2}$/ + errorMessage: qsTr("This is not a valid colour") + } + ] + validationMode: StatusInput.ValidationMode.Always + + onNewColorChanged: { + if (!valid) + return; + + locked = true; + root.color = newColor; + locked = false; + } + Layout.alignment: Qt.AlignHCenter + } + + StatusBaseText { + text: qsTr("White text should be legable on top of this colour") + font.pixelSize: 15 + } + + Rectangle { + implicitHeight: 48 + radius: 10 + color: root.color + Layout.fillWidth: true + + StatusBaseText { + id: preview + x: 16 + y: 16 + text: root.color.toString() + color: Theme.palette.white + font.pixelSize: 15 + } + } + + StatusBaseText { + text: qsTr("Standard colours") + font.pixelSize: 15 + } + + StatusColorSelectorGrid { + id: colorSelectionGrid + columns: 8 + model: ["#4360df", "#887af9", "#d37ef4", "#51d0f0", "#26a69a", "#7cda00", "#eab700", "#fa6565"] + selectedColorIndex: -1 + onColorSelected: { + root.color = selectedColor; + } + Layout.alignment: Qt.AlignHCenter + } + } +} diff --git a/ui/app/AppLayouts/CommunitiesPortal/panels/CommunityTagsPanel.qml b/ui/app/AppLayouts/CommunitiesPortal/panels/CommunityTagsPanel.qml new file mode 100644 index 0000000000..709f06efcb --- /dev/null +++ b/ui/app/AppLayouts/CommunitiesPortal/panels/CommunityTagsPanel.qml @@ -0,0 +1,130 @@ +import QtQuick 2.14 +import QtQuick.Controls 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 utils 1.0 + +ScrollView { + id: root + + property string tags + property string selectedTags + property int maxSelectedTags: 4 + + property string title: qsTr("Community Tags") + + property var rightButtons: StatusButton { + text: qsTr("Confirm Community Tags") + onClicked: { + var selectedTags = []; + for (let i = 0; i < d.tagsModel.count; ++i) { + let item = d.tagsModel.get(i); + if (item.selected) + selectedTags.push(item.name); + } + root.accepted(selectedTags.length ? JSON.stringify(selectedTags) : ""); + } + } + + signal accepted(string selectedTags) + + function updateSelectedTags() { + var array = selectedTags.length ? JSON.parse(selectedTags) : []; + + d.cntSelectedTags = 0; + for (let i = 0; i < d.tagsModel.count; ++i) { + let item = d.tagsModel.get(i); + if (array.indexOf(item.name) != -1) { + item.selected = true; + d.cntSelectedTags++; + } else { + item.selected = false; + } + d.tagsModel.set(i, item); + } + } + + onTagsChanged: { + var obj = JSON.parse(tags); + + d.cntSelectedTags = 0; + d.tagsModel.clear(); + for (const key of Object.keys(obj)) { + d.tagsModel.append({ name: key, emoji: obj[key], selected: false }); + } + } + onSelectedTagsChanged: updateSelectedTags() + + contentHeight: column.height + + ScrollBar.vertical.policy: ScrollBar.AsNeeded + ScrollBar.horizontal.policy: ScrollBar.AlwaysOff + + QtObject { + id: d + + property int cntSelectedTags: 0 + property ListModel tagsModel: ListModel {} + } + + ColumnLayout { + id: column + width: root.width - root.leftPadding - root.rightPadding + spacing: 20 + + StatusInput { + id: tagsFilter + leftPadding: 0 + rightPadding: 0 + label: qsTr("Select tags that will fit your Community") + input.icon.name: "search" + input.placeholderText: qsTr("Search tags") + Layout.fillWidth: true + } + + StatusCommunityTags { + filterString: tagsFilter.text + model: d.tagsModel + enabled: d.cntSelectedTags < maxSelectedTags + onClicked: { + d.cntSelectedTags++; + item.selected = true; + } + Layout.fillWidth: true + } + + StatusModalDivider { + Layout.fillWidth: true + } + + RowLayout { + StatusBaseText { + text: qsTr("Selected tags") + font.pixelSize: 15 + Layout.fillWidth: true + } + + StatusBaseText { + text: d.cntSelectedTags + "/" + maxSelectedTags + color: Theme.palette.baseColor1 + font.pixelSize: 13 + } + } + + StatusCommunityTags { + model: d.tagsModel + showOnlySelected: true + onClicked: { + d.cntSelectedTags--; + item.selected = false; + } + Layout.fillWidth: true + } + } +} \ No newline at end of file diff --git a/ui/app/AppLayouts/CommunitiesPortal/popups/CreateCommunityPopup.qml b/ui/app/AppLayouts/CommunitiesPortal/popups/CreateCommunityPopup.qml index 15caad0267..7803c2da93 100644 --- a/ui/app/AppLayouts/CommunitiesPortal/popups/CreateCommunityPopup.qml +++ b/ui/app/AppLayouts/CommunitiesPortal/popups/CreateCommunityPopup.qml @@ -17,51 +17,23 @@ import StatusQ.Popups 0.1 import "../../Chat/controls/community" -StatusModal { +import "../controls" +import "../panels" + +StatusStackModal { id: root property var store - width: 640 - padding: 16 + stackTitle: qsTr("Create New Community") - header.title: qsTr("Create New Community") - - rightButtons: [ - StatusButton { - text: qsTr("Next") - enabled: generalView.canGoNext - visible: generalView.visible - - onClicked: stackLayout.currentIndex = stackLayout.currentIndex + 1 - }, - StatusButton { - text: qsTr("Create Community") - enabled: introMessageInput.valid && outroMessageInput.valid - visible: introMessageInput.visible - - onClicked: d.createCommunity() - } - ] - - leftButtons: [ - StatusRoundButton { - id: btnBack - icon.name: "arrow-right" - icon.width: 20 - icon.height: 16 - rotation: 180 - visible: stackLayout.currentIndex !== 0 - - onClicked: stackLayout.currentIndex = 0 - } - ] - - StackLayout { - id: stackLayout - - anchors.fill: parent + finishButton: StatusButton { + text: qsTr("Create Community") + enabled: introMessageInput.valid && outroMessageInput.valid + onClicked: d.createCommunity() + } + stackItems: [ Flickable { id: generalView @@ -82,7 +54,7 @@ StatusModal { id: nameInput Layout.fillWidth: true Component.onCompleted: nameInput.input.forceActiveFocus( - Qt.MouseFocusReason) + Qt.MouseFocusReason) } CommunityDescriptionInput { @@ -97,13 +69,42 @@ StatusModal { CommunityColorPicker { id: colorPicker + onPick: root.replace(colorPanel) Layout.fillWidth: true + + Component { + id: colorPanel + + CommunityColorPanel { + Component.onCompleted: color = colorPicker.color + onAccepted: { + colorPicker.color = color; + root.replace(null); + } + } + } } CommunityTagsPicker { id: communityTagsPicker tags: root.store.communityTags + onPick: root.replace(tagsPanel) Layout.fillWidth: true + + Component { + id: tagsPanel + + CommunityTagsPanel { + Component.onCompleted: { + tags = communityTagsPicker.tags; + selectedTags = communityTagsPicker.selectedTags; + } + onAccepted: { + communityTagsPicker.selectedTags = selectedTags; + root.replace(null); + } + } + } } StatusModalDivider { @@ -121,8 +122,7 @@ StatusModal { Layout.fillHeight: true } } - } - + }, ColumnLayout { id: introOutroMessageView @@ -143,7 +143,7 @@ StatusModal { Layout.fillWidth: true } } - } + ] QtObject { id: d diff --git a/ui/app/AppLayouts/CommunitiesPortal/stores/CommunitiesStore.qml b/ui/app/AppLayouts/CommunitiesPortal/stores/CommunitiesStore.qml index 8f69d2bf5c..76eb3dd5e6 100644 --- a/ui/app/AppLayouts/CommunitiesPortal/stores/CommunitiesStore.qml +++ b/ui/app/AppLayouts/CommunitiesPortal/stores/CommunitiesStore.qml @@ -31,12 +31,15 @@ QtObject { ListElement { name: "privacy"; emoji: "👻"} } + property string communityTags: communitiesModuleInst.tags + function createCommunity(args = { name: "", description: "", introMessage: "", outroMessage: "", color: "", + tags: "", image: { src: "", AX: 0, @@ -51,7 +54,8 @@ QtObject { } }) { return communitiesModuleInst.createCommunity( - args.name, args.description, args.introMessage, args.outroMessage, args.options.checkedMembership, args.color, + args.name, args.description, args.introMessage, args.outroMessage, args.options.checkedMembership, + args.color, args.tags, args.image.src, args.image.AX, args.image.AY, args.image.BX, args.image.BY, args.options.historyArchiveSupportEnabled, args.options.pinMessagesAllowedForMembers); } diff --git a/ui/imports/utils/Global.qml b/ui/imports/utils/Global.qml index 80ec3de7d3..f9a9cfdf96 100644 --- a/ui/imports/utils/Global.qml +++ b/ui/imports/utils/Global.qml @@ -39,6 +39,9 @@ QtObject { function openPopup(popupComponent, params = {}) { const popup = popupComponent.createObject(root.appMain, params); + popup.x = Qt.binding(function() { return (root.appMain.width - popup.width) / 2; }); + popup.y = popup.margins; + popup.height = Qt.binding(function() { return root.appMain.height - popup.margins * 2; }); popup.open(); return popup; }