feat(@community): add frontend for intro message

issue: #5746
This commit is contained in:
Patryk Osmaczko 2022-05-26 17:46:02 +02:00 committed by osmaczko
parent 6cda41e183
commit 51eefad5e5
22 changed files with 817 additions and 701 deletions

View File

@ -0,0 +1,68 @@
import QtQuick 2.14
import QtQuick.Layouts 1.14
import QtQuick.Controls 2.14
import QtQuick.Dialogs 1.3
import QtGraphicalEffects 1.13
import utils 1.0
import shared.panels 1.0
import shared.popups 1.0
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Layout 0.1
import StatusQ.Components 0.1
import StatusQ.Popups 0.1
import StatusQ.Controls 0.1
import StatusQ.Controls.Validators 0.1
Item {
id: root
property alias source: editor.source
property alias cropRect: editor.cropRect
property string imageData
implicitHeight: layout.implicitHeight
implicitWidth: layout.implicitWidth
ColumnLayout {
id: layout
anchors.fill: parent
spacing: 8
StatusBaseText {
text: qsTr("Community banner")
font.pixelSize: 15
color: Theme.palette.directColor1
}
EditCroppedImagePanel {
id: editor
Layout.preferredWidth: 475
Layout.preferredHeight: Layout.preferredWidth / aspectRatio
Layout.alignment: Qt.AlignHCenter
imageFileDialogTitle: qsTr("Choose an image for banner")
title: qsTr("Community banner")
acceptButtonText: qsTr("Make this my Community banner")
roundedImage: false
aspectRatio: 375/184
dataImage: root.imageData
NoImageUploadedPanel {
anchors.centerIn: parent
visible: !editor.userSelectedImage && !root.imageData
showARHint: true
}
}
}
}

View File

@ -0,0 +1,55 @@
import QtQuick 2.14
import QtQuick.Layouts 1.14
import QtQuick.Controls 2.14
import utils 1.0
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Popups 0.1
import StatusQ.Controls 0.1
ColumnLayout {
id: root
property color color: Theme.palette.primaryColor1
spacing: 8
StatusBaseText {
text: qsTr("Community colour")
font.pixelSize: 15
color: Theme.palette.directColor1
}
StatusPickerButton {
Layout.fillWidth: true
property string validationError: ""
bgColor: root.color
contentColor: Theme.palette.indirectColor1
text: root.color.toString()
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
}
}
}
}

View File

@ -0,0 +1,30 @@
import QtQuick 2.14
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
StatusInput {
id: root
leftPadding: 0
rightPadding: 0
label: qsTr("Description")
charLimit: 140
input.placeholderText: qsTr("What your community is about")
input.multiline: true
input.implicitHeight: 88
validators: [
StatusMinLengthValidator {
minLength: 1
errorMessage: Utils.getErrorMessage(root.errors,
qsTr("community description"))
}
]
validationMode: StatusInput.ValidationMode.Always
}

View File

@ -0,0 +1,34 @@
import QtQuick 2.14
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
StatusInput {
id: root
leftPadding: 0
rightPadding: 0
label: qsTr("Community introduction and rules")
charLimit: 1400
input.multiline: true
input.implicitHeight: 400
input.placeholder.text: qsTr("What new members will read before joining (eg. community rules, welcome message, etc.). Members will need to tick a check box agreeing to these rules before they are allowed to join your community.")
input.placeholder.wrapMode: Text.WordWrap
input.verticalAlignment: TextEdit.AlignTop
validators: [
StatusMinLengthValidator {
minLength: 1
errorMessage: Utils.getErrorMessage(root.errors,
qsTr("community intro message"))
}
]
validationMode: StatusInput.ValidationMode.Always
}

View File

@ -0,0 +1,63 @@
import QtQuick 2.14
import QtQuick.Layouts 1.14
import QtQuick.Controls 2.14
import QtQuick.Dialogs 1.3
import QtGraphicalEffects 1.13
import utils 1.0
import shared.panels 1.0
import shared.popups 1.0
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Layout 0.1
import StatusQ.Components 0.1
import StatusQ.Popups 0.1
import StatusQ.Controls 0.1
import StatusQ.Controls.Validators 0.1
Item {
id: root
property alias source: editor.source
property alias cropRect: editor.cropRect
property string imageData
implicitWidth: layout.implicitWidth
implicitHeight: layout.implicitHeight
ColumnLayout {
id: layout
anchors.fill: parent
spacing: 8
StatusBaseText {
text: qsTr("Community logo")
font.pixelSize: 15
color: Theme.palette.directColor1
}
EditCroppedImagePanel {
id: editor
Layout.preferredWidth: 128
Layout.preferredHeight: Layout.preferredWidth / aspectRatio
Layout.alignment: Qt.AlignHCenter
imageFileDialogTitle: qsTr("Choose an image as logo")
title: qsTr("Community logo")
acceptButtonText: qsTr("Make this my Community logo")
dataImage: root.imageData
NoImageUploadedPanel {
anchors.centerIn: parent
visible: !editor.userSelectedImage && !root.imageData
}
}
}
}

View File

@ -0,0 +1,26 @@
import QtQuick 2.14
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
StatusInput {
id: root
leftPadding: 0
rightPadding: 0
label: qsTr("Community name")
charLimit: 30
input.placeholderText: qsTr("A catchy name")
validators: [
StatusMinLengthValidator {
minLength: 1
errorMessage: Utils.getErrorMessage(root.errors,
qsTr("community name"))
}
]
validationMode: StatusInput.ValidationMode.Always
}

View File

@ -0,0 +1,77 @@
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
ColumnLayout {
id: root
property alias archiveSupportOptionVisible: archiveSupport.visible
property alias archiveSupportEnabled: archiveSupportToggle.checked
property alias requestToJoinEnabled: requestToJoinToggle.checked
property alias pinMessagesEnabled: pinMessagesToggle.checked
spacing: 0
QtObject {
id: d
readonly property real optionHeight: 64
}
RowLayout {
id: archiveSupport
Layout.fillWidth: true
StatusBaseText {
text: qsTr("Community history service")
}
Item {
Layout.fillWidth: true
implicitHeight: d.optionHeight
}
StatusCheckBox {
id: archiveSupportToggle
}
}
RowLayout {
Layout.fillWidth: true
StatusBaseText {
text: qsTr("Request to join required")
}
Item {
Layout.fillWidth: true
implicitHeight: d.optionHeight
}
StatusCheckBox {
id: requestToJoinToggle
}
}
RowLayout {
Layout.fillWidth: true
StatusBaseText {
text: qsTr("Any member can pin a message")
}
Item {
Layout.fillWidth: true
implicitHeight: d.optionHeight
}
StatusCheckBox {
id: pinMessagesToggle
}
}
}

View File

@ -0,0 +1,29 @@
import QtQuick 2.14
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
StatusInput {
id: root
leftPadding: 0
rightPadding: 0
label: qsTr("Leaving community message")
charLimit: 80
input.placeholder.text: qsTr("The message a member will see when they leave your community")
input.placeholder.wrapMode: Text.WordWrap
validators: [
StatusMinLengthValidator {
minLength: 1
errorMessage: Utils.getErrorMessage(root.errors,
qsTr("community intro message"))
}
]
validationMode: StatusInput.ValidationMode.Always
}

View File

@ -17,6 +17,7 @@ Item {
// optional // optional
property string previousPage property string previousPage
property bool dirty: false property bool dirty: false
property bool editable: false
readonly property Item contentItem: contentLoader.item readonly property Item contentItem: contentLoader.item
@ -30,7 +31,8 @@ Item {
} }
function notifyDirty() { function notifyDirty() {
toastAnimation.running = true cancelChangesButtonAnimation.running = true
saveChangesButtonAnimation.running = true
saveChangesButton.forceActiveFocus() saveChangesButton.forceActiveFocus()
} }
@ -74,76 +76,73 @@ Item {
id: contentLoader id: contentLoader
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
Layout.leftMargin: 24
Layout.rightMargin: 24
sourceComponent: root.content sourceComponent: root.content
} }
}
Rectangle { Rectangle {
id: toastMessage Layout.fillWidth: true
anchors { implicitHeight: buttonsLayout.implicitHeight
bottom: parent.bottom
horizontalCenter: parent.horizontalCenter
bottomMargin: 16
}
height: toastContent.height + 16
width: toastContent.width + 16
opacity: root.dirty ? 1 : 0
color: Theme.palette.statusToastMessage.backgroundColor color: Theme.palette.statusToastMessage.backgroundColor
radius: 8 visible: root.editable
border.color: Theme.palette.dangerColor2
border.width: 2
layer.enabled: true
layer.effect: DropShadow {
verticalOffset: 3
radius: 8
samples: 15
fast: true
cached: true
color: Theme.palette.dangerColor2
spread: 0.1
}
NumberAnimation on border.width {
id: toastAnimation
from: 0
to: 4
loops: 2
duration: 600
onFinished: toastMessage.border.width = 2
}
RowLayout { RowLayout {
id: toastContent id: buttonsLayout
x: 8 anchors.fill: parent
y: 8 enabled: root.dirty
StatusBaseText { Item {
text: qsTr("Changes detected!") Layout.fillWidth: true
color: Theme.palette.directColor1
} }
StatusButton { StatusButton {
text: qsTr("Cancel") id: cancelChangesButton
text: qsTr("Cancel changes")
type: StatusBaseButton.Type.Danger type: StatusBaseButton.Type.Danger
border.color: textColor
border.width: 0
onClicked: root.resetChangesClicked() onClicked: root.resetChangesClicked()
NumberAnimation on border.width {
id: cancelChangesButtonAnimation
from: 0
to: 2
loops: 2
duration: 600
onFinished: cancelChangesButton.border.width = 0
}
} }
StatusButton { StatusButton {
id: saveChangesButton id: saveChangesButton
text: qsTr("Save changes") text: qsTr("Save changes")
onClicked: root.saveChangesClicked()
}
}
Behavior on opacity { border.color: textColor
NumberAnimation {} border.width: 0
onClicked: root.saveChangesClicked()
NumberAnimation on border.width {
id: saveChangesButtonAnimation
from: 0
to: 2
loops: 2
duration: 600
onFinished: saveChangesButton.border.width = 0
}
}
}
} }
} }
} }

View File

@ -16,21 +16,24 @@ import StatusQ.Popups 0.1
import StatusQ.Controls 0.1 import StatusQ.Controls 0.1
import StatusQ.Controls.Validators 0.1 import StatusQ.Controls.Validators 0.1
import "../../controls/community"
Flickable { Flickable {
id: root id: root
property color color: Theme.palette.primaryColor1
property alias name: nameInput.text property alias name: nameInput.text
property alias description: descriptionTextInput.text property alias description: descriptionTextInput.text
property string logoImageData: "" property alias introMessage: introMessageTextInput.text
property alias logoImagePath: logoEditor.source property alias outroMessage: outroMessageTextInput.text
property alias logoCropRect: logoEditor.cropRect property alias color: colorPicker.color
property string bannerImageData: "" property alias options: options
property alias bannerPath: bannerEditor.source
property alias bannerCropRect: bannerEditor.cropRect property alias logoImageData: logoPicker.imageData
property bool isCommunityHistoryArchiveSupportEnabled: false property alias logoImagePath: logoPicker.source
property alias historyArchiveSupportToggle: historyArchiveSupportToggle.checked property alias logoCropRect: logoPicker.cropRect
property alias bannerImageData: bannerPicker.imageData
property alias bannerPath: bannerPicker.source
property alias bannerCropRect: bannerPicker.cropRect
contentWidth: layout.width contentWidth: layout.width
contentHeight: layout.height contentHeight: layout.height
@ -38,182 +41,64 @@ Flickable {
interactive: contentHeight > height interactive: contentHeight > height
flickableDirection: Flickable.VerticalFlick flickableDirection: Flickable.VerticalFlick
implicitWidth: 600
implicitHeight: 800
ColumnLayout { ColumnLayout {
id: layout id: layout
width: root.width width: root.width
spacing: 12 spacing: 12
StatusInput { CommunityNameInput {
id: nameInput id: nameInput
Layout.fillWidth: true Layout.fillWidth: true
leftPadding: 0
rightPadding: 0
label: qsTr("Community name")
charLimit: 30
input.placeholderText: qsTr("A catchy name")
validators: [
StatusMinLengthValidator {
minLength: 1
errorMessage: Utils.getErrorMessage(nameInput.errors,
qsTr("community name"))
}
]
validationMode: StatusInput.ValidationMode.Always
Component.onCompleted: nameInput.input.forceActiveFocus(Qt.MouseFocusReason) Component.onCompleted: nameInput.input.forceActiveFocus(Qt.MouseFocusReason)
} }
StatusInput { CommunityDescriptionInput {
id: descriptionTextInput id: descriptionTextInput
Layout.fillWidth: true
leftPadding: 0
rightPadding: 0
label: qsTr("Description")
charLimit: 140
input.placeholderText: qsTr("What your community is about")
input.multiline: true
input.implicitHeight: 88
validators: [
StatusMinLengthValidator {
minLength: 1
errorMessage: Utils.getErrorMessage(
descriptionTextInput.errors,
qsTr("community description"))
}
]
validationMode: StatusInput.ValidationMode.Always
}
ColumnLayout {
spacing: 8
// Logo
//
StatusBaseText {
text: qsTr("Community logo")
font.pixelSize: 15
color: Theme.palette.directColor1
}
EditCroppedImagePanel {
id: logoEditor
Layout.preferredWidth: 128
Layout.preferredHeight: Layout.preferredWidth / aspectRatio
Layout.alignment: Qt.AlignHCenter
imageFileDialogTitle: qsTr("Choose an image as logo")
title: qsTr("Community logo")
acceptButtonText: qsTr("Make this my Community logo")
dataImage: root.logoImageData
NoImageUploadedPanel {
anchors.centerIn: parent
visible: !logoEditor.userSelectedImage && !root.logoImageData
}
}
// Banner
//
StatusBaseText {
text: qsTr("Community banner")
font.pixelSize: 15
color: Theme.palette.directColor1
}
EditCroppedImagePanel {
id: bannerEditor
Layout.preferredWidth: 475
Layout.preferredHeight: Layout.preferredWidth / aspectRatio
Layout.alignment: Qt.AlignHCenter
imageFileDialogTitle: qsTr("Choose an image for banner")
title: qsTr("Community banner")
acceptButtonText: qsTr("Make this my Community banner")
roundedImage: false
aspectRatio: 375/184
dataImage: root.bannerImageData
NoImageUploadedPanel {
anchors.centerIn: parent
visible: !bannerEditor.userSelectedImage && !root.bannerImageData
showARHint: true
}
}
Rectangle {
Layout.fillWidth: true Layout.fillWidth: true
} }
}
ColumnLayout { CommunityLogoPicker {
id: logoPicker
Layout.fillWidth: true Layout.fillWidth: true
spacing: 8
StatusBaseText {
text: qsTr("Community colour")
font.pixelSize: 15
color: Theme.palette.directColor1
} }
StatusPickerButton { CommunityBannerPicker {
id: bannerPicker
Layout.fillWidth: true Layout.fillWidth: true
property string validationError: ""
bgColor: root.color
contentColor: Theme.palette.indirectColor1
text: root.color.toString()
onClicked: {
colorDialog.color = root.color;
colorDialog.open();
}
onTextChanged: {
validationError = Utils.validateAndReturnError(text,
Utils.Validate.NoEmpty |
Utils.Validate.TextHexColor);
} }
StatusColorDialog { CommunityColorPicker {
id: colorDialog id: colorPicker
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;
}
}
}
}
StatusListItem {
title: qsTr("Community history service")
Layout.fillWidth: true Layout.fillWidth: true
visible: root.isCommunityHistoryArchiveSupportEnabled
components: [
StatusSwitch {
id: historyArchiveSupportToggle
enabled: root.isCommunityHistoryArchiveSupportEnabled
} }
]
StatusModalDivider {
Layout.fillWidth: true
Layout.bottomMargin: -layout.spacing
}
CommunityOptions {
id: options
}
StatusModalDivider {
Layout.fillWidth: true
Layout.topMargin: -layout.spacing
Layout.bottomMargin: 8
}
CommunityIntroMessageInput {
id: introMessageTextInput
Layout.fillWidth: true
}
CommunityOutroMessageInput {
id: outroMessageTextInput
Layout.fillWidth: true
} }
Item { Item {

View File

@ -9,27 +9,26 @@ import StatusQ.Components 0.1
import "../../layouts" import "../../layouts"
/*! TODO: very confusing to be refactored
The "API" properties are just for input purposes and to track the initial state
used in evaluating the dirty flag. They should not be updated based
on user input. The final relevant values are accessed through the \c item member of \c edit property
*/
StackLayout { StackLayout {
id: root id: root
property string name property string name
property string description property string description
property string introMessage
property string outroMessage
property string logoImageData property string logoImageData
property string bannerImageData property string bannerImageData
property rect bannerCropRect property rect bannerCropRect
property color color property color color
property bool archiveSupportEnabled
property bool requestToJoinEnabled
property bool pinMessagesEnabled
property bool archiveSupportOptionVisible: false
property bool editable: false property bool editable: false
property bool owned: false property bool owned: false
property bool isCommunityHistoryArchiveSupportEnabled: false
property bool historyArchiveSupportToggle: false
signal edited(Item item) // item containing edited fields (name, description, logoImagePath, bannerPath, bannerCropRect, color) signal edited(Item item) // item containing edited fields (name, description, logoImagePath, color, options, etc..)
clip: true clip: true
@ -128,31 +127,39 @@ StackLayout {
previousPage: qsTr("Overview") previousPage: qsTr("Overview")
title: qsTr("Edit Community") title: qsTr("Edit Community")
editable: true
content: CommunityEditSettingsPanel { content: CommunityEditSettingsPanel {
id: communityEditSettingsPanel
name: root.name name: root.name
description: root.description description: root.description
introMessage: root.introMessage
outroMessage: root.outroMessage
color: root.color color: root.color
logoImageData: root.logoImageData logoImageData: root.logoImageData
bannerImageData: root.bannerImageData bannerImageData: root.bannerImageData
isCommunityHistoryArchiveSupportEnabled: root.isCommunityHistoryArchiveSupportEnabled options {
historyArchiveSupportToggle: root.historyArchiveSupportToggle archiveSupportOptionVisible: root.archiveSupportOptionVisible
archiveSupportEnabled: root.archiveSupportEnabled
requestToJoinEnabled: root.requestToJoinEnabled
pinMessagesEnabled: root.pinMessagesEnabled
}
Component.onCompleted: { Component.onCompleted: {
editCommunityPage.dirty = editCommunityPage.dirty =
Qt.binding(() => { Qt.binding(() => {
return root.name !== name || return root.name != name ||
root.description !== description || root.description != description ||
root.introMessage != introMessage ||
root.outroMessage != outroMessage ||
root.archiveSupportEnabled != options.archiveSupportEnabled ||
root.requestToJoinEnabled != options.requestToJoinEnabled ||
root.pinMessagesEnabled != options.pinMessagesEnabled ||
root.color != color ||
logoImagePath.length > 0 || logoImagePath.length > 0 ||
isValidRect(logoCropRect) || isValidRect(logoCropRect) ||
root.color !== color ||
bannerPath.length > 0 || bannerPath.length > 0 ||
isValidRect(bannerCropRect) || isValidRect(bannerCropRect)
root.historyArchiveSupportToggle !== historyArchiveSupportToggle
}) })
function isValidRect(r /*rect*/) { return r.width !== 0 && r.height !== 0 } function isValidRect(r /*rect*/) { return r.width !== 0 && r.height !== 0 }
} }
} }

View File

@ -20,7 +20,6 @@ Column {
signal membersListButtonClicked() signal membersListButtonClicked()
signal notificationsButtonClicked(bool checked) signal notificationsButtonClicked(bool checked)
signal editButtonClicked()
signal transferOwnershipButtonClicked() signal transferOwnershipButtonClicked()
signal leaveButtonClicked() signal leaveButtonClicked()
signal copyToClipboard(string link) signal copyToClipboard(string link)
@ -111,16 +110,6 @@ Column {
bottomPadding: 8 bottomPadding: 8
} }
StatusListItem {
anchors.horizontalCenter: parent.horizontalCenter
visible: root.community.amISectionAdmin
//% "Edit community"
title: qsTrId("edit-community")
icon.name: "edit"
type: StatusListItem.Type.Secondary
sensor.onClicked: root.editButtonClicked()
}
StatusListItem { StatusListItem {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
visible: root.community.amISectionAdmin visible: root.community.amISectionAdmin

View File

@ -64,12 +64,6 @@ StatusModal {
onMembersListButtonClicked: root.contentItem.push(membersList) onMembersListButtonClicked: root.contentItem.push(membersList)
onNotificationsButtonClicked: root.communitySectionModule.setCommunityMuted(checked) onNotificationsButtonClicked: root.communitySectionModule.setCommunityMuted(checked)
onEditButtonClicked: Global.openPopup(editCommunityroot, {
store: root.store,
community: root.community,
communitySectionModule: root.communitySectionModule,
onSave: root.close
})
onTransferOwnershipButtonClicked: Global.openPopup(transferOwnershiproot, { onTransferOwnershipButtonClicked: Global.openPopup(transferOwnershiproot, {
privateKey: communitySectionModule.exportCommunity(root.community.id), privateKey: communitySectionModule.exportCommunity(root.community.id),
store: root.store store: root.store
@ -94,18 +88,6 @@ StatusModal {
} }
} }
Component {
id: editCommunityroot
CreateCommunityPopup {
anchors.centerIn: parent
store: root.store
isEdit: true
onClosed: {
destroy()
}
}
}
Component { Component {
id: membersList id: membersList
CommunityProfilePopupMembersListPanel { CommunityProfilePopupMembersListPanel {

View File

@ -48,14 +48,20 @@ StatusModal {
qsTrId("new-category") qsTrId("new-category")
contentItem: Column { contentItem: Column {
property alias categoryName: nameInput
width: root.width width: root.width
property alias categoryName: nameInput topPadding: 16
StatusInput { StatusInput {
id: nameInput id: nameInput
anchors.left: parent.left
anchors.leftMargin: 16
label: qsTr("Category title")
charLimit: maxCategoryNameLength charLimit: maxCategoryNameLength
input.placeholderText: qsTr("Category title") input.placeholderText: qsTr("Name the category")
validators: [StatusMinLengthValidator { validators: [StatusMinLengthValidator {
minLength: 1 minLength: 1
errorMessage: Utils.getErrorMessage(nameInput.errors, qsTr("category name")) errorMessage: Utils.getErrorMessage(nameInput.errors, qsTr("category name"))

View File

@ -102,9 +102,14 @@ StatusModal {
Column { Column {
id: content id: content
width: popup.width width: popup.width
topPadding: 16
StatusInput { StatusInput {
id: nameInput id: nameInput
anchors.left: parent.left
anchors.leftMargin: 16
label: qsTr("Channel name") label: qsTr("Channel name")
charLimit: popup.maxChannelNameLength charLimit: popup.maxChannelNameLength
input.placeholderText: qsTr("Name the channel") input.placeholderText: qsTr("Name the channel")
@ -208,6 +213,10 @@ StatusModal {
StatusInput { StatusInput {
id: descriptionTextArea id: descriptionTextArea
anchors.left: parent.left
anchors.leftMargin: 16
label: qsTr("Description") label: qsTr("Description")
charLimit: 140 charLimit: 140

View File

@ -1,6 +1,7 @@
import QtQuick 2.12 import QtQuick 2.14
import QtQuick.Controls 2.3 import QtQuick.Controls 2.4
import QtGraphicalEffects 1.13 import QtQuick.Layouts 1.14
import QtGraphicalEffects 1.14
import QtQuick.Dialogs 1.3 import QtQuick.Dialogs 1.3
import utils 1.0 import utils 1.0
@ -14,417 +15,168 @@ import StatusQ.Controls 0.1
import StatusQ.Controls.Validators 0.1 import StatusQ.Controls.Validators 0.1
import StatusQ.Popups 0.1 import StatusQ.Popups 0.1
import "../../controls/community"
StatusModal { StatusModal {
id: popup id: root
height: 509
property var store property var store
property var communitySectionModule
property bool isEdit: false
property QtObject community: null
property var onSave: () => {}
readonly property int maxCommunityNameLength: 30
readonly property int maxCommunityDescLength: 140
readonly property var communityColorValidator: Utils.Validate.NoEmpty
| Utils.Validate.TextHexColor
onOpened: { width: 640
if (isEdit) { height: 720
contentItem.communityName.input.text = community.name;
contentItem.communityDescription.input.text = community.description;
contentItem.communityColor.color = community.color;
contentItem.communityColor.colorSelected = true
if (community.largeImage) {
contentItem.communityImage.selectedImage = community.largeImage
}
requestToJoinCheckbox.checked = community.access === Constants.communityChatOnRequestAccess
}
contentItem.communityName.input.edit.forceActiveFocus()
}
onClosed: destroy()
function isFormValid() { // Code below is needed because StatusModal content padding is messed up
return contentItem.communityName.valid && contentItem.communityDescription.valid && // FIXME: when StatusModal is reworked
Utils.validateAndReturnError(contentItem.communityColor.color.toString().toUpperCase(), topPadding: 64 + 16 // 64 is header height
communityColorValidator) === "" leftPadding: 16
} rightPadding: 16
bottomPadding: 71 + 16 // 71 is footer height
header.title: isEdit ? header.title: qsTr("Create New Community")
qsTr("Edit Community") :
qsTr("Create New Community")
contentItem: ScrollView { rightButtons: [
StatusButton {
text: qsTr("Next")
enabled: generalView.canGoNext
visible: generalView.visible
id: scrollView onClicked: stackLayout.currentIndex = stackLayout.currentIndex + 1
property ScrollBar vScrollBar: ScrollBar.vertical
property alias communityName: nameInput
property alias communityDescription: descriptionTextArea
property alias communityColor: colorDialog
property alias communityImage: addImageButton
property alias imageCropperModal: imageCropperModal
property alias historyArchiveSupportToggle: historyArchiveSupportToggle
property alias pinMessagesAllMembersCheckbox: pinMessagesAllMembersCheckbox
contentHeight: content.height
bottomPadding: 8
width: popup.width
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
clip: true
function scrollBackUp() {
vScrollBar.setPosition(0)
}
Column {
id: content
width: popup.width
spacing: 8
Item {
height: 8
width: parent.width
}
StatusInput {
id: nameInput
label: qsTr("Name your community")
charLimit: maxCommunityNameLength
input.placeholderText: qsTr("A catchy name")
validators: [
StatusMinLengthValidator {
minLength: 1
errorMessage: Utils.getErrorMessage(nameInput.errors, "community name")
}, },
StatusRegularExpressionValidator { StatusButton {
regularExpression: /^[^<>]+$/ text: qsTr("Create Community")
errorMessage: qsTr("This is not a valid community name") enabled: introMessageInput.valid && outroMessageInput.valid
visible: introMessageInput.visible
onClicked: d.createCommunity()
} }
] ]
validationMode: StatusInput.ValidationMode.Always
}
StatusInput {
id: descriptionTextArea
label: qsTr("Give it a short description")
charLimit: maxCommunityDescLength
input.placeholderText: qsTr("What your community is about")
input.multiline: true
input.implicitHeight: 88
input.verticalAlignment: TextEdit.AlignTop
validators: [StatusMinLengthValidator {
minLength: 1
errorMessage: Utils.getErrorMessage(descriptionTextArea.errors, "community description")
}]
validationMode: StatusInput.ValidationMode.Always
}
StatusBaseText {
id: thumbnailText
//% "Thumbnail image"
text: qsTrId("thumbnail-image")
font.pixelSize: 15
color: Theme.palette.directColor1
anchors.left: parent.left
anchors.leftMargin: 16
anchors.topMargin: 8
}
Item {
width: parent.width
height: addImageButton.height + 32
Rectangle {
id: addImageButton
color: imagePreview.visible ? "transparent" : Style.current.inputBackground
width: 128
height: width
radius: width / 2
anchors.centerIn: parent
property string selectedImage: ""
FileDialog {
id: imageDialog
//% "Please choose an image"
title: qsTrId("please-choose-an-image")
folder: shortcuts.pictures
nameFilters: [
//% "Image files (*.jpg *.jpeg *.png)"
qsTrId("image-files----jpg---jpeg---png-")
]
onAccepted: {
addImageButton.selectedImage = imageDialog.fileUrls[0]
imageCropperModal.open()
}
}
Rectangle {
id: imagePreviewCropper
clip: true
width: parent.width
height: parent.height
radius: parent.width / 2
visible: !!addImageButton.selectedImage
Image {
id: imagePreview
visible: !!addImageButton.selectedImage
source: addImageButton.selectedImage
fillMode: Image.PreserveAspectFit
width: parent.width
height: parent.height
}
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Rectangle {
anchors.centerIn: parent
width: imageCropperModal.width
height: imageCropperModal.height
radius: width / 2
}
}
}
Item {
id: addImageCenter
visible: !imagePreview.visible
width: uploadText.width
height: childrenRect.height
anchors.centerIn: parent
SVGImage {
id: imageImg
source: Style.svg("images_icon")
width: 20
height: 18
anchors.horizontalCenter: parent.horizontalCenter
}
StatusBaseText {
id: uploadText
//% "Upload"
text: qsTrId("upload")
anchors.top: imageImg.bottom
anchors.topMargin: 5
font.pixelSize: 15
color: Theme.palette.baseColor1
}
}
StatusRoundButton {
type: StatusRoundButton.Type.Secondary
icon.name: "add"
anchors.top: parent.top
anchors.right: parent.right
anchors.rightMargin: Style.current.halfPadding
highlighted: sensor.containsMouse
}
MouseArea {
id: sensor
hoverEnabled: true
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: imageDialog.open()
}
ImageCropperModal {
id: imageCropperModal
selectedImage: addImageButton.selectedImage
ratio: "1:1"
}
}
}
StatusBaseText {
text: qsTr("Community colour")
font.pixelSize: 15
color: Theme.palette.directColor1
anchors.left: parent.left
anchors.leftMargin: 16
}
Item {
anchors.horizontalCenter: parent.horizontalCenter
height: colorSelectorButton.height + 16
width: parent.width - 32
StatusPickerButton {
id: colorSelectorButton
anchors.top: parent.top
anchors.topMargin: 10
property string validationError: ""
bgColor: colorDialog.colorSelected ?
colorDialog.color : Theme.palette.baseColor2
contentColor: colorDialog.colorSelected ? Theme.palette.indirectColor1 : Theme.palette.baseColor1
text: colorDialog.colorSelected ?
colorDialog.color.toString().toUpperCase() :
qsTr("Pick a colour")
onClicked: colorDialog.open();
onTextChanged: {
if (colorDialog.colorSelected) {
validationError = Utils.validateAndReturnError(text, communityColorValidator)
}
}
}
StatusColorDialog {
id: colorDialog
anchors.centerIn: parent
property bool colorSelected: false
color: Theme.palette.primaryColor1
onAccepted: colorSelected = true
}
StatusBaseText {
text: colorSelectorButton.validationError
visible: !!text
color: Theme.palette.dangerColor1
anchors.top: colorSelectorButton.bottom
anchors.topMargin: 4
anchors.right: colorSelectorButton.right
}
}
StatusModalDivider {
topPadding: 8
bottomPadding: 8
visible: !isEdit
}
StatusListItem {
anchors.horizontalCenter: parent.horizontalCenter
visible: popup.store.isCommunityHistoryArchiveSupportEnabled
title: qsTr("Community history service")
sensor.onClicked: {
if (popup.store.isCommunityHistoryArchiveSupportEnabled) {
historyArchiveSupportToggle.checked = !historyArchiveSupportToggle.checked
}
}
components: [
StatusCheckBox {
id: historyArchiveSupportToggle
enabled: popup.store.isCommunityHistoryArchiveSupportEnabled
checked: isEdit ? community.historyArchiveSupportEnabled : false
}
]
}
StatusListItem {
anchors.horizontalCenter: parent.horizontalCenter
title: qsTr("Request to join required")
sensor.onClicked: {
requestToJoinCheckbox.checked = !requestToJoinCheckbox.checked
}
components: [
StatusCheckBox {
id: requestToJoinCheckbox
checked: true
}
]
}
StatusListItem {
anchors.horizontalCenter: parent.horizontalCenter
title: qsTr("Any member can pin a message")
sensor.onClicked: {
pinMessagesAllMembersCheckbox.checked = !pinMessagesAllMembersCheckbox.checked
}
components: [
StatusCheckBox {
id: pinMessagesAllMembersCheckbox
checked: isEdit ? community.pinMessageAllMembersEnabled : false
}
]
}
}
}
leftButtons: [ leftButtons: [
StatusRoundButton { StatusRoundButton {
id: btnBack id: btnBack
visible: isEdit
icon.name: "arrow-right" icon.name: "arrow-right"
icon.width: 20 icon.width: 20
icon.height: 16 icon.height: 16
rotation: 180 rotation: 180
onClicked: popup.destroy() visible: stackLayout.currentIndex !== 0
onClicked: stackLayout.currentIndex = 0
} }
] ]
rightButtons: [ StackLayout {
StatusButton { id: stackLayout
id: btnCreateEdit
enabled: isFormValid() anchors.fill: parent
text: isEdit ?
//% "Save" Flickable {
qsTrId("Save") : id: generalView
//% "Create"
qsTrId("create") readonly property bool canGoNext: nameInput.valid && descriptionTextInput.valid
onClicked: {
if (!isFormValid()) { clip: true
popup.contentItem.scrollBackUp() contentHeight: generalViewLayout.height
return interactive: contentHeight > height
flickableDirection: Flickable.VerticalFlick
ColumnLayout {
id: generalViewLayout
width: generalView.width
spacing: 12
CommunityNameInput {
id: nameInput
Layout.fillWidth: true
Component.onCompleted: nameInput.input.forceActiveFocus(
Qt.MouseFocusReason)
} }
let error = false; CommunityDescriptionInput {
if(isEdit) { id: descriptionTextInput
error = communitySectionModule.editCommunity( Layout.fillWidth: true
Utils.filterXSS(popup.contentItem.communityName.input.text),
Utils.filterXSS(popup.contentItem.communityDescription.input.text),
requestToJoinCheckbox.checked ? Constants.communityChatOnRequestAccess : Constants.communityChatPublicAccess,
popup.contentItem.communityColor.color.toString().toUpperCase(),
// to retain the existing image, pass "" for the image path
popup.contentItem.communityImage.selectedImage === community.largeImage ? "" :
popup.contentItem.communityImage.selectedImage,
popup.contentItem.imageCropperModal.aX,
popup.contentItem.imageCropperModal.aY,
popup.contentItem.imageCropperModal.bX,
popup.contentItem.imageCropperModal.bY,
popup.contentItem.historyArchiveSupportToggle.checked,
popup.contentItem.pinMessagesAllMembersCheckbox.checked
)
} else {
error = popup.store.createCommunity(
Utils.filterXSS(popup.contentItem.communityName.input.text),
Utils.filterXSS(popup.contentItem.communityDescription.input.text),
requestToJoinCheckbox.checked ? Constants.communityChatOnRequestAccess : Constants.communityChatPublicAccess,
popup.contentItem.communityColor.color.toString().toUpperCase(),
popup.contentItem.communityImage.selectedImage,
popup.contentItem.imageCropperModal.aX,
popup.contentItem.imageCropperModal.aY,
popup.contentItem.imageCropperModal.bX,
popup.contentItem.imageCropperModal.bY,
popup.contentItem.historyArchiveSupportToggle.checked,
popup.contentItem.pinMessagesAllMembersCheckbox.checked
)
} }
CommunityLogoPicker {
id: logoPicker
Layout.fillWidth: true
}
CommunityColorPicker {
id: colorPicker
Layout.fillWidth: true
}
StatusModalDivider {
Layout.fillWidth: true
}
CommunityOptions {
id: options
archiveSupportOptionVisible: root.store.isCommunityHistoryArchiveSupportEnabled
archiveSupportEnabled: archiveSupportOptionVisible
}
Item {
Layout.fillHeight: true
}
}
}
ColumnLayout {
id: introOutroMessageView
spacing: 12
CommunityIntroMessageInput {
id: introMessageInput
Layout.fillWidth: true
Layout.fillHeight: true
input.maximumHeight: 0
}
CommunityOutroMessageInput {
id: outroMessageInput
Layout.fillWidth: true
}
}
}
QtObject {
id: d
function createCommunity() {
let error = store.createCommunity(
Utils.filterXSS(nameInput.input.text),
Utils.filterXSS(descriptionTextInput.input.text),
Utils.filterXSS(introMessageInput.input.text),
Utils.filterXSS(outroMessageInput.input.text),
options.requestToJoinEnabled ? Constants.communityChatOnRequestAccess : Constants.communityChatPublicAccess,
colorPicker.color.toString().toUpperCase(),
logoPicker.source,
logoPicker.cropRect.x,
logoPicker.cropRect.y,
logoPicker.cropRect.x + logoPicker.cropRect.width,
logoPicker.cropRect.y + logoPicker.cropRect.height,
options.archiveSupportEnabled,
options.pinMessagesEnabled
)
if (error) { if (error) {
creatingError.text = error.error errorDialog.text = error.error
return creatingError.open() errorDialog.open()
} }
popup.onSave() root.close()
popup.close()
} }
} }
]
MessageDialog { MessageDialog {
id: creatingError id: errorDialog
//% "Error creating the community"
title: qsTrId("error-creating-the-community") title: qsTr("Error creating the community")
icon: StandardIcon.Critical icon: StandardIcon.Critical
standardButtons: StandardButton.Ok standardButtons: StandardButton.Ok
} }
} }

View File

@ -230,8 +230,8 @@ QtObject {
// Not Refactored Yet // Not Refactored Yet
property var activeCommunityChatsModel: "" //chatsModelInst.communities.activeCommunity.chats property var activeCommunityChatsModel: "" //chatsModelInst.communities.activeCommunity.chats
function createCommunity(communityName, communityDescription, checkedMembership, communityColor, communityImage, imageCropperModalaX, imageCropperModalaY, imageCropperModalbX, imageCropperModalbY, historyArchiveSupportEnabled, pinMessagesAllowedForMembers) { function createCommunity(name, description, introMessage, outroMessage, checkedMembership, color, image, imageAX, imageAY, imageBX, imageBY, historyArchiveSupportEnabled, pinMessagesAllowedForMembers) {
communitiesModuleInst.createCommunity(communityName, communityDescription, checkedMembership, communityColor, communityImage, imageCropperModalaX, imageCropperModalaY, imageCropperModalbX, imageCropperModalbY, historyArchiveSupportEnabled, pinMessagesAllowedForMembers); communitiesModuleInst.createCommunity(name, description, introMessage, outroMessage, checkedMembership, color, image, imageAX, imageAY, imageBX, imageBY, historyArchiveSupportEnabled, pinMessagesAllowedForMembers);
} }
function importCommunity(communityKey) { function importCommunity(communityKey) {

View File

@ -116,24 +116,35 @@ StatusAppTwoPanelLayout {
CommunityOverviewSettingsPanel { CommunityOverviewSettingsPanel {
name: root.community.name name: root.community.name
description: root.community.description description: root.community.description
introMessage: root.community.introMessage
outroMessage: root.community.outroMessage
logoImageData: root.community.image logoImageData: root.community.image
bannerImageData: root.community.bannerImageData bannerImageData: root.community.bannerImageData
color: root.community.color color: root.community.color
archiveSupportEnabled: root.community.historyArchiveSupportEnabled
requestToJoinEnabled: root.community.access === Constants.communityChatOnRequestAccess
pinMessagesEnabled: root.community.pinMessageAllMembersEnabled
archiveSupportOptionVisible: root.rootStore.isCommunityHistoryArchiveSupportEnabled
editable: root.community.amISectionAdmin editable: root.community.amISectionAdmin
isCommunityHistoryArchiveSupportEnabled: root.rootStore.isCommunityHistoryArchiveSupportEnabled
historyArchiveSupportToggle: community.historyArchiveSupportEnabled
onEdited: { onEdited: {
root.chatCommunitySectionModule.editCommunity( let error = root.chatCommunitySectionModule.editCommunity(
Utils.filterXSS(item.name), Utils.filterXSS(item.name),
Utils.filterXSS(item.description), Utils.filterXSS(item.description),
root.community.access, Utils.filterXSS(item.introMessage),
Utils.filterXSS(item.outroMessage),
item.options.requestToJoinEnabled ? Constants.communityChatOnRequestAccess : Constants.communityChatPublicAccess,
item.color.toString().toUpperCase(), item.color.toString().toUpperCase(),
JSON.stringify({imagePath: String(item.logoImagePath).replace("file://", ""), cropRect: item.logoCropRect}), JSON.stringify({imagePath: String(item.logoImagePath).replace("file://", ""), cropRect: item.logoCropRect}),
JSON.stringify({imagePath: String(item.bannerPath).replace("file://", ""), cropRect: item.bannerCropRect}), JSON.stringify({imagePath: String(item.bannerPath).replace("file://", ""), cropRect: item.bannerCropRect}),
item.historyArchiveSupportToggle, item.options.archiveSupportEnabled,
false /*TODO port the modal implementation*/ item.options.pinMessagesEnabled
) )
if (error) {
errorDialog.text = error.error
errorDialog.open()
}
} }
} }
@ -181,4 +192,11 @@ StatusAppTwoPanelLayout {
id: d id: d
property int currentIndex: 0 property int currentIndex: 0
} }
MessageDialog {
id: errorDialog
title: qsTr("Error editing the community")
icon: StandardIcon.Critical
standardButtons: StandardButton.Ok
}
} }

View File

@ -337,18 +337,6 @@ Item {
}) })
} }
StatusMenuItem {
enabled: model.amISectionAdmin
//% "Edit Community"
text: qsTrId("edit-community")
icon.name: "edit"
onTriggered: Global.openPopup(editCommunityPopup, {
store: appMain.rootStore,
community: model,
communitySectionModule: communityContextMenu.chatCommunitySectionModule
})
}
StatusMenuSeparator {} StatusMenuSeparator {}
StatusMenuItem { StatusMenuItem {
@ -738,17 +726,6 @@ Item {
} }
} }
Component {
id: editCommunityPopup
CreateCommunityPopup {
anchors.centerIn: parent
isEdit: true
onClosed: {
destroy()
}
}
}
Component { Component {
id: pinnedMessagesPopupComponent id: pinnedMessagesPopupComponent
PinnedMessagesPopup { PinnedMessagesPopup {

View File

@ -0,0 +1,86 @@
import QtQuick 2.14
import QtQuick.Controls 2.14
import QtQuick.Layouts 1.1
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Controls 0.1
import StatusQ.Components 0.1
import StatusQ.Popups 0.1
StatusModal {
id: root
property string name
property string introMessage
property url imageSrc
signal joined
// Code below is needed because StatusModal content padding is messed up
// FIXME: when StatusModal is reworked
width: undefined // popup is able to determine size from its content
topPadding: 64 + 16 // 64 is header height
leftPadding: 16
rightPadding: 16
bottomPadding: 71 + 16 // 71 is footer height
header.title: qsTr("Welcome to %1").arg(name)
rightButtons: [
StatusButton {
text: qsTr("Join %1").arg(root.name)
enabled: checkBox.checked
onClicked: {
root.joined()
root.close()
}
}
]
ColumnLayout {
anchors.fill: parent
spacing: 24
StatusRoundedImage {
Layout.alignment: Qt.AlignCenter
Layout.preferredHeight: 64
Layout.preferredWidth: 64
visible: image.status == Image.Loading || image.status == Image.Ready
image.source: root.imageSrc
}
ScrollView {
Layout.preferredWidth: contentWidth
Layout.minimumWidth: 300
Layout.fillHeight: true
Layout.preferredHeight: contentHeight
Layout.maximumHeight: 400
contentWidth: messageContent.width
contentHeight: messageContent.height
clip: true
StatusBaseText {
id: messageContent
width: Math.min(implicitWidth, 640)
text: root.introMessage !== "" ? root.introMessage : qsTr("Community <b>%1</b> has no intro message...").arg(root.name)
clip: true
color: Theme.palette.directColor1
wrapMode: Text.WordWrap
}
}
StatusCheckBox {
id: checkBox
Layout.alignment: Qt.AlignCenter
text: qsTr("I agree with the above")
}
}
}

View File

@ -1,6 +1,7 @@
ChatCommandsPopup 1.0 ChatCommandsPopup.qml ChatCommandsPopup 1.0 ChatCommandsPopup.qml
BlockContactConfirmationDialog 1.0 BlockContactConfirmationDialog.qml BlockContactConfirmationDialog 1.0 BlockContactConfirmationDialog.qml
ConfirmationDialog 1.0 ConfirmationDialog.qml ConfirmationDialog 1.0 ConfirmationDialog.qml
CommunityIntroDialog 1.0 CommunityIntroDialog.qml
DownloadModal 1.0 DownloadModal.qml DownloadModal 1.0 DownloadModal.qml
DownloadPage 1.0 DownloadPage.qml DownloadPage 1.0 DownloadPage.qml
ImageCropperModal 1.0 ImageCropperModal.qml ImageCropperModal 1.0 ImageCropperModal.qml

View File

@ -75,6 +75,21 @@ Item {
} }
} }
Component {
id: communityIntroDialog
CommunityIntroDialog {
anchors.centerIn: parent
property var joinMethod: () => {}
name: root.invitedCommunity ? root.invitedCommunity.name : ""
introMessage: root.invitedCommunity ? root.invitedCommunity.introMessage : ""
imageSrc: root.invitedCommunity ? root.invitedCommunity.image : ""
onJoined: joinMethod()
}
}
Loader { Loader {
id: rectangleBubbleLoader id: rectangleBubbleLoader
@ -297,6 +312,7 @@ Item {
//% "Unsupported state" //% "Unsupported state"
text: qsTrId("unsupported-state") text: qsTrId("unsupported-state")
onClicked: { onClicked: {
let onBtnClick = function(){ let onBtnClick = function(){
let error let error
@ -304,19 +320,20 @@ Item {
root.store.setActiveCommunity(communityId); root.store.setActiveCommunity(communityId);
return return
} else if (rectangleBubble.state === "unjoined") { } else if (rectangleBubble.state === "unjoined") {
error = root.store.joinCommunity(communityId) Global.openPopup(communityIntroDialog, { joinMethod: () => {
let error = root.store.joinCommunity(communityId)
if (error) joiningError.showError(error)
} });
} }
else if (rectangleBubble.state === "requestToJoin") { else if (rectangleBubble.state === "requestToJoin") {
error = root.store.requestToJoinCommunity(communityId, userProfile.name) Global.openPopup(communityIntroDialog, { joinMethod: () => {
if (!error) { let error = root.store.requestToJoinCommunity(communityId, userProfile.name)
rectangleBubble.isPendingRequest = root.store.isCommunityRequestPending(communityId) if (error) joiningError.showError(error)
} else rectangleBubble.isPendingRequest = root.store.isCommunityRequestPending(communityId)
} });
} }
if (error) { if (error) joiningError.showError(error)
joiningError.text = error
return joiningError.open()
}
} }
if (localAccountSensitiveSettings.communitiesEnabled) { if (localAccountSensitiveSettings.communitiesEnabled) {
@ -328,6 +345,12 @@ Item {
MessageDialog { MessageDialog {
id: joiningError id: joiningError
function showError(error) {
joiningError.text = error
joiningError.open()
}
//% "Error joining the community" //% "Error joining the community"
title: qsTrId("error-joining-the-community") title: qsTrId("error-joining-the-community")
icon: StandardIcon.Critical icon: StandardIcon.Critical