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

View File

@ -16,21 +16,24 @@ import StatusQ.Popups 0.1
import StatusQ.Controls 0.1
import StatusQ.Controls.Validators 0.1
import "../../controls/community"
Flickable {
id: root
property color color: Theme.palette.primaryColor1
property alias name: nameInput.text
property alias description: descriptionTextInput.text
property string logoImageData: ""
property alias logoImagePath: logoEditor.source
property alias logoCropRect: logoEditor.cropRect
property string bannerImageData: ""
property alias bannerPath: bannerEditor.source
property alias bannerCropRect: bannerEditor.cropRect
property bool isCommunityHistoryArchiveSupportEnabled: false
property alias historyArchiveSupportToggle: historyArchiveSupportToggle.checked
property alias introMessage: introMessageTextInput.text
property alias outroMessage: outroMessageTextInput.text
property alias color: colorPicker.color
property alias options: options
property alias logoImageData: logoPicker.imageData
property alias logoImagePath: logoPicker.source
property alias logoCropRect: logoPicker.cropRect
property alias bannerImageData: bannerPicker.imageData
property alias bannerPath: bannerPicker.source
property alias bannerCropRect: bannerPicker.cropRect
contentWidth: layout.width
contentHeight: layout.height
@ -38,182 +41,64 @@ Flickable {
interactive: contentHeight > height
flickableDirection: Flickable.VerticalFlick
implicitWidth: 600
implicitHeight: 800
ColumnLayout {
id: layout
width: root.width
spacing: 12
StatusInput {
CommunityNameInput {
id: nameInput
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)
}
StatusInput {
CommunityDescriptionInput {
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
}
CommunityLogoPicker {
id: logoPicker
Layout.fillWidth: true
}
ColumnLayout {
CommunityBannerPicker {
id: bannerPicker
Layout.fillWidth: true
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;
}
}
}
}
StatusListItem {
title: qsTr("Community history service")
CommunityColorPicker {
id: colorPicker
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 {

View File

@ -9,27 +9,26 @@ import StatusQ.Components 0.1
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 {
id: root
property string name
property string description
property string introMessage
property string outroMessage
property string logoImageData
property string bannerImageData
property rect bannerCropRect
property color color
property bool archiveSupportEnabled
property bool requestToJoinEnabled
property bool pinMessagesEnabled
property bool archiveSupportOptionVisible: false
property bool editable: 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
@ -128,31 +127,39 @@ StackLayout {
previousPage: qsTr("Overview")
title: qsTr("Edit Community")
editable: true
content: CommunityEditSettingsPanel {
id: communityEditSettingsPanel
name: root.name
description: root.description
introMessage: root.introMessage
outroMessage: root.outroMessage
color: root.color
logoImageData: root.logoImageData
bannerImageData: root.bannerImageData
isCommunityHistoryArchiveSupportEnabled: root.isCommunityHistoryArchiveSupportEnabled
historyArchiveSupportToggle: root.historyArchiveSupportToggle
options {
archiveSupportOptionVisible: root.archiveSupportOptionVisible
archiveSupportEnabled: root.archiveSupportEnabled
requestToJoinEnabled: root.requestToJoinEnabled
pinMessagesEnabled: root.pinMessagesEnabled
}
Component.onCompleted: {
editCommunityPage.dirty =
Qt.binding(() => {
return root.name !== name ||
root.description !== description ||
return root.name != name ||
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 ||
isValidRect(logoCropRect) ||
root.color !== color ||
bannerPath.length > 0 ||
isValidRect(bannerCropRect) ||
root.historyArchiveSupportToggle !== historyArchiveSupportToggle
isValidRect(bannerCropRect)
})
function isValidRect(r /*rect*/) { return r.width !== 0 && r.height !== 0 }
}
}

View File

@ -20,7 +20,6 @@ Column {
signal membersListButtonClicked()
signal notificationsButtonClicked(bool checked)
signal editButtonClicked()
signal transferOwnershipButtonClicked()
signal leaveButtonClicked()
signal copyToClipboard(string link)
@ -111,16 +110,6 @@ Column {
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 {
anchors.horizontalCenter: parent.horizontalCenter
visible: root.community.amISectionAdmin

View File

@ -64,12 +64,6 @@ StatusModal {
onMembersListButtonClicked: root.contentItem.push(membersList)
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, {
privateKey: communitySectionModule.exportCommunity(root.community.id),
store: root.store
@ -94,18 +88,6 @@ StatusModal {
}
}
Component {
id: editCommunityroot
CreateCommunityPopup {
anchors.centerIn: parent
store: root.store
isEdit: true
onClosed: {
destroy()
}
}
}
Component {
id: membersList
CommunityProfilePopupMembersListPanel {

View File

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

View File

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

View File

@ -1,6 +1,7 @@
import QtQuick 2.12
import QtQuick.Controls 2.3
import QtGraphicalEffects 1.13
import QtQuick 2.14
import QtQuick.Controls 2.4
import QtQuick.Layouts 1.14
import QtGraphicalEffects 1.14
import QtQuick.Dialogs 1.3
import utils 1.0
@ -14,417 +15,168 @@ import StatusQ.Controls 0.1
import StatusQ.Controls.Validators 0.1
import StatusQ.Popups 0.1
import "../../controls/community"
StatusModal {
id: popup
height: 509
id: root
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: {
if (isEdit) {
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
width: 640
height: 720
// Code below is needed because StatusModal content padding is messed up
// FIXME: when StatusModal is reworked
topPadding: 64 + 16 // 64 is header height
leftPadding: 16
rightPadding: 16
bottomPadding: 71 + 16 // 71 is footer height
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()
}
contentItem.communityName.input.edit.forceActiveFocus()
}
onClosed: destroy()
function isFormValid() {
return contentItem.communityName.valid && contentItem.communityDescription.valid &&
Utils.validateAndReturnError(contentItem.communityColor.color.toString().toUpperCase(),
communityColorValidator) === ""
}
header.title: isEdit ?
qsTr("Edit Community") :
qsTr("Create New Community")
contentItem: ScrollView {
id: scrollView
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 {
regularExpression: /^[^<>]+$/
errorMessage: qsTr("This is not a valid community name")
}
]
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: [
StatusRoundButton {
id: btnBack
visible: isEdit
icon.name: "arrow-right"
icon.width: 20
icon.height: 16
rotation: 180
onClicked: popup.destroy()
visible: stackLayout.currentIndex !== 0
onClicked: stackLayout.currentIndex = 0
}
]
rightButtons: [
StatusButton {
id: btnCreateEdit
enabled: isFormValid()
text: isEdit ?
//% "Save"
qsTrId("Save") :
//% "Create"
qsTrId("create")
onClicked: {
if (!isFormValid()) {
popup.contentItem.scrollBackUp()
return
StackLayout {
id: stackLayout
anchors.fill: parent
Flickable {
id: generalView
readonly property bool canGoNext: nameInput.valid && descriptionTextInput.valid
clip: true
contentHeight: generalViewLayout.height
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;
if(isEdit) {
error = communitySectionModule.editCommunity(
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
)
CommunityDescriptionInput {
id: descriptionTextInput
Layout.fillWidth: true
}
if (error) {
creatingError.text = error.error
return creatingError.open()
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
}
popup.onSave()
popup.close()
}
}
]
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) {
errorDialog.text = error.error
errorDialog.open()
}
root.close()
}
}
MessageDialog {
id: creatingError
//% "Error creating the community"
title: qsTrId("error-creating-the-community")
id: errorDialog
title: qsTr("Error creating the community")
icon: StandardIcon.Critical
standardButtons: StandardButton.Ok
}
}

View File

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

View File

@ -116,24 +116,35 @@ StatusAppTwoPanelLayout {
CommunityOverviewSettingsPanel {
name: root.community.name
description: root.community.description
introMessage: root.community.introMessage
outroMessage: root.community.outroMessage
logoImageData: root.community.image
bannerImageData: root.community.bannerImageData
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
isCommunityHistoryArchiveSupportEnabled: root.rootStore.isCommunityHistoryArchiveSupportEnabled
historyArchiveSupportToggle: community.historyArchiveSupportEnabled
onEdited: {
root.chatCommunitySectionModule.editCommunity(
let error = root.chatCommunitySectionModule.editCommunity(
Utils.filterXSS(item.name),
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(),
JSON.stringify({imagePath: String(item.logoImagePath).replace("file://", ""), cropRect: item.logoCropRect}),
JSON.stringify({imagePath: String(item.bannerPath).replace("file://", ""), cropRect: item.bannerCropRect}),
item.historyArchiveSupportToggle,
false /*TODO port the modal implementation*/
item.options.archiveSupportEnabled,
item.options.pinMessagesEnabled
)
if (error) {
errorDialog.text = error.error
errorDialog.open()
}
}
}
@ -181,4 +192,11 @@ StatusAppTwoPanelLayout {
id: d
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 {}
StatusMenuItem {
@ -738,17 +726,6 @@ Item {
}
}
Component {
id: editCommunityPopup
CreateCommunityPopup {
anchors.centerIn: parent
isEdit: true
onClosed: {
destroy()
}
}
}
Component {
id: pinnedMessagesPopupComponent
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
BlockContactConfirmationDialog 1.0 BlockContactConfirmationDialog.qml
ConfirmationDialog 1.0 ConfirmationDialog.qml
CommunityIntroDialog 1.0 CommunityIntroDialog.qml
DownloadModal 1.0 DownloadModal.qml
DownloadPage 1.0 DownloadPage.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 {
id: rectangleBubbleLoader
@ -297,6 +312,7 @@ Item {
//% "Unsupported state"
text: qsTrId("unsupported-state")
onClicked: {
let onBtnClick = function(){
let error
@ -304,19 +320,20 @@ Item {
root.store.setActiveCommunity(communityId);
return
} 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") {
error = root.store.requestToJoinCommunity(communityId, userProfile.name)
if (!error) {
rectangleBubble.isPendingRequest = root.store.isCommunityRequestPending(communityId)
}
Global.openPopup(communityIntroDialog, { joinMethod: () => {
let error = root.store.requestToJoinCommunity(communityId, userProfile.name)
if (error) joiningError.showError(error)
else rectangleBubble.isPendingRequest = root.store.isCommunityRequestPending(communityId)
} });
}
if (error) {
joiningError.text = error
return joiningError.open()
}
if (error) joiningError.showError(error)
}
if (localAccountSensitiveSettings.communitiesEnabled) {
@ -328,6 +345,12 @@ Item {
MessageDialog {
id: joiningError
function showError(error) {
joiningError.text = error
joiningError.open()
}
//% "Error joining the community"
title: qsTrId("error-joining-the-community")
icon: StandardIcon.Critical