From 739437df56eb5054a988c74a4cdb2c22585bb2b5 Mon Sep 17 00:00:00 2001 From: Jonathan Rainville Date: Tue, 9 Aug 2022 15:01:09 -0400 Subject: [PATCH] Revert "fix(Profile): Added menu to remove profile image" This reverts commit f60dacc45dd35773fc205ef9c0a2640bb79030d7. --- .../Profile/popups/ChangeProfilePicModal.qml | 115 ++++++++++ .../views/profile/MyProfileSettingsView.qml | 2 - ui/imports/shared/controls/ImageCropper.qml | 213 ++++++++++++++++++ .../shared/controls/chat/ProfileHeader.qml | 26 +-- .../shared/panels/CropCornerRectangle.qml | 29 +++ .../shared/popups/ImageCropperModal.qml | 88 ++++++++ 6 files changed, 446 insertions(+), 27 deletions(-) create mode 100644 ui/app/AppLayouts/Profile/popups/ChangeProfilePicModal.qml create mode 100644 ui/imports/shared/controls/ImageCropper.qml create mode 100644 ui/imports/shared/panels/CropCornerRectangle.qml create mode 100644 ui/imports/shared/popups/ImageCropperModal.qml diff --git a/ui/app/AppLayouts/Profile/popups/ChangeProfilePicModal.qml b/ui/app/AppLayouts/Profile/popups/ChangeProfilePicModal.qml new file mode 100644 index 0000000000..fd895a666f --- /dev/null +++ b/ui/app/AppLayouts/Profile/popups/ChangeProfilePicModal.qml @@ -0,0 +1,115 @@ +import QtQuick 2.13 +import QtQuick.Dialogs 1.3 + +import utils 1.0 + +import StatusQ.Controls 0.1 + +import shared 1.0 +import shared.panels 1.0 +import shared.popups 1.0 + +// TODO: replace with StatusModal +ModalPopup { + id: popup + title: qsTr("Profile picture") + + property var profileStore + + property string selectedImage // selectedImage is for us to be able to analyze it before setting it as current + property string uploadError + property url largeImage: popup.profileStore.profileLargeImage + property bool hasIdentityImage: !!popup.profileStore.profileLargeImage + + onClosed: { + destroy() + } + + onSelectedImageChanged: { + if (!selectedImage) { + return + } + + cropImageModal.open() + } + + Item { + anchors.fill: parent + + RoundedImage { + id: profilePic + source: popup.largeImage + width: 160 + height: 160 + anchors.verticalCenter: parent.verticalCenter + anchors.horizontalCenter: parent.horizontalCenter + border.width: 1 + border.color: Style.current.border + onClicked: imageDialog.open() + } + + StyledText { + visible: !!uploadError + text: uploadError + anchors.left: parent.left + anchors.right: parent.right + anchors.top: profilePic.bottom + horizontalAlignment: Text.AlignHCenter + font.pixelSize: 13 + wrapMode: Text.WordWrap + anchors.topMargin: 13 + font.weight: Font.Thin + color: Style.current.danger + } + + ImageCropperModal { + id: cropImageModal + + selectedImage: popup.selectedImage + ratio: "1:1" + onCropFinished: { + popup.uploadError = popup.profileStore.uploadImage(selectedImage, aX, aY, bX, bY) + } + } + } + + footer: Item { + width: parent.width + height: uploadBtn.height + + StatusFlatButton { + visible: popup.hasIdentityImage + type: StatusBaseButton.Type.Danger + text: qsTr("Remove") + anchors.right: uploadBtn.left + anchors.rightMargin: Style.current.padding + anchors.bottom: parent.bottom + onClicked: { + popup.uploadError = popup.profileStore.removeImage() + } + } + + StatusButton { + id: uploadBtn + text: qsTr("Upload") + anchors.right: parent.right + anchors.bottom: parent.bottom + onClicked: { + imageDialog.open() + } + + FileDialog { + id: imageDialog + title: qsTr("Please choose an image") + folder: shortcuts.pictures + nameFilters: [ + qsTr("Image files (*.jpg *.jpeg *.png)") + ] + onAccepted: { + selectedImage = imageDialog.fileUrls[0] + } + } + } + } +} + diff --git a/ui/app/AppLayouts/Profile/views/profile/MyProfileSettingsView.qml b/ui/app/AppLayouts/Profile/views/profile/MyProfileSettingsView.qml index c37175c7ba..fe8cade5ef 100644 --- a/ui/app/AppLayouts/Profile/views/profile/MyProfileSettingsView.qml +++ b/ui/app/AppLayouts/Profile/views/profile/MyProfileSettingsView.qml @@ -58,8 +58,6 @@ ColumnLayout { Layout.leftMargin: Style.current.padding Layout.rightMargin: Style.current.padding - store: root.profileStore - displayName: profileStore.name pubkey: profileStore.pubkey icon: profileStore.profileLargeImage diff --git a/ui/imports/shared/controls/ImageCropper.qml b/ui/imports/shared/controls/ImageCropper.qml new file mode 100644 index 0000000000..4c57c6d5e3 --- /dev/null +++ b/ui/imports/shared/controls/ImageCropper.qml @@ -0,0 +1,213 @@ +import QtQuick 2.13 + +import "../panels" + +import utils 1.0 + +Item { + id: root + property Image image + property alias selectorRectangle: selectorRectangle + property string ratio: "" + property var splitRatio: !!ratio ? ratio.split(":") : null + property int widthRatio: !!ratio ? parseInt(splitRatio[0]) : -1 + property int heightRatio: !!ratio ? parseInt(splitRatio[1]) : -1 + property bool settingCorners: false + property int draggedCorner: 0 + property bool ready: false + + readonly property int topLeft: 0 + readonly property int topRight: 1 + readonly property int bottomLeft: 2 + readonly property int bottomRight: 3 + + width: image.width + height: image.height + + Rectangle { + id: selectorRectangle + visible: false + x: 0 + y: 0 + border.width: 2 + border.color: Style.current.orange + color: Style.current.transparent + + function fitRatio(makeBigger) { + if (!!ratio) { + if ((makeBigger && selectorRectangle.width < selectorRectangle.height) || (!makeBigger && selectorRectangle.width > selectorRectangle.height)) { + selectorRectangle.width = (selectorRectangle.height/heightRatio) * widthRatio + } else { + selectorRectangle.height = (selectorRectangle.width/widthRatio) * heightRatio + } + } + } + + function initialSetup() { + selectorRectangle.width = image.width + selectorRectangle.height = image.height + + fitRatio() + topLeftCorner.x = 0 + topLeftCorner.y = 0 + topRightCorner.x = selectorRectangle.width - topRightCorner.width + topRightCorner.y = 0 + bottomLeftCorner.x = 0 + bottomLeftCorner.y = selectorRectangle.height - topRightCorner.height + bottomRightCorner.x = selectorRectangle.width - topRightCorner.width + bottomRightCorner.y = selectorRectangle.height - topRightCorner.height + } + + + function adjustRectangleSize() { + if (!selectorRectangle.visible) { + return + } + + selectorRectangle.width = bottomRightCorner.x + bottomRightCorner.width - topLeftCorner.x + selectorRectangle.height = bottomRightCorner.y + bottomRightCorner.height - topLeftCorner.y + selectorRectangle.x = topLeftCorner.x + selectorRectangle.y = topLeftCorner.y + + if (!!ratio) { + // FIXME with a ratio that is not 1:1, the rectangle can go out of bounds + fitRatio() + + switch(draggedCorner) { + case topLeft: + selectorRectangle.x = topLeftCorner.x + selectorRectangle.y = topLeftCorner.y + break + case topRight: + selectorRectangle.x = topRightCorner.x - selectorRectangle.width + topRightCorner.width + selectorRectangle.y = topRightCorner.y + break + case bottomLeft: + selectorRectangle.x = bottomLeftCorner.x + selectorRectangle.y = bottomLeftCorner.y - selectorRectangle.height + bottomLeftCorner.height + break + case bottomRight: + selectorRectangle.x = bottomRightCorner.x - selectorRectangle.width + bottomRightCorner.width + selectorRectangle.y = bottomRightCorner.y - selectorRectangle.height + bottomRightCorner.height + break + } + } + } + + Connections { + target: image + onStatusChanged: { + if (image.status === Image.Ready) { + selectorRectangle.initialSetup() + selectorRectangle.visible = true + root.ready = true + } + } + } + } + function putCorners() { + settingCorners = true + + topLeftCorner.x = selectorRectangle.x + topLeftCorner.y = selectorRectangle.y + topRightCorner.x = selectorRectangle.x + selectorRectangle.width - topRightCorner.width + topRightCorner.y = selectorRectangle.y + bottomLeftCorner.x = selectorRectangle.x + bottomLeftCorner.y = selectorRectangle.y + selectorRectangle.height - topRightCorner.height + bottomRightCorner.x = selectorRectangle.x + selectorRectangle.width - topRightCorner.width + bottomRightCorner.y = selectorRectangle.y + selectorRectangle.height - topRightCorner.height + + settingCorners = false + } + + + // Size calculations are only done on top-left and bottom-right, because the other two corners follow them + CropCornerRectangle { + id: topLeftCorner + onXChanged: { + if (settingCorners) return + if (x < 0) x = 0 + if (x > topRightCorner.x - width) x = topRightCorner.x - width + + bottomLeftCorner.x = x + selectorRectangle.adjustRectangleSize() + } + onYChanged: { + if (settingCorners) return + if (y < 0) y = 0 + if (y > bottomRightCorner.y - height) y = bottomRightCorner.y - height + + topRightCorner.y = y + selectorRectangle.adjustRectangleSize() + } + onPressed: { + draggedCorner = topLeft + } + + onReleased: { + putCorners() + } + } + + CropCornerRectangle { + id: topRightCorner + onXChanged: { + if (settingCorners) return + bottomRightCorner.x = x + } + onYChanged: { + if (settingCorners) return + topLeftCorner.y = y + } + onPressed: { + draggedCorner = topRight + } + onReleased: { + putCorners() + } + } + + CropCornerRectangle { + id: bottomLeftCorner + onXChanged: { + if (settingCorners) return + topLeftCorner.x = x + } + onYChanged: { + if (settingCorners) return + bottomRightCorner.y = y + } + onPressed: { + draggedCorner = bottomLeft + } + onReleased: { + putCorners() + } + } + + CropCornerRectangle { + id: bottomRightCorner + onXChanged: { + if (settingCorners) return + if (x < topLeftCorner.x + topLeftCorner.width) x = topLeftCorner.x + topLeftCorner.width + if (x > image.width - width) x = image.width - width + topRightCorner.x = x + + selectorRectangle.adjustRectangleSize() + } + onYChanged: { + if (settingCorners) return + if (y < topRightCorner.y + topRightCorner.height) y = topRightCorner.y + topRightCorner.height + if (y > image.height - height) y = image.height - height + bottomLeftCorner.y = y + + selectorRectangle.adjustRectangleSize() + } + onPressed: { + draggedCorner = bottomRight + } + onReleased: { + putCorners() + } + } +} diff --git a/ui/imports/shared/controls/chat/ProfileHeader.qml b/ui/imports/shared/controls/chat/ProfileHeader.qml index 11df8f8996..429135de0c 100644 --- a/ui/imports/shared/controls/chat/ProfileHeader.qml +++ b/ui/imports/shared/controls/chat/ProfileHeader.qml @@ -7,7 +7,6 @@ import shared.controls 1.0 import StatusQ.Controls 0.1 import StatusQ.Components 0.1 -import StatusQ.Popups 0.1 import StatusQ.Core.Utils 0.1 as StatusQUtils Item { @@ -95,12 +94,7 @@ Item { icon.width: d.getSize(8, 12, 20) icon.height: d.getSize(8, 12, 20) - onClicked: { - if (!!root.store.profileLargeImage) - imageEditMenu.popup(this, mouse.x, mouse.y); - else - Global.openChangeProfilePicPopup(); - } + onClicked: Global.openChangeProfilePicPopup() } } @@ -216,22 +210,4 @@ Item { publicKey: root.pubkey } } - - StatusPopupMenu { - id: imageEditMenu - - StatusMenuItem { - text: qsTr("Upload a file") - icon.name: "download" - iconRotation: 180 - onTriggered: Global.openChangeProfilePicPopup() - } - - StatusMenuItem { - text: qsTr("Remove image") - type: StatusMenuItem.Danger - icon.name: "delete" - onTriggered: root.store.removeImage() - } - } } diff --git a/ui/imports/shared/panels/CropCornerRectangle.qml b/ui/imports/shared/panels/CropCornerRectangle.qml new file mode 100644 index 0000000000..ca38e4ec2b --- /dev/null +++ b/ui/imports/shared/panels/CropCornerRectangle.qml @@ -0,0 +1,29 @@ +import QtQuick 2.13 + +import utils 1.0 + +Rectangle { + signal released() + signal pressed() + + id: root + width: 25 + height: 25 + border.width: 2 + border.color: Style.current.orange + color: Utils.setColorAlpha(Style.current.orange, 0.5) + + Drag.active: dragArea.drag.active + + MouseArea { + id: dragArea + property int oldX + property int oldY + + anchors.fill: parent + drag.target: parent + cursorShape: Qt.PointingHandCursor + onReleased: root.released() + onPressed: root.pressed() + } +} diff --git a/ui/imports/shared/popups/ImageCropperModal.qml b/ui/imports/shared/popups/ImageCropperModal.qml new file mode 100644 index 0000000000..429a7e42ed --- /dev/null +++ b/ui/imports/shared/popups/ImageCropperModal.qml @@ -0,0 +1,88 @@ +import QtQuick 2.13 +import QtQuick.Controls 2.13 +import QtQuick.Layouts 1.13 + +import utils 1.0 + +import StatusQ.Controls 0.1 + +import "../controls" +import "." + +// TODO: replace with StatusModal +ModalPopup { + property string selectedImage + property string ratio: "1:1" + property int aX: 0 + property int aY: 0 + property int bX: 0 + property int bY: 0 + signal cropFinished(aX: int, aY: int, bX: int, bY: int) + + id: cropImageModal + width: image.width + 50 + height: image.height + 170 + title: qsTr("Crop your image (optional)") + + Image { + id: image + width: 400 + source: cropImageModal.selectedImage + anchors.verticalCenter: parent.verticalCenter + anchors.horizontalCenter: parent.horizontalCenter + fillMode: Image.PreserveAspectFit + } + + ImageCropper { + id: imageCropper + x: image.x + y: image.y + image: image + ratio: cropImageModal.ratio + onReadyChanged: { + if (ready) { + // cropImageModal.calculateCrop() + cropImageModal.aX = Qt.binding(function() { + const aXPercent = imageCropper.selectorRectangle.x / image.width + return Math.round(aXPercent * image.sourceSize.width) + }) + cropImageModal.aY = Qt.binding(function() { + const aYPercent = imageCropper.selectorRectangle.y / image.height + return Math.round(aYPercent * image.sourceSize.height) + }) + cropImageModal.bX = Qt.binding(function() { + const bXPercent = (imageCropper.selectorRectangle.x + imageCropper.selectorRectangle.width) / image.width + return Math.round(bXPercent * image.sourceSize.width) + }) + cropImageModal.bY = Qt.binding(function() { + const bYPercent = (imageCropper.selectorRectangle.y + imageCropper.selectorRectangle.height) / image.height + return Math.round(bYPercent * image.sourceSize.height) + }) + } + } + + } + + footer: StatusButton { + id: doneBtn + text: qsTr("Finish") + anchors.right: parent.right + anchors.bottom: parent.bottom + onClicked: { + const aXPercent = imageCropper.selectorRectangle.x / image.width + const aYPercent = imageCropper.selectorRectangle.y / image.height + const bXPercent = (imageCropper.selectorRectangle.x + imageCropper.selectorRectangle.width) / image.width + const bYPercent = (imageCropper.selectorRectangle.y + imageCropper.selectorRectangle.height) / image.height + + + const aX = Math.round(aXPercent * image.sourceSize.width) + const aY = Math.round(aYPercent * image.sourceSize.height) + + const bX = Math.round(bXPercent * image.sourceSize.width) + const bY = Math.round(bYPercent * image.sourceSize.height) + + cropImageModal.cropFinished(aX, aY, bX, bY) + cropImageModal.close() + } + } +}