From 26dddcaff93d3cb522d37f181d5516e4a577c8a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Tinkl?= Date: Tue, 8 Oct 2024 22:22:22 +0200 Subject: [PATCH] feat(CreateCommunityPopup): validation and ensuring min. 1 tag added - enable the Next button, and delay the validation after it's been clicked - visually display required fields also for logo, banner, and tags (min 1 tag is required now) - when pasting over limit, chop the text to the maximum length, instead of just leaving the text field empty - do not let the popup autoclose on clicking outside or pressing Esc - add a StoryBook page - minor cleanups and alignments to the latest Figma designs/flows Fixes #13966 Fixes #16479 Fixes #14902 --- storybook/pages/CreateCommunityPopupPage.qml | 181 ++++++++++++++++++ .../Components/StatusCommunityTags.qml | 4 +- .../src/StatusQ/Controls/StatusBaseInput.qml | 2 +- .../Controls/StatusColorSelectorGrid.qml | 5 +- .../src/StatusQ/Controls/StatusInput.qml | 17 +- .../StatusQ/Controls/StatusPickerButton.qml | 11 +- .../Validators/StatusMinLengthValidator.qml | 5 +- .../Controls/Validators/StatusValidator.qml | 2 +- .../tests/TestControls/tst_StatusInput.qml | 38 +++- .../Communities/controls/BannerPicker.qml | 17 +- .../Communities/controls/ColorPicker.qml | 5 +- .../Communities/controls/DescriptionInput.qml | 2 +- .../controls/EditCommunitySettingsForm.qml | 33 +++- .../controls/IntroMessageInput.qml | 3 +- .../Communities/controls/LogoPicker.qml | 15 +- .../Communities/controls/NameInput.qml | 3 +- .../Communities/controls/Options.qml | 12 +- .../Communities/controls/TagsPicker.qml | 31 ++- .../Communities/panels/BannerPanel.qml | 9 +- .../Communities/panels/ColorPanel.qml | 19 +- .../panels/OverviewSettingsPanel.qml | 4 +- .../Communities/panels/TagsPanel.qml | 38 ++-- .../popups/CreateCommunityPopup.qml | 33 +++- .../shared/panels/EditCroppedImagePanel.qml | 10 + .../shared/panels/NoImageUploadedPanel.qml | 2 +- 25 files changed, 399 insertions(+), 102 deletions(-) create mode 100644 storybook/pages/CreateCommunityPopupPage.qml diff --git a/storybook/pages/CreateCommunityPopupPage.qml b/storybook/pages/CreateCommunityPopupPage.qml new file mode 100644 index 0000000000..b67155415b --- /dev/null +++ b/storybook/pages/CreateCommunityPopupPage.qml @@ -0,0 +1,181 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 + +import StatusQ 0.1 + +import Storybook 1.0 +import Models 1.0 + +import mainui 1.0 + +import shared.stores 1.0 as SharedStores + +import AppLayouts.Communities.popups 1.0 +import AppLayouts.Communities.controls 1.0 +import AppLayouts.Communities.stores 1.0 as CommunitiesStores + +import AppLayouts.stores 1.0 as AppLayoutStores + +SplitView { + id: root + + Logs { id: logs } + + orientation: Qt.Vertical + + property var dialog + + function createAndOpenDialog() { + dialog = dlgComponent.createObject(popupBg) + dialog.open() + } + + Component.onCompleted: createAndOpenDialog() + + Item { + SplitView.fillWidth: true + SplitView.fillHeight: true + + PopupBackground { + id: popupBg + anchors.fill: parent + + Button { + anchors.centerIn: parent + text: "Reopen" + + onClicked: createAndOpenDialog() + } + } + + Popups { + popupParent: root + sharedRootStore: SharedStores.RootStore {} + rootStore: AppLayoutStores.RootStore {} + communityTokensStore: SharedStores.CommunityTokensStore {} + isDevBuild: ctrlIsDevBuild.checked + } + + Component { + id: dlgComponent + CreateCommunityPopup { + id: dialog + anchors.centerIn: parent + destroyOnClose: true + modal: false + isDiscordImport: isDiscordCheckBox.checked + isDevBuild: ctrlIsDevBuild.checked + + QtObject { + id: localAppSettings + readonly property bool testEnvironment: dialog.isDevBuild + } + + store: CommunitiesStores.CommunitiesStore { + readonly property string communityTags: ModelsData.communityTags + + function createCommunity() { + logs.logEvent("createCommunity") + } + + property string discordImportChannelName + readonly property bool discordImportInProgress: false + readonly property bool discordDataExtractionInProgress: false + readonly property int discordImportErrorsCount: 0 + readonly property int discordImportWarningsCount: 0 + readonly property int discordOldestMessageTimestamp: 0 + + property var discordFileList: ListModel { + readonly property int selectedCount: count + property bool selectedFilesValid + } + + property var discordCategoriesModel: ListModel {} + + property var discordChannelsModel: ListModel { + property bool hasSelectedItems + readonly property int count: 32 // hide the parsing/loading spinner + } + + function setFileListItems(filePaths) { + for (const filePath of filePaths) { + discordFileList.append({"filePath": filePath, errorMessage: ""}) + } + } + + function removeFileListItem(path) { + for (let i = 0; i < discordFileList.count; i++) { + const item = discordFileList.get(i) + if (item.filePath === path) + discordFileList.remove(i) + } + } + + function clearFileList() { + discordFileList.clear() + discordFileList.selectedFilesValid = false + } + + function clearDiscordCategoriesAndChannels() { + discordCategoriesModel.clear() + discordChannelsModel.clear() + discordChannelsModel.hasSelectedItems = false + } + + function requestExtractChannelsAndCategories() { + discordFileList.selectedFilesValid = true + } + + function toggleOneDiscordChannel(id) { + logs.logEvent("toggleOneDiscordChannel", ["id"], arguments) + } + + function resetDiscordImport() { + logs.logEvent("resetDiscordImport") + } + + function requestCancelDiscordChannelImport(id) { + logs.logEvent("requestCancelDiscordChannelImport", ["id"], arguments) + } + + function requestImportDiscordChannel(args, timestamp) { + logs.logEvent("requestImportDiscordChannel", ["args", "timestamp"], arguments) + } + } + } + } + } + + LogsAndControlsPanel { + SplitView.minimumHeight: 100 + SplitView.preferredHeight: 200 + + logsView.logText: logs.logText + + ColumnLayout { + Switch { + id: isDiscordCheckBox + text: "Discord import" + onToggled: { + if (!!dialog && dialog.opened) + dialog.close() + } + } + Switch { + id: ctrlIsDevBuild + text: "Dev build" + onToggled: { + if (!!dialog && dialog.opened) + dialog.close() + } + } + } + } +} + +// category: Popups + +// https://www.figma.com/design/qHfFm7C9LwtXpfdbxssCK3/Kuba%E2%8E%9CDesktop---Communities?node-id=52741-266926&node-type=frame&t=PkDxeWSXoiZbIQXv-0 +// https://www.figma.com/design/qHfFm7C9LwtXpfdbxssCK3/Kuba%E2%8E%9CDesktop---Communities?node-id=2636-359221&node-type=frame&t=PkDxeWSXoiZbIQXv-0 +// https://www.figma.com/design/qHfFm7C9LwtXpfdbxssCK3/Kuba%E2%8E%9CDesktop---Communities?node-id=52741-267155&node-type=frame&t=PkDxeWSXoiZbIQXv-0 diff --git a/ui/StatusQ/src/StatusQ/Components/StatusCommunityTags.qml b/ui/StatusQ/src/StatusQ/Components/StatusCommunityTags.qml index 3c3c2c05b2..9ee2cb9748 100644 --- a/ui/StatusQ/src/StatusQ/Components/StatusCommunityTags.qml +++ b/ui/StatusQ/src/StatusQ/Components/StatusCommunityTags.qml @@ -1,4 +1,4 @@ -import QtQuick 2.14 +import QtQuick 2.15 import StatusQ 0.1 import StatusQ.Core 0.1 @@ -76,7 +76,7 @@ Item { delegate: StatusCommunityTag { emoji: model.emoji name: model.name - removable: root.mode === StatusCommunityTags.ShowSelectedOnly && root.active + removable: root.mode === StatusCommunityTags.ShowSelectedOnly && root.active && repeater.count > 1 highlighted: root.mode === StatusCommunityTags.Highlight && model.selected onClicked: root.clicked(model) diff --git a/ui/StatusQ/src/StatusQ/Controls/StatusBaseInput.qml b/ui/StatusQ/src/StatusQ/Controls/StatusBaseInput.qml index 66b51fbd1a..51841be81d 100644 --- a/ui/StatusQ/src/StatusQ/Controls/StatusBaseInput.qml +++ b/ui/StatusQ/src/StatusQ/Controls/StatusBaseInput.qml @@ -419,7 +419,7 @@ Item { let utf8Length = Utils.encodeUtf8(text).length if (utf8Length > root.maximumLength) { var cursor = cursorPosition - text = previousText + text = text.slice(0, maximumLength) if (cursor > edit.length) { cursorPosition = edit.length } else { diff --git a/ui/StatusQ/src/StatusQ/Controls/StatusColorSelectorGrid.qml b/ui/StatusQ/src/StatusQ/Controls/StatusColorSelectorGrid.qml index 57cde15e4c..2a420113a4 100644 --- a/ui/StatusQ/src/StatusQ/Controls/StatusColorSelectorGrid.qml +++ b/ui/StatusQ/src/StatusQ/Controls/StatusColorSelectorGrid.qml @@ -1,10 +1,8 @@ -import QtQuick 2.13 +import QtQuick 2.15 import StatusQ.Core 0.1 import StatusQ.Core.Theme 0.1 -import "./" - Column { id: root @@ -27,7 +25,6 @@ Column { id: title width: parent.width verticalAlignment: Text.AlignVCenter - font.pixelSize: 15 color: Theme.palette.baseColor1 } diff --git a/ui/StatusQ/src/StatusQ/Controls/StatusInput.qml b/ui/StatusQ/src/StatusQ/Controls/StatusInput.qml index 469b1f3e81..6dddd5c015 100644 --- a/ui/StatusQ/src/StatusQ/Controls/StatusInput.qml +++ b/ui/StatusQ/src/StatusQ/Controls/StatusInput.qml @@ -97,8 +97,8 @@ Item { */ property alias errorMessageCmp: errorMessage /*! - \qmlproperty string StatusInput::label - This property sets the label text. + \qmlproperty int StatusInput::labelPadding + This property sets the padding of the label text. */ property int labelPadding: 8 /*! @@ -118,7 +118,7 @@ Item { property int charLimit: 0 /*! \qmlproperty string StatusInput::charLimitLabel - This property overrides the char default chart limit text format. + This property overrides the default char limit text. */ property string charLimitLabel: "" /*! @@ -157,12 +157,12 @@ Item { */ property real maximumHeight: 44 /*! - \qmlproperty list StatusBaseInput::validators + \qmlproperty list StatusBaseInput::validators This property sets the list of validators to be considered. */ property list validators /*! - \qmlproperty list StatusBaseInput::validators + \qmlproperty list StatusBaseInput::asyncValidators This property sets the list of async validators to be considered. */ property list asyncValidators @@ -250,7 +250,7 @@ Item { */ property var errors: ({}) /*! - \qmlproperty var StatusBaseInput::errors + \qmlproperty var StatusBaseInput::asyncErrors This property holds the validation async errors. */ property var asyncErrors: ({}) @@ -343,8 +343,9 @@ Item { root.validatedValue = root.text } } + /*! - \qmlmethod + \qmlmethod updateAsyncValidity(validatorName, value, result) This function updates the text input async validation. */ function updateAsyncValidity(validatorName, value, result) { @@ -435,7 +436,7 @@ Item { id: charLimitLabelItem Layout.alignment: Qt.AlignVCenter height: visible ? contentHeight : 0 - visible: root.charLimit > 0 + visible: root.charLimit > 0 || !!root.charLimitLabel text: root.charLimitLabel ? root.charLimitLabel : "%1 / %2".arg(Utils.encodeUtf8(statusBaseInput.text).length).arg(root.charLimit) font.pixelSize: 12 color: statusBaseInput.enabled ? Theme.palette.baseColor1 : Theme.palette.directColor6 diff --git a/ui/StatusQ/src/StatusQ/Controls/StatusPickerButton.qml b/ui/StatusQ/src/StatusQ/Controls/StatusPickerButton.qml index 53d8960ba7..ac82d17adb 100644 --- a/ui/StatusQ/src/StatusQ/Controls/StatusPickerButton.qml +++ b/ui/StatusQ/src/StatusQ/Controls/StatusPickerButton.qml @@ -29,6 +29,8 @@ StatusButton { Down } + property bool isError + horizontalPadding: 16 verticalPadding: 3 spacing: 4 @@ -37,6 +39,8 @@ StatusButton { background: Rectangle { radius: 8 color: root.bgColor + border.color: Theme.palette.dangerColor1 + border.width: root.isError ? 1 : 0 } opacity: !root.interactive || !root.enabled ? 0.5 : 1 contentItem: RowLayout { @@ -62,16 +66,11 @@ StatusButton { elide: Text.ElideRight } StatusIcon { - icon: "tiny/chevron-right" + icon: "next" visible: root.type === StatusPickerButton.PickerType.Next color: !Qt.colorEqual(root.contentColor, Theme.palette.baseColor1) ? root.contentColor : Theme.palette.directColor1 width: root.icon.width height: root.icon.height } } - - HoverHandler { - enabled: root.enabled - cursorShape: Qt.PointingHandCursor - } } diff --git a/ui/StatusQ/src/StatusQ/Controls/Validators/StatusMinLengthValidator.qml b/ui/StatusQ/src/StatusQ/Controls/Validators/StatusMinLengthValidator.qml index e36ce316f9..453a2eec7b 100644 --- a/ui/StatusQ/src/StatusQ/Controls/Validators/StatusMinLengthValidator.qml +++ b/ui/StatusQ/src/StatusQ/Controls/Validators/StatusMinLengthValidator.qml @@ -8,8 +8,8 @@ StatusValidator { errorMessage: { minLength === 1 ? - "Please enter a value" : - `The value must be at least ${minLength} characters.` + qsTr("Please enter a value") : + qsTr("The value must be at least %n character(s).", "", minLength) } validate: function (value) { @@ -19,4 +19,3 @@ StatusValidator { } } } - diff --git a/ui/StatusQ/src/StatusQ/Controls/Validators/StatusValidator.qml b/ui/StatusQ/src/StatusQ/Controls/Validators/StatusValidator.qml index fc8dc1c5ce..7d373d9d1a 100644 --- a/ui/StatusQ/src/StatusQ/Controls/Validators/StatusValidator.qml +++ b/ui/StatusQ/src/StatusQ/Controls/Validators/StatusValidator.qml @@ -1,4 +1,4 @@ -import QtQuick 2.14 +import QtQuick 2.15 QtObject { property string name: "" diff --git a/ui/StatusQ/tests/TestControls/tst_StatusInput.qml b/ui/StatusQ/tests/TestControls/tst_StatusInput.qml index 2c30ec7eb2..ac0e48af62 100644 --- a/ui/StatusQ/tests/TestControls/tst_StatusInput.qml +++ b/ui/StatusQ/tests/TestControls/tst_StatusInput.qml @@ -1,6 +1,7 @@ -import QtQuick 2.0 -import QtTest 1.0 +import QtQuick 2.15 +import QtTest 1.15 +import StatusQ 0.1 import StatusQ.Controls 0.1 import StatusQ.Controls.Validators 0.1 @@ -24,7 +25,7 @@ Item { property StatusInput testControl: null - name: "RegexValidationTest" + name: "StatusInput-RegexValidationTest" when: windowShown @@ -110,7 +111,7 @@ Item { property StatusInput testControl: null - name: "BindedValuesTest" + name: "StatusInput-BindedValuesTest" when: windowShown property QtObject dataObject: QtObject { @@ -167,6 +168,35 @@ Item { verify(qtOuput.qtOuput().length === 0, `No output expected. Found:\n"${qtOuput.qtOuput()}"\n`) } + + // regression test for https://github.com/status-im/status-desktop/issues/16479 + function test_paste_text() { + verify(!!testControl) + + const textToPaste = "1234567890abcdef" // 16 chars + ClipboardUtils.setText(textToPaste) + + // clear + testControl.input.edit.clear() + testControl.charLimit = 0 + + keySequence(StandardKey.Paste) + + // verify we can paste the full text + compare(testControl.input.edit.length, textToPaste.length) + compare(testControl.input.edit.text, textToPaste) + compare(testControl.input.dirty, true) + + // clear again, and set a lower limit + testControl.input.edit.clear() + testControl.charLimit = 10 + + keySequence(StandardKey.Paste) + + // verify we can paste (some) text and it gets truncated to the charLimit + compare(testControl.input.edit.length, testControl.charLimit) + compare(testControl.input.edit.text, textToPaste.slice(0, testControl.charLimit)) + } } MonitorQtOutput { diff --git a/ui/app/AppLayouts/Communities/controls/BannerPicker.qml b/ui/app/AppLayouts/Communities/controls/BannerPicker.qml index 55264ae0a8..92b7620746 100644 --- a/ui/app/AppLayouts/Communities/controls/BannerPicker.qml +++ b/ui/app/AppLayouts/Communities/controls/BannerPicker.qml @@ -27,12 +27,16 @@ Item { implicitHeight: layout.childrenRect.height + function validate() { + editor.isError = !hasSelectedImage + } + ColumnLayout { id: layout anchors.fill: parent - spacing: 19 + spacing: 16 StatusBaseText { text: qsTr("Community banner") @@ -67,6 +71,17 @@ Item { additionalTextPixelSize: Theme.tertiaryTextFontSize } } + + StatusBaseText { + Layout.fillWidth: true + Layout.topMargin: -layout.spacing/2 + visible: editor.isError + text: qsTr("Upload a community banner") + font.pixelSize: Theme.tertiaryTextFontSize + color: Theme.palette.dangerColor1 + wrapMode: Text.WordWrap + horizontalAlignment: Text.AlignHCenter + } } } diff --git a/ui/app/AppLayouts/Communities/controls/ColorPicker.qml b/ui/app/AppLayouts/Communities/controls/ColorPicker.qml index 86593dc600..17631da1d4 100644 --- a/ui/app/AppLayouts/Communities/controls/ColorPicker.qml +++ b/ui/app/AppLayouts/Communities/controls/ColorPicker.qml @@ -19,8 +19,6 @@ ColumnLayout { spacing: 8 - implicitHeight: childrenRect.height - StatusBaseText { Layout.fillWidth: true text: root.title @@ -35,6 +33,9 @@ ColumnLayout { bgColor: root.color contentColor: Theme.palette.white text: root.color.toString() + font.weight: Font.Normal + icon.width: 24 + icon.height: 24 onClicked: root.pick() onTextChanged: { diff --git a/ui/app/AppLayouts/Communities/controls/DescriptionInput.qml b/ui/app/AppLayouts/Communities/controls/DescriptionInput.qml index 73fd86b0bb..8811f647ee 100644 --- a/ui/app/AppLayouts/Communities/controls/DescriptionInput.qml +++ b/ui/app/AppLayouts/Communities/controls/DescriptionInput.qml @@ -1,4 +1,4 @@ -import QtQuick 2.14 +import QtQuick 2.15 import utils 1.0 diff --git a/ui/app/AppLayouts/Communities/controls/EditCommunitySettingsForm.qml b/ui/app/AppLayouts/Communities/controls/EditCommunitySettingsForm.qml index cb14a00a49..d1bf4cec46 100644 --- a/ui/app/AppLayouts/Communities/controls/EditCommunitySettingsForm.qml +++ b/ui/app/AppLayouts/Communities/controls/EditCommunitySettingsForm.qml @@ -21,6 +21,7 @@ Control { readonly property alias isDescriptionDirty: descriptionTextInput.input.dirty readonly property alias isLogoSelected: logoPicker.hasSelectedImage readonly property alias isBannerSelected: bannerPicker.hasSelectedImage + readonly property alias areTagsSelected: tagsPicker.hasSelectedTags property alias name: nameInput.text property alias description: descriptionTextInput.text @@ -38,31 +39,50 @@ Control { implicitWidth: 608 - contentItem: ColumnLayout { + function validate(isDevBuild = false) { + if (!nameInput.validate(true)) + nameInput.input.dirty = true + if (!descriptionTextInput.validate(true)) + descriptionTextInput.input.dirty = true + if (!isDevBuild) { + logoPicker.validate() + bannerPicker.validate() + tagsPicker.validate() + } + + return nameInput.valid && descriptionTextInput.valid && + (isDevBuild ? true : logoPicker.hasSelectedImage && bannerPicker.hasSelectedImage && tagsPicker.hasSelectedTags) + } + + contentItem: ColumnLayout { spacing: 16 NameInput { id: nameInput input.edit.objectName: "communityNameInput" Layout.fillWidth: true - Component.onCompleted: nameInput.input.forceActiveFocus(Qt.MouseFocusReason) + input.tabNavItem: descriptionTextInput.input.edit + Component.onCompleted: nameInput.input.forceActiveFocus() } DescriptionInput { id: descriptionTextInput input.edit.objectName: "communityDescriptionInput" + input.tabNavItem: nameInput.input.edit Layout.fillWidth: true } LogoPicker { id: logoPicker objectName: "communityLogoPicker" + onHasSelectedImageChanged: validate() Layout.fillWidth: true } BannerPicker { id: bannerPicker objectName: "communityBannerPicker" + onHasSelectedImageChanged: validate() Layout.fillWidth: true Layout.topMargin: -8 //Closer by design } @@ -109,12 +129,13 @@ Control { width: 640 replaceItem: TagsPanel { Component.onCompleted: { - tags = tagsPicker.tags; - selectedTags = tagsPicker.selectedTags; + tags = tagsPicker.tags + selectedTags = tagsPicker.selectedTags } onAccepted: { - tagsPicker.selectedTags = selectedTags; - close(); + tagsPicker.selectedTags = selectedTags + tagsPicker.validate() + close() } } onClosed: destroy() diff --git a/ui/app/AppLayouts/Communities/controls/IntroMessageInput.qml b/ui/app/AppLayouts/Communities/controls/IntroMessageInput.qml index 6a77aa2beb..07ef2572af 100644 --- a/ui/app/AppLayouts/Communities/controls/IntroMessageInput.qml +++ b/ui/app/AppLayouts/Communities/controls/IntroMessageInput.qml @@ -1,9 +1,8 @@ -import QtQuick 2.14 +import QtQuick 2.15 import utils 1.0 import StatusQ.Core 0.1 -import StatusQ.Core.Theme 0.1 import StatusQ.Controls 0.1 import StatusQ.Controls.Validators 0.1 diff --git a/ui/app/AppLayouts/Communities/controls/LogoPicker.qml b/ui/app/AppLayouts/Communities/controls/LogoPicker.qml index 22b0df708f..5e7210abef 100644 --- a/ui/app/AppLayouts/Communities/controls/LogoPicker.qml +++ b/ui/app/AppLayouts/Communities/controls/LogoPicker.qml @@ -27,6 +27,10 @@ Item { implicitHeight: layout.childrenRect.height + function validate() { + editor.isError = !hasSelectedImage + } + ColumnLayout { id: layout @@ -37,7 +41,6 @@ Item { id: label Layout.fillWidth: true text: qsTr("Community logo") - font.pixelSize: 15 color: Theme.palette.directColor1 horizontalAlignment: Qt.AlignLeft } @@ -59,6 +62,16 @@ Item { visible: !editor.userSelectedImage && !root.imageData } } + + StatusBaseText { + Layout.fillWidth: true + visible: editor.isError + text: qsTr("Upload a community logo") + font.pixelSize: Theme.tertiaryTextFontSize + color: Theme.palette.dangerColor1 + wrapMode: Text.WordWrap + horizontalAlignment: Text.AlignHCenter + } } } diff --git a/ui/app/AppLayouts/Communities/controls/NameInput.qml b/ui/app/AppLayouts/Communities/controls/NameInput.qml index 84ec516620..2e02e75a99 100644 --- a/ui/app/AppLayouts/Communities/controls/NameInput.qml +++ b/ui/app/AppLayouts/Communities/controls/NameInput.qml @@ -1,9 +1,8 @@ -import QtQuick 2.14 +import QtQuick 2.15 import utils 1.0 import StatusQ.Core 0.1 -import StatusQ.Core.Theme 0.1 import StatusQ.Controls 0.1 import StatusQ.Controls.Validators 0.1 diff --git a/ui/app/AppLayouts/Communities/controls/Options.qml b/ui/app/AppLayouts/Communities/controls/Options.qml index a4c056d99c..8df6cb2c5c 100644 --- a/ui/app/AppLayouts/Communities/controls/Options.qml +++ b/ui/app/AppLayouts/Communities/controls/Options.qml @@ -31,7 +31,6 @@ ColumnLayout { StatusCheckBox { id: archiveSupportToggle width: (parent.width-12) - checked: false leftSide: false padding: 0 anchors.verticalCenter: parent.verticalCenter @@ -39,11 +38,7 @@ ColumnLayout { StatusToolTip { text: qsTr('For this Community Setting to work, you also need to activate "Archive Protocol Enabled" in Advanced Settings') - visible: hoverHandler.hovered - } - HoverHandler { - id: hoverHandler - enabled: true + visible: parent.hovered } } } @@ -65,7 +60,7 @@ ColumnLayout { ColumnLayout { Layout.preferredWidth: parent.width Layout.topMargin: 22 - spacing: 0 + spacing: 4 StatusCheckBox { id: requestToJoinToggle Layout.fillWidth: true @@ -78,9 +73,8 @@ ColumnLayout { } StatusBaseText { - id: warningText Layout.fillWidth: true - Layout.rightMargin: 12 + Layout.rightMargin: 64 visible: requestToJoinToggle.checked wrapMode: Text.WordWrap text: qsTr("Warning: Only token gated communities (or token gated channels inside non-token gated community) are encrypted") diff --git a/ui/app/AppLayouts/Communities/controls/TagsPicker.qml b/ui/app/AppLayouts/Communities/controls/TagsPicker.qml index 0dc4404870..915a9d5f72 100644 --- a/ui/app/AppLayouts/Communities/controls/TagsPicker.qml +++ b/ui/app/AppLayouts/Communities/controls/TagsPicker.qml @@ -1,5 +1,5 @@ -import QtQuick 2.14 -import QtQuick.Layouts 1.14 +import QtQuick 2.15 +import QtQuick.Layouts 1.15 import utils 1.0 @@ -15,14 +15,19 @@ ColumnLayout { property string tags property string selectedTags + readonly property bool hasSelectedTags: localAppSettings.testEnvironment || selectedTags !== "" + signal pick() - implicitHeight: childrenRect.height - spacing: 14 + spacing: 8 onSelectedTagsChanged: d.handleSelectedTags() Component.onCompleted: d.handleSelectedTags() + function validate() { + pickerButton.isError = !hasSelectedTags + } + QtObject { id: d @@ -33,7 +38,7 @@ ColumnLayout { d.tagsModel.clear(); for (const key of Object.keys(obj)) { - if (array.indexOf(key) != -1) { + if (array.indexOf(key) !== -1) { d.tagsModel.append({ name: key, emoji: obj[key], selected: false }); } } @@ -42,15 +47,19 @@ ColumnLayout { StatusBaseText { text: qsTr("Tags") - font.pixelSize: 15 color: Theme.palette.directColor1 } StatusPickerButton { + id: pickerButton bgColor: d.tagsModel.count === 0 ? Theme.palette.baseColor2 : "transparent" text: d.tagsModel.count === 0 ? qsTr("Choose tags describing the community") : "" onClicked: root.pick() + font.weight: Font.Normal + icon.width: 24 + icon.height: 24 Layout.fillWidth: true + Layout.preferredHeight: 44 StatusCommunityTags { anchors.verticalCenter: parent.verticalCenter @@ -60,4 +69,14 @@ ColumnLayout { contentWidth: width } } + + StatusBaseText { + Layout.fillWidth: true + visible: pickerButton.isError + text: qsTr("Add at least 1 tag") + font.pixelSize: Theme.tertiaryTextFontSize + color: Theme.palette.dangerColor1 + wrapMode: Text.WordWrap + horizontalAlignment: Text.AlignRight + } } diff --git a/ui/app/AppLayouts/Communities/panels/BannerPanel.qml b/ui/app/AppLayouts/Communities/panels/BannerPanel.qml index 80e47c8a80..44da4281bc 100644 --- a/ui/app/AppLayouts/Communities/panels/BannerPanel.qml +++ b/ui/app/AppLayouts/Communities/panels/BannerPanel.qml @@ -1,15 +1,11 @@ -import QtQuick 2.13 -import QtQuick.Controls 2.13 -import QtGraphicalEffects 1.13 +import QtQuick 2.15 +import QtQuick.Controls 2.15 import StatusQ.Core 0.1 import StatusQ.Components 0.1 import StatusQ.Core.Theme 0.1 import StatusQ.Controls 0.1 as StatusQControls -import shared.panels 1.0 -import shared.status 1.0 - import utils 1.0 Rectangle { @@ -61,7 +57,6 @@ Rectangle { anchors.top: parent.top anchors.topMargin: 48 horizontalAlignment: Text.AlignHCenter - font.pixelSize: 15 color: Theme.palette.directColor1 wrapMode: Text.WordWrap anchors.right: parent.right diff --git a/ui/app/AppLayouts/Communities/panels/ColorPanel.qml b/ui/app/AppLayouts/Communities/panels/ColorPanel.qml index e1be177b9d..cbb7c57f80 100644 --- a/ui/app/AppLayouts/Communities/panels/ColorPanel.qml +++ b/ui/app/AppLayouts/Communities/panels/ColorPanel.qml @@ -1,6 +1,6 @@ -import QtQuick 2.14 -import QtQuick.Controls 2.14 -import QtQuick.Layouts 1.12 +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 import StatusQ.Core 0.1 import StatusQ.Core.Theme 0.1 @@ -69,7 +69,7 @@ StatusScrollView { // TODO: editingFinished() signal instead of this crutch property bool locked: false - implicitWidth: 256 + Layout.preferredWidth: 256 validators: [ StatusRegularExpressionValidator { regularExpression: /^#(?:[0-9a-fA-F]{3}){1,2}$/ @@ -91,22 +91,18 @@ StatusScrollView { StatusBaseText { text: qsTr("Preview") - font.pixelSize: 15 } Rectangle { - implicitHeight: 48 + Layout.fillWidth: true + Layout.preferredHeight: 44 radius: 10 color: root.color - Layout.fillWidth: true StatusBaseText { - id: preview - x: 16 - y: 16 + anchors.centerIn: parent text: qsTr("White text should be legible on top of this colour") color: Theme.palette.white - font.pixelSize: 15 } } @@ -114,7 +110,6 @@ StatusScrollView { id: colorSelectionGrid titleText: qsTr("Standard colours") title.color: Theme.palette.directColor1 - title.font.pixelSize: 15 columns: 8 model: Theme.palette.communityColorsArray selectedColorIndex: -1 diff --git a/ui/app/AppLayouts/Communities/panels/OverviewSettingsPanel.qml b/ui/app/AppLayouts/Communities/panels/OverviewSettingsPanel.qml index 176262749e..45acc5be93 100644 --- a/ui/app/AppLayouts/Communities/panels/OverviewSettingsPanel.qml +++ b/ui/app/AppLayouts/Communities/panels/OverviewSettingsPanel.qml @@ -173,7 +173,9 @@ StackLayout { Connections { target: root - onCommunityIdChanged: reset() + function onCommunityIdChanged() { + reset() + } } } diff --git a/ui/app/AppLayouts/Communities/panels/TagsPanel.qml b/ui/app/AppLayouts/Communities/panels/TagsPanel.qml index 47350c76af..612ef00ba3 100644 --- a/ui/app/AppLayouts/Communities/panels/TagsPanel.qml +++ b/ui/app/AppLayouts/Communities/panels/TagsPanel.qml @@ -1,6 +1,6 @@ -import QtQuick 2.14 -import QtQuick.Controls 2.14 -import QtQuick.Layouts 1.14 +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 import StatusQ.Core 0.1 import StatusQ.Core.Theme 0.1 @@ -23,6 +23,7 @@ StatusScrollView { property var rightButtons: StatusButton { objectName: "confirmCommunityTagsButton" text: qsTr("Confirm Community Tags") + enabled: d.countSelectedTags > 0 onClicked: { var selectedTags = []; for (let i = 0; i < d.tagsModel.count; ++i) { @@ -39,12 +40,12 @@ StatusScrollView { function updateSelectedTags() { var array = selectedTags.length ? JSON.parse(selectedTags) : []; - d.cntSelectedTags = 0; + d.countSelectedTags = 0; for (let i = 0; i < d.tagsModel.count; ++i) { let item = d.tagsModel.get(i); - if (array.indexOf(item.name) != -1) { + if (array.indexOf(item.name) !== -1) { item.selected = true; - d.cntSelectedTags++; + d.countSelectedTags++; } else { item.selected = false; } @@ -55,7 +56,7 @@ StatusScrollView { onTagsChanged: { var obj = JSON.parse(tags); - d.cntSelectedTags = 0; + d.countSelectedTags = 0; d.tagsModel.clear(); for (const key of Object.keys(obj)) { d.tagsModel.append({ name: key, emoji: obj[key], selected: false }); @@ -70,7 +71,7 @@ StatusScrollView { QtObject { id: d - property int cntSelectedTags: 0 + property int countSelectedTags: 0 property ListModel tagsModel: ListModel {} } @@ -83,7 +84,6 @@ StatusScrollView { id: tagsFilter label: qsTr("Select tags that will fit your Community") labelPadding: Style.current.bigPadding - font.pixelSize: 15 input.asset.name: "search" placeholderText: qsTr("Search tags") Layout.fillWidth: true @@ -96,9 +96,9 @@ StatusScrollView { StatusCommunityTags { filterString: tagsFilter.text model: d.tagsModel - enabled: d.cntSelectedTags < maxSelectedTags + enabled: d.countSelectedTags < maxSelectedTags onClicked: { - d.cntSelectedTags++; + d.countSelectedTags++; item.selected = true; } Layout.fillWidth: true @@ -112,12 +112,11 @@ StatusScrollView { RowLayout { StatusBaseText { text: qsTr("Selected tags") - font.pixelSize: 15 Layout.fillWidth: true } StatusBaseText { - text: qsTr("%1 / %2").arg(d.cntSelectedTags).arg(maxSelectedTags) + text: qsTr("%1 / %2").arg(d.countSelectedTags).arg(maxSelectedTags) color: Theme.palette.baseColor1 font.pixelSize: 13 } @@ -127,10 +126,21 @@ StatusScrollView { model: d.tagsModel mode: StatusCommunityTags.ShowSelectedOnly onClicked: { - d.cntSelectedTags--; + d.countSelectedTags--; item.selected = false; } Layout.fillWidth: true + Layout.bottomMargin: Style.current.padding + } + + StatusBaseText { + Layout.fillWidth: true + Layout.bottomMargin: Style.current.padding + text: qsTr("No tags selected yet") + color: Theme.palette.baseColor1 + visible: d.countSelectedTags === 0 + font.pixelSize: 13 + horizontalAlignment: Qt.AlignHCenter } } } diff --git a/ui/app/AppLayouts/Communities/popups/CreateCommunityPopup.qml b/ui/app/AppLayouts/Communities/popups/CreateCommunityPopup.qml index 381b746c8d..4947b8fb1b 100644 --- a/ui/app/AppLayouts/Communities/popups/CreateCommunityPopup.qml +++ b/ui/app/AppLayouts/Communities/popups/CreateCommunityPopup.qml @@ -1,6 +1,6 @@ -import QtQuick 2.14 -import QtQuick.Controls 2.14 -import QtQuick.Layouts 1.14 +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 import QtQuick.Dialogs 1.3 import utils 1.0 @@ -30,6 +30,8 @@ StatusStackModal { qsTr("Create New Community") width: 640 + closePolicy: Popup.NoAutoClose // explicit [x] click needed, or via the `close()` method + nextButton: StatusButton { objectName: "createCommunityNextBtn" font.weight: Font.Medium @@ -55,8 +57,6 @@ StatusStackModal { if (typeof (nextAction) == "function") { return nextAction() } - if (!root.isDiscordImport) - d.createCommunity() } } @@ -363,7 +363,12 @@ StatusStackModal { StatusScrollView { id: generalView contentWidth: availableWidth - readonly property bool canGoNext: generalViewLayout.isNameValid && generalViewLayout.isDescriptionValid && (root.isDevBuild || (generalViewLayout.isLogoSelected && generalViewLayout.isBannerSelected)) + + readonly property var nextAction: () => { + if (generalViewLayout.validate(root.isDevBuild)) { + root.currentIndex++ + } + } padding: 0 clip: false @@ -389,8 +394,20 @@ StatusStackModal { ColumnLayout { id: introOutroMessageView - spacing: 11 - readonly property bool canGoNext: introMessageInput.valid && outroMessageInput.valid + spacing: 16 + + readonly property var nextAction: () => { + if (!introMessageInput.validate(true)) + introMessageInput.input.dirty = true + if (!outroMessageInput.validate(true)) + outroMessageInput.input.dirty = true + if (introMessageInput.valid && outroMessageInput.valid) { + if (root.isDiscordImport) + root.currentIndex++ + else + d.createCommunity() + } + } IntroMessageInput { id: introMessageInput diff --git a/ui/imports/shared/panels/EditCroppedImagePanel.qml b/ui/imports/shared/panels/EditCroppedImagePanel.qml index fa94083d24..463609225c 100644 --- a/ui/imports/shared/panels/EditCroppedImagePanel.qml +++ b/ui/imports/shared/panels/EditCroppedImagePanel.qml @@ -35,6 +35,7 @@ Item { property bool userSelectedImage: false readonly property bool nothingToShow: state === d.noImageState + property bool isError readonly property alias cropWorkflow : imageCropWorkflow @@ -119,6 +120,9 @@ Item { icon.name: "edit_pencil" + width: 40 + height: 40 + readonly property real rotationRadius: root.roundedImage ? parent.width/2 : imageCropEditor.radius transform: [ Translate { @@ -154,10 +158,16 @@ Item { radius: roundedImage ? Math.max(width, height)/2 : croppedPreview.radius color: Style.current.inputBackground + border.color: Theme.palette.dangerColor1 + border.width: root.isError ? 1 : 0 + StatusRoundButton { id: addButton icon.name: "add" + width: 40 + height: 40 + readonly property real rotationRadius: root.roundedImage ? parent.width/2 : imageCropEditor.radius transform: [ diff --git a/ui/imports/shared/panels/NoImageUploadedPanel.qml b/ui/imports/shared/panels/NoImageUploadedPanel.qml index f4b62cb121..e8cd9e6f44 100644 --- a/ui/imports/shared/panels/NoImageUploadedPanel.qml +++ b/ui/imports/shared/panels/NoImageUploadedPanel.qml @@ -10,7 +10,7 @@ import StatusQ.Core 0.1 import StatusQ.Core.Theme 0.1 /*! - /brief Image icon and ulopad text hints for banner and logo + /brief Image icon and upload text hints for banner and logo */ Control { id: root