feat(Communities/Settings): Communities banner editing

Use the generic EditCroppedImagePanel for banner cropping

- Depends on status-go IdentityImage banner extension

fixes #5118
This commit is contained in:
Stefan 2022-05-23 21:53:51 +03:00 committed by Stefan Dunca
parent 4b399d15e3
commit 9f633f0fcc
17 changed files with 144 additions and 53 deletions

View File

@ -378,6 +378,7 @@ proc editCommunity*(
color: string,
imageUrl: string,
aX: int, aY: int, bX: int, bY: int,
bannerJsonStr: string,
historyArchiveSupportEnabled: bool,
pinMessageAllMembersEnabled: bool) =
self.communityService.editCommunity(
@ -388,6 +389,7 @@ proc editCommunity*(
color,
imageUrl,
aX, aY, bX, bY,
bannerJsonStr,
historyArchiveSupportEnabled,
pinMessageAllMembersEnabled)

View File

@ -243,7 +243,7 @@ method removeUserFromCommunity*(self: AccessInterface, pubKey: string) {.base.}
method banUserFromCommunity*(self: AccessInterface, pubKey: string) {.base.} =
raise newException(ValueError, "No implementation available")
method editCommunity*(self: AccessInterface, name: string, description: string, access: int, color: string, imagePath: string, aX: int, aY: int, bX: int, bY: int, historyArchiveSupportEnabled: bool, pinMessageAllMembersEnabled: bool) {.base.} =
method editCommunity*(self: AccessInterface, name: string, description: string, access: int, color: string, imagePath: string, aX: int, aY: int, bX: int, bY: int, bannerJsonData: string, historyArchiveSupportEnabled: bool, pinMessageAllMembersEnabled: bool) {.base.} =
raise newException(ValueError, "No implementation available")
method exportCommunity*(self: AccessInterface): string {.base.} =

View File

@ -727,9 +727,11 @@ method banUserFromCommunity*(self: Module, pubKey: string) =
method editCommunity*(self: Module, name: string, description: string,
access: int, color: string,
imagePath: string,
aX: int, aY: int, bX: int, bY: int, historyArchiveSupportEnabled: bool,
aX: int, aY: int, bX: int, bY: int,
bannerJsonStr: string,
historyArchiveSupportEnabled: bool,
pinMessageAllMembersEnabled: bool) =
self.controller.editCommunity(name, description, access, color, imagePath, aX, aY, bX, bY, historyArchiveSupportEnabled, pinMessageAllMembersEnabled)
self.controller.editCommunity(name, description, access, color, imagePath, aX, aY, bX, bY, bannerJsonStr, historyArchiveSupportEnabled, pinMessageAllMembersEnabled)
method exportCommunity*(self: Module): string =
self.controller.exportCommunity()

View File

@ -244,8 +244,8 @@ QtObject:
proc banUserFromCommunity*(self: View, pubKey: string) {.slot.} =
self.delegate.banUserFromCommunity(pubKey)
proc editCommunity*(self: View, name: string, description: string, access: int, color: string, imagePath: string, aX: int, aY: int, bX: int, bY: int, historyArchiveSupportEnabled: bool, pinMessageAllMembersEnabled: bool) {.slot.} =
self.delegate.editCommunity(name, description, access, color, imagePath, aX, aY, bX, bY, historyArchiveSupportEnabled, pinMessageAllMembersEnabled)
proc editCommunity*(self: View, name: string, description: string, access: int, color: string, imagePath: string, aX: int, aY: int, bX: int, bY: int, bannerJsonData: string, historyArchiveSupportEnabled: bool, pinMessageAllMembersEnabled: bool) {.slot.} =
self.delegate.editCommunity(name, description, access, color, imagePath, aX, aY, bX, bY, bannerJsonData, historyArchiveSupportEnabled, pinMessageAllMembersEnabled)
proc exportCommunity*(self: View): string {.slot.} =
self.delegate.exportCommunity()

View File

@ -75,6 +75,7 @@ method getCommunityItem(self: Module, c: CommunityDto): SectionItem =
c.admin,
c.description,
c.images.thumbnail,
c.images.banner,
icon = "",
c.color,
hasNotification = false,

View File

@ -197,6 +197,7 @@ proc createChannelGroupItem[T](self: Module[T], c: ChannelGroupDto): SectionItem
c.admin,
c.description,
c.images.thumbnail,
c.images.banner,
icon = if (isCommunity): "" else: conf.CHAT_SECTION_ICON,
c.color,
hasNotification,

View File

@ -59,6 +59,12 @@ QtObject:
QtProperty[string] image:
read = getImage
proc getBannerImageData(self: ActiveSection): string {.slot.} =
return self.item.bannerImageData
QtProperty[string] bannerImageData:
read = getBannerImageData
proc getIcon(self: ActiveSection): string {.slot.} =
return self.item.icon

View File

@ -19,6 +19,7 @@ type
amISectionAdmin: bool
description: string
image: string
bannerImageData: string
icon: string
color: string
hasNotification: bool
@ -44,6 +45,7 @@ proc initItem*(
amISectionAdmin = false,
description = "",
image = "",
bannerImageData = "",
icon = "",
color = "",
hasNotification = false,
@ -68,6 +70,7 @@ proc initItem*(
result.amISectionAdmin = amISectionAdmin
result.description = description
result.image = image
result.bannerImageData = bannerImageData
result.icon = icon
result.color = color
result.hasNotification = hasNotification
@ -99,6 +102,7 @@ proc `$`*(self: SectionItem): string =
amISectionAdmin: {self.amISectionAdmin},
description: {self.description},
image: {self.image},
bannerImageData: {self.bannerImageData},
icon: {self.icon},
color: {self.color},
hasNotification: {self.hasNotification},
@ -135,6 +139,9 @@ proc description*(self: SectionItem): string {.inline.} =
proc image*(self: SectionItem): string {.inline.} =
self.image
proc bannerImageData*(self: SectionItem): string {.inline.} =
self.bannerImageData
proc icon*(self: SectionItem): string {.inline.} =
self.icon

View File

@ -12,6 +12,7 @@ type
AmISectionAdmin
Description
Image
BannerImageData
Icon
Color
HasNotification
@ -72,6 +73,7 @@ QtObject:
ModelRole.AmISectionAdmin.int: "amISectionAdmin",
ModelRole.Description.int:"description",
ModelRole.Image.int:"image",
ModelRole.BannerImageData.int:"bannerImageData",
ModelRole.Icon.int:"icon",
ModelRole.Color.int:"color",
ModelRole.HasNotification.int:"hasNotification",
@ -114,6 +116,8 @@ QtObject:
result = newQVariant(item.description)
of ModelRole.Image:
result = newQVariant(item.image)
of ModelRole.BannerImageData:
result = newQVariant(item.bannerImageData)
of ModelRole.Icon:
result = newQVariant(item.icon)
of ModelRole.Color:
@ -211,6 +215,7 @@ QtObject:
ModelRole.Name.int,
ModelRole.Description.int,
ModelRole.Image.int,
ModelRole.BannerImageData.int,
ModelRole.Icon.int,
ModelRole.Color.int,
ModelRole.HasNotification.int,
@ -312,6 +317,7 @@ QtObject:
"amISectionAdmin": item.amISectionAdmin,
"description": item.description,
"image": item.image,
"bannerImageData": item.bannerImageData,
"icon": item.icon,
"color": item.color,
"hasNotification": item.hasNotification,

View File

@ -31,6 +31,7 @@ type
Images* = object
thumbnail*: string
large*: string
banner*: string
type ChatMember* = object
id*: string
@ -136,6 +137,10 @@ proc toImages*(jsonObj: JsonNode): Images =
if(jsonObj.getProp("thumbnail", thumbnailObj)):
discard thumbnailObj.getProp("uri", result.thumbnail)
var bannerObj: JsonNode
if(jsonObj.getProp("banner", bannerObj)):
discard bannerObj.getProp("uri", result.banner)
proc toCategory*(jsonObj: JsonNode): Category =
result = Category()
if (not jsonObj.getProp("category_id", result.id)):

View File

@ -572,6 +572,7 @@ QtObject:
color: string,
imageUrl: string,
aX: int, aY: int, bX: int, bY: int,
bannerJsonStr: string,
historyArchiveSupportEnabled: bool,
pinMessageAllMembersEnabled: bool) =
try:
@ -584,6 +585,7 @@ QtObject:
color,
image,
aX, aY, bX, bY,
bannerJsonStr,
historyArchiveSupportEnabled,
pinMessageAllMembersEnabled)

View File

@ -2,6 +2,8 @@ import json, strutils
import core, utils
import response_type
import interpret/cropped_image
export response_type
proc getJoinedComunities*(): RpcResponse[JsonNode] {.raises: [Exception].} =
@ -66,9 +68,11 @@ proc editCommunity*(
aY: int,
bX: int,
bY: int,
bannerJsonStr: string,
historyArchiveSupportEnabled: bool,
pinMessageAllMembersEnabled: bool
): RpcResponse[JsonNode] {.raises: [Exception].} =
let bannerImage = newCroppedImage(bannerJsonStr)
result = callPrivateRPC("editCommunity".prefix, %*[{
# TODO this will need to be renamed membership (small m)
"CommunityID": communityId,
@ -82,6 +86,7 @@ proc editCommunity*(
"imageAy": aY,
"imageBx": bX,
"imageBy": bY,
"banner": bannerImage,
"historyArchiveSupportEnabled": historyArchiveSupportEnabled,
"pinMessageAllMembersEnabled": pinMessageAllMembersEnabled
}])

View File

@ -0,0 +1,22 @@
import json
#[ Represent a part of an image
image: path to the image
x, y, width, height: crop rectangle in image coordinates
]#
type CroppedImage* = ref object
imagePath*: string
x*: int
y*: int
width*: int
height*: int
proc newCroppedImage*(jsonStr: string): CroppedImage =
let rootNode = parseJson(jsonStr)
result = CroppedImage()
result.imagePath = rootNode["imagePath"].getStr()
let cropRect = rootNode["cropRect"]
result.x = int(cropRect["x"].getFloat())
result.y = int(cropRect["y"].getFloat())
result.width = int(cropRect["width"].getFloat())
result.height = int(cropRect["height"].getFloat())

View File

@ -23,11 +23,15 @@ Flickable {
property alias name: nameInput.text
property alias description: descriptionTextInput.text
property alias image: addImageButton.selectedImage
property alias logoImagePath: addImageButton.selectedImage
property string logoImageData: ""
readonly property alias imageAx: imageCropperModal.aX
readonly property alias imageAy: imageCropperModal.aY
readonly property alias imageBx: imageCropperModal.bX
readonly property alias imageBy: imageCropperModal.bY
property string bannerImageData: ""
property alias bannerPath: bannerEditor.source
property alias bannerCropRect: bannerEditor.cropRect
property bool isCommunityHistoryArchiveSupportEnabled: false
property alias historyArchiveSupportToggle: historyArchiveSupportToggle.checked
@ -91,12 +95,10 @@ Flickable {
}
ColumnLayout {
Layout.fillWidth: true
spacing: 8
StatusBaseText {
//% "Thumbnail image"
text: qsTrId("thumbnail-image")
text: qsTr("Community logo")
font.pixelSize: 15
color: Theme.palette.directColor1
}
@ -121,11 +123,12 @@ Flickable {
id: imageDialog
title: qsTr("Please choose an image")
folder: shortcuts.pictures
nameFilters: [//% "Image files (*.jpg *.jpeg *.png)"
qsTrId("image-files----jpg---jpeg---png-")]
nameFilters: [qsTr("Image files (*.jpg *.jpeg *.png)")]
onAccepted: {
addImageButton.selectedImage = imageDialog.fileUrls[0]
imageCropperModal.open()
if(imageDialog.fileUrls.length > 0) {
addImageButton.selectedImage = imageDialog.fileUrls[0]
imageCropperModal.open()
}
}
}
@ -135,12 +138,14 @@ Flickable {
width: parent.width
height: parent.height
radius: parent.width / 2
visible: !!addImageButton.selectedImage
visible: !!addImageButton.selectedImage || !!root.logoImageData
Image {
id: imagePreview
visible: !!addImageButton.selectedImage
visible: !!addImageButton.selectedImage || !!root.logoImageData
source: addImageButton.selectedImage
? addImageButton.selectedImage
: root.logoImageData
fillMode: Image.PreserveAspectFit
width: parent.width
height: parent.height
@ -156,30 +161,10 @@ Flickable {
}
}
Item {
id: addImageCenter
visible: !imagePreview.visible
width: uploadText.width
height: childrenRect.height
NoImageUploadedPanel {
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
}
visible: !imagePreview.visible
}
StatusRoundButton {
@ -206,6 +191,43 @@ Flickable {
}
}
}
// 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
}
}
ColumnLayout {

View File

@ -10,23 +10,26 @@ import StatusQ.Components 0.1
import "../../layouts"
/*! TODO: very confusing to be refactored
The "API" properties are just for input purpose to and to track the inital state
that will be used in evaluating the dirty flag. They should not be updated based
on the user input. The final values are accessed through the \c item member of \c edit property
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 image
property string logoImageData
property string bannerImageData
property rect bannerCropRect
property color color
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, image, color)
signal edited(Item item) // item containing edited fields (name, description, logoImagePath, bannerPath, bannerCropRect, color)
clip: true
@ -47,7 +50,7 @@ StackLayout {
icon {
width: 80
height: 80
isLetterIdenticon: !root.image
isLetterIdenticon: !root.logoImageData
color: root.color
letterSize: width / 2.4
}
@ -55,7 +58,7 @@ StackLayout {
image {
width: 80
height: 80
source: root.image
source: root.logoImageData
}
}
@ -132,19 +135,24 @@ StackLayout {
name: root.name
description: root.description
color: root.color
image: root.image
logoImageData: root.logoImageData
bannerImageData: root.bannerImageData
isCommunityHistoryArchiveSupportEnabled: root.isCommunityHistoryArchiveSupportEnabled
historyArchiveSupportToggle: root.historyArchiveSupportToggle
Component.onCompleted: {
editCommunityPage.dirty =
Qt.binding(() => {
return root.name != name ||
root.description != description ||
root.image != image ||
root.color != color ||
return root.name !== name ||
root.description !== description ||
logoImagePath.length > 0 ||
root.color !== color ||
bannerPath.length > 0 ||
isValidRect(bannerCropRect) ||
root.historyArchiveSupportToggle !== historyArchiveSupportToggle
})
function isValidRect(r /*rect*/) { return r.width !== 0 && r.height !== 0 }
}
}

View File

@ -116,7 +116,8 @@ StatusAppTwoPanelLayout {
CommunityOverviewSettingsPanel {
name: root.community.name
description: root.community.description
image: root.community.image
logoImageData: root.community.image
bannerImageData: root.community.bannerImageData
color: root.community.color
editable: root.community.amISectionAdmin
isCommunityHistoryArchiveSupportEnabled: root.rootStore.isCommunityHistoryArchiveSupportEnabled
@ -128,12 +129,13 @@ StatusAppTwoPanelLayout {
Utils.filterXSS(item.description),
root.community.access,
item.color.toString().toUpperCase(),
item.image === root.community.image ? "" : item.image,
item.logoImagePath,
item.imageAx,
item.imageAy,
item.imageBx,
item.imageBy,
root.rootStore.isCommunityHistoryArchiveSupportEnabled,
JSON.stringify({imagePath: String(item.bannerPath).replace("file://", ""), cropRect: item.bannerCropRect}),
item.historyArchiveSupportToggle,
false /*TODO port the modal implementation*/
)
}

2
vendor/status-go vendored

@ -1 +1 @@
Subproject commit 82550fca343f56fbfcb71cc416ebcd96bf5409c9
Subproject commit 63e58ba0354448b67449053b4599b2974d398a11