fix(@desktop/sugnup): Profile omage chosen during signup sould be saved for the user and squish test for the same

fixes #6861
This commit is contained in:
Khushboo Mehta 2022-08-18 09:38:47 +02:00 committed by Khushboo-dev-cpp
parent b23414e999
commit 8eb328bae7
30 changed files with 172 additions and 466 deletions

View File

@ -241,10 +241,10 @@ method onNodeLogin*[T](self: Module[T], error: string) =
quit() # quit the app
if error.len == 0:
self.controller.cleanTmpData()
self.delegate.userLoggedIn()
if currStateObj.flowType() != FlowType.AppLogin:
self.controller.storeIdentityImage()
self.controller.cleanTmpData()
else:
self.view.setAppState(AppState.StartupState)
if currStateObj.flowType() == FlowType.AppLogin:
@ -300,4 +300,4 @@ method onSharedKeycarModuleFlowTerminated*[T](self: Module[T], lastStepInTheCurr
self.keycardSharedModule = nil
if lastStepInTheCurrentFlow:
self.controller.cleanTmpData()
self.view.setCurrentStartupState(newWelcomeState(FlowType.General, nil))
self.view.setCurrentStartupState(newWelcomeState(FlowType.General, nil))

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

View File

@ -28,6 +28,7 @@ _MAX_WAIT_OBJ_TIMEOUT = 5000
# The default minimum timeout to find ui object
_MIN_WAIT_OBJ_TIMEOUT = 500
_SEARCH_IMAGES_PATH = "../shared/searchImages/"
# Waits for the given object is loaded, visible and enabled.
# It returns a tuple: True in case it is found. Otherwise, false. And the object itself.
@ -297,3 +298,7 @@ def verify_not_found(realNameVarName: str, message: str, timeoutMSec: int = 500)
test.fail(message, f'Unexpected: the object "{realNameVarName}" was found.')
except LookupError as err:
test.passes(message, f'Expected: the object "{realNameVarName}" was not found. Exception: {str(err)}.')
def grabScreenshot_and_save(obj, imageName:str, delay:int = 0):
img = object.grabScreenshot(obj, {"delay": delay})
img.save(_SEARCH_IMAGES_PATH + imageName + ".png")

View File

@ -100,3 +100,6 @@ def log(text: str):
def verify_screenshot(vp: str):
test.vp(vp)
def imagePresent(imageName: str, tolerant: bool = True, threshold: int = 99.5, minScale: int = 50, maxScale: int = 200, multiscale: bool = True):
test.imagePresent(imageName, {"tolerant": tolerant, "threshold": threshold, "minScale": minScale, "maxScale": maxScale, "multiscale": multiscale})

View File

@ -16,12 +16,11 @@ from drivers.SquishDriverVerification import *
import time
class MainScreenComponents(Enum):
STATUS_ICON = "mainWindow_statusIcon_StatusIcon_2"
PUBLIC_CHAT_ICON = "mainWindow_public_chat_icon_StatusIcon"
CHAT_NAVBAR_ICON = "navBarListView_Chat_navbar_StatusNavBarTabButton"
COMMUNITY_PORTAL_BUTTON = "navBarListView_Communities_Portal_navbar_StatusNavBarTabButton"
JOIN_PUBLIC_CHAT = "join_public_chat_StatusMenuItemDelegate"
SETTINGS_BUTTON = "settings_navbar_settings_icon_StatusIcon"
SETTINGS_BUTTON = "navBarListView_Settings_navbar_StatusNavBarTabButton"
WALLET_BUTTON = "wallet_navbar_wallet_icon_StatusIcon"
START_CHAT_BTN = "mainWindow_startChat"
CHAT_LIST = "chatList_Repeater"

View File

@ -14,6 +14,7 @@ import sys
from drivers.SquishDriver import *
from drivers.SquishDriverVerification import *
from common.SeedUtils import *
from screens.StatusMainScreen import MainScreenComponents
class AgreementPopUp(Enum):
@ -36,7 +37,9 @@ class SignUpComponents(Enum):
CONFIRM_PSW_AGAIN_INPUT: str = "onboarding_confirmPswAgain_Input"
FINALIZE_PSW_BUTTON: str = "onboarding_finalise_password_button"
PASSWORD_PREFERENCE: str = "mainWindow_I_prefer_to_use_my_password_StatusBaseText"
PROFILE_IMAGE_CROP_WORKFLOW_ITEM: str = "mainWindow_WelcomeScreen_Image_Crop_Workflow_Item"
PROFILE_IMAGE_CROPPER_ACCEPT_BUTTON: str = "mainWindow_WelcomeScreen_Image_Cropper_Accept_Button"
WELCOME_SCREEN_USER_PROFILE_IMAGE: str = "mainWindow_WelcomeScreen_User_Profile_Image"
class SeedPhraseComponents(Enum):
IMPORT_A_SEED_TEXT: str = "import_a_seed_phrase_StatusBaseText"
@ -58,6 +61,20 @@ class PasswordStrengthPossibilities(Enum):
NUMBERS_SYMBOLS_LOWER_UPPER_GOOD = "numbers_symbols_lower_upper_good"
NUMBERS_SYMBOLS_LOWER_UPPER_GREAT = "numbers_symbols_lower_upper_great"
class MainScreen(Enum):
SETTINGS_BUTTON = "settings_navbar_settings_icon_StatusIcon"
class UserContextStatusMenu(Enum):
USER_CONTEXT_MENU_VIEW_MY_PROFILE_BUTTON: str = "userContextMenu_ViewMyProfile_Action"
class MyProfileModal(Enum):
MY_PROFILE_MODAL_USER_IMAGE: str = "myProfileModal_UserImage"
class LoginView(Enum):
LOGIN_VIEW_USER_IMAGE: str = "loginView_userImage"
PASSWORD_INPUT = "loginView_passwordInput"
SUBMIT_BTN = "loginView_submitBtn"
class StatusWelcomeScreen:
def __init__(self):
@ -99,6 +116,7 @@ class StatusWelcomeScreen:
def input_username(self, username: str):
type(SignUpComponents.USERNAME_INPUT.value, username)
click_obj_by_name(SignUpComponents.DETAILS_NEXT_BUTTON.value)
# There is another page with the same Next button
click_obj_by_name(SignUpComponents.DETAILS_NEXT_BUTTON.value)
@ -152,4 +170,60 @@ class StatusWelcomeScreen:
verify_screenshot("VP-PWStrength-numbers_symbols_lower_upper_great")
# TODO: Get screenshots in Linux
def input_profile_image(self, profileImageUrl: str):
workflow = get_obj(SignUpComponents.PROFILE_IMAGE_CROP_WORKFLOW_ITEM.value)
workflow.cropImage(profileImageUrl)
click_obj_by_name(SignUpComponents.PROFILE_IMAGE_CROPPER_ACCEPT_BUTTON.value)
def input_username_and_grab_profile_image_sreenshot(self, username: str):
type(SignUpComponents.USERNAME_INPUT.value, username)
click_obj_by_name(SignUpComponents.DETAILS_NEXT_BUTTON.value)
# take a screenshot of the profile image to compare it later with the main screen
profileIcon = wait_and_get_obj(SignUpComponents.WELCOME_SCREEN_USER_PROFILE_IMAGE.value)
grabScreenshot_and_save(profileIcon, "profiletestimage", 200)
# There is another page with the same Next button
click_obj_by_name(SignUpComponents.DETAILS_NEXT_BUTTON.value)
def input_username_profileImage_password_and_finalize_sign_up(self, profileImageUrl: str, username: str, password: str):
self.input_profile_image(profileImageUrl)
self.input_username_and_grab_profile_image_sreenshot(username)
self.input_password(password)
self.input_confirmation_password(password)
if sys.platform == "darwin":
click_obj_by_name(SignUpComponents.PASSWORD_PREFERENCE.value)
def profile_modal_image_is_updated(self):
click_obj_by_name(MainScreenComponents.PROFILE_NAVBAR_BUTTON.value)
click_obj_by_name(UserContextStatusMenu.USER_CONTEXT_MENU_VIEW_MY_PROFILE_BUTTON.value)
imagePresent("profiletestimage", True, 97, 95, 100, True)
def profile_settings_image_is_updated(self):
# first time clicking on settings button closes the my profile modal
click_obj_by_name(MainScreen.SETTINGS_BUTTON.value)
click_obj_by_name(MainScreen.SETTINGS_BUTTON.value)
imagePresent("profiletestimage", True, 97, 100, 183, True)
def grab_screenshot(self):
# take a screenshot of the profile image to compare it later with the main screen
loginUserName = wait_and_get_obj(LoginView.LOGIN_VIEW_USER_IMAGE.value)
grabScreenshot_and_save(loginUserName, "loginUserName", 200)
def profile_image_is_updated(self):
profileNavBarButton = wait_and_get_obj(MainScreenComponents.PROFILE_NAVBAR_BUTTON.value)
imagePresent("loginUserName", True, 98, 85, 100, True)
click_obj(profileNavBarButton)
imagePresent("loginUserName", True, 99, 95, 100, True)
def enter_password(self, password):
click_obj_by_name(LoginView.PASSWORD_INPUT.value)
type(LoginView.PASSWORD_INPUT.value, password)
click_obj_by_name(LoginView.SUBMIT_BTN.value)

View File

@ -1,9 +1,11 @@
from remotesystem import RemoteSystem
from drivers.SquishDriverVerification import *
import os
import os.path as path
import shutil
import distutils.dir_util
def erase_directory(dir: str):
directory = path.abspath(path.join(__file__, dir))
if (os.path.isdir(directory)):
@ -27,3 +29,14 @@ def copy_directory(src: str, dst: str):
distutils.dir_util.copy_tree(src, dst)
except OSError:
os.remove(dst)
def delete_created_searchImage(fileName: str):
try:
remoteOS = RemoteSystem()
verify(remoteOS.deleteFile(fileName), "screenshot file was not deleted" + fileName)
verify(not remoteOS.exists(fileName), "screenshot file was not deleted" + fileName)
except Exception as e:
verify_failure("RemoteSystem error" + str(e))

View File

@ -17,6 +17,8 @@ def hook(context):
context.userData = {}
context.userData["aut_name"] = _status_desktop_app_name
context.userData["status_data_folder_path"] = _status_data_folder_path
context.userData["fixtures_root"] = os.path.join(os.path.dirname(__file__), "../../../fixtures/")
context.userData["search_images"] = os.path.join(os.path.dirname(__file__), "../shared/searchImages/")
@OnScenarioEnd
def hook(context):

View File

@ -9,6 +9,10 @@ statusDesktop_mainWindow_AppMain_EmojiPopup_SearchTextInput = {"container": stat
mainWindow_ScrollView = {"container": statusDesktop_mainWindow, "type": "StatusScrollView", "unnamed": 1, "visible": True}
mainWindow_ScrollView_2 = {"container": statusDesktop_mainWindow, "occurrence": 2, "type": "StatusScrollView", "unnamed": 1, "visible": True}
mainWindow_ProfileNavBarButton = {"container": statusDesktop_mainWindow, "objectName": "statusProfileNavBarTabButton", "type": "StatusNavBarTabButton", "visible": True}
settings_navbar_settings_icon_StatusIcon = {"container": mainWindow_navBarListView_ListView, "objectName": "settings-icon", "type": "StatusIcon", "visible": True}
# User Status Profile Menu
userContextMenu_ViewMyProfile_Action = {"container": statusDesktop_mainWindow_overlay, "objectName": "userStatusViewMyProfileAction", "type": "StatusMenuItemDelegate", "visible": True}
# popups
close_popup_StatusFlatRoundButton = {"container": statusDesktop_mainWindow_overlay, "id": "closeButton", "type": "StatusFlatRoundButton", "unnamed": 1, "visible": True}
@ -18,4 +22,3 @@ mainWindow_public_chat_icon_StatusIcon = {"container": statusDesktop_mainWindow,
chatList_Repeater = {"container": statusDesktop_mainWindow, "objectName": "chatListItems", "type": "Repeater"}
mainWindow_startChat = {"checkable": True, "container": statusDesktop_mainWindow, "objectName": "startChatButton", "type": "StatusIconTabButton"}
join_public_chat_StatusMenuItemDelegate = {"checkable": False, "container": statusDesktop_mainWindow_overlay, "enabled": True, "text": "Join public chat", "type": "StatusMenuItemDelegate", "unnamed": 1, "visible": True}
mainWindow_statusIcon_StatusIcon_2 = {"container": statusDesktop_mainWindow, "id": "statusIcon", "source": "qrc:/StatusQ/src/assets/img/icons/public-chat.svg", "type": "StatusIcon", "unnamed": 1, "visible": True}

View File

@ -7,3 +7,4 @@ loginView_submitBtn = {"container": statusDesktop_mainWindow, "type": "StatusRou
loginView_main = {"container": statusDesktop_mainWindow, "type": "LoginView", "visible": True}
loginView_errMsgLabel = {"container": statusDesktop_mainWindow, "objectName": "loginPassworkInputValidationErrorText", "type": "StatusBaseText", "visible": True}
accountsView_accountListPanel = {"container": statusDesktop_mainWindow, "type": "ListView", "visible": True} # This probably is missing an objectName
loginView_userImage = {"container": statusDesktop_mainWindow, "objectName": "loginViewUserImage", "type": "UserImage", "visible": True}

View File

@ -17,6 +17,9 @@ onboarding_DiplayName_Input = {"container": statusDesktop_mainWindow, "objectNam
onboarding_DetailsView_NextButton = {"container": statusDesktop_mainWindow, "objectName": "onboardingDetailsViewNextButton", "type": "StatusButton"}
mainWindow_I_prefer_to_use_my_password_StatusBaseText = {"container": statusDesktop_mainWindow, "objectName": "touchIdIPreferToUseMyPasswordText", "type": "StatusBaseText"}
mainWindow_Ok_got_it_StatusBaseText = {"container": statusDesktop_mainWindow, "type": "StatusButton", "objectName": "allowNotificationsOnboardingOkButton", "visible": True}
mainWindow_WelcomeScreen_User_Profile_Image = {"container": statusDesktop_mainWindow, "type": "StatusSmartIdenticon", "objectName": "welcomeScreenUserProfileImage"}
mainWindow_WelcomeScreen_Image_Crop_Workflow_Item= {"container": statusDesktop_mainWindow, "type": "Item", "objectName": "imageCropWorkflow"}
mainWindow_WelcomeScreen_Image_Cropper_Accept_Button= {"container": statusDesktop_mainWindow, "type": "StatusButton", "objectName": "imageCropperAcceptButton"}
# Seed phrase form:
import_a_seed_phrase_StatusBaseText = {"container": statusDesktop_mainWindow, "text": "Import a seed phrase", "type": "StatusBaseText", "unnamed": 1, "visible": True}

View File

@ -3,3 +3,4 @@
from objectmaphelper import *
from scripts.onboarding_names import *
from scripts.login_names import *

View File

@ -44,6 +44,33 @@ def step(context, seed_phrase):
def step(context):
_welcomeScreen.seed_phrase_visible()
@When("the user logs in with password |any|")
def step(context, password: str):
_welcomeScreen.enter_password(password)
@Then("the user is online")
def step(context):
_mainScreen.user_is_online()
@When("the user signs up with profileImage |any|, username |any| and password |any|")
def step(context, profileImageUrl, username, password):
_welcomeScreen.input_username_profileImage_password_and_finalize_sign_up("file:///"+context.userData["fixtures_root"]+"images/"+profileImageUrl, username, password)
@Then("my profile modal has the updated profile image")
def step(context):
_welcomeScreen.profile_modal_image_is_updated()
@Then("the profile setting has the updated profile image")
def step(context):
_welcomeScreen.profile_settings_image_is_updated()
@When("a screenshot of the profileImage is taken")
def step(context):
_welcomeScreen.grab_screenshot()
@Then("the profile navigation bar has the updated profile image")
def step(context):
_welcomeScreen.profile_image_is_updated()
delete_created_searchImage(context.userData["search_images"] +"profiletestimage.png")
delete_created_searchImage(context.userData["search_images"]+"loginUserName.png")

View File

@ -134,4 +134,15 @@ Feature: Status Desktop Sign Up
Scenario: After Signing up the Profile state should be online
Given A first time user lands on the status desktop and generates new key
When user signs up with username tester123 and password TesTEr16843/!@00
Then the user is online
Then the user is online
Scenario: User signs up with a profile image
Given A first time user lands on the status desktop and generates new key
When the user signs up with profileImage doggo.jpeg, username tester123 and password TesTEr16843/!@00
Then my profile modal has the updated profile image
And the profile setting has the updated profile image
When the user restarts the app
And a screenshot of the profileImage is taken
And the user logs in with password TesTEr16843/!@00
Then the profile navigation bar has the updated profile image

View File

@ -8,7 +8,7 @@ from scripts.onboarding_names import *
from sections.chat_names import *
from sections.community_names import *
from sections.community_portal_names import *
from sections.login_names import *
from scripts.login_names import *
from sections.search_names import *
from sections.settings_names import *
from sections.wallet_names import *

View File

@ -28,7 +28,6 @@ class SettingsSubsection(Enum):
# Main:
navBarListView_Settings_navbar_StatusNavBarTabButton = {"checkable": True, "container": mainWindow_navBarListView_ListView, "objectName": "Settings-navbar", "type": "StatusNavBarTabButton", "visible": True}
settings_navbar_settings_icon_StatusIcon = {"container": navBarListView_Settings_navbar_StatusNavBarTabButton, "objectName": "settings-icon", "type": "StatusIcon", "visible": True}
settingsSave_StatusButton = {"container": statusDesktop_mainWindow, "objectName": "settingsDirtyToastMessageSaveButton", "type": "StatusButton", "visible": True}
settings_Sidebar_ENS_Item = {"container": mainWindow_ScrollView, "objectName": "ENS usernames-MainMenu", "type": "StatusNavigationListItem"}
@ -101,6 +100,9 @@ languageView_language_StatusPickerButton = {"container": languageView_language_S
languageView_language_ListView = {"container": languageView_language_StatusListPicker, "type": "ListView", "unnamed": 1}
languageView_language_StatusInput = {"container": languageView_language_ListView, "type": "StatusInput", "unnamed": 1}
# My Profile Modal
myProfileModal_UserImage = {"container": statusDesktop_mainWindow_overlay, "objectName": "myProfileModalUserImage", "type": "UserImage", "visible": True}
# Backup seed phrase:
backup_seed_phrase_popup_Acknowledgements_havePen_checkbox = {"container": statusDesktop_mainWindow_overlay, "objectName": "Acknowledgements_havePen", "type": "StatusCheckBox", "checkable": True, "visible": True}
backup_seed_phrase_popup_Acknowledgements_writeDown_checkbox = {"container": statusDesktop_mainWindow_overlay, "objectName": "Acknowledgements_writeDown", "type": "StatusCheckBox", "checkable": True, "visible": True}

View File

@ -88,6 +88,7 @@ Item {
StatusSmartIdenticon {
anchors.left: parent.left
id: userImage
objectName: "welcomeScreenUserProfileImage"
image {
width: 86
height: 86

View File

@ -188,6 +188,7 @@ Item {
UserImage {
id: userImage
objectName: "loginViewUserImage"
image: root.startupStore.selectedLoginAccount.thumbnailImage
name: root.startupStore.selectedLoginAccount.username
colorId: root.startupStore.selectedLoginAccount.colorId

View File

@ -1,115 +0,0 @@
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]
}
}
}
}
}

View File

@ -1,2 +1 @@
ChangeProfilePicModal 1.0 ChangeProfilePicModal.qml
BackupSeedModal 1.0 BackupSeedModal.qml

View File

@ -1,213 +0,0 @@
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()
}
}
}

View File

@ -72,6 +72,7 @@ Item {
UserImage {
id: userImage
objectName: "myProfileModalUserImage"
name: root.displayName
pubkey: root.pubkey
image: root.icon

View File

@ -8,7 +8,6 @@ FormGroup 1.0 FormGroup.qml
GasSelector 1.0 GasSelector.qml
GasSelectorButton 1.0 GasSelectorButton.qml
GasValidator 1.0 GasValidator.qml
ImageCropper 1.0 ImageCropper.qml
Input 1.0 Input.qml
RadioButtonSelector 1.0 RadioButtonSelector.qml
RecipientSelector 1.0 RecipientSelector.qml

View File

@ -1,29 +0,0 @@
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()
}
}

View File

@ -1,7 +1,6 @@
Address 1.0 Address.qml
AddressRequiredValidator 1.0 AddressRequiredValidator.qml
BalanceValidator 1.0 BalanceValidator.qml
CropCornerRectangle 1.0 CropCornerRectangle.qml
GlossaryEntry 1.0 GlossaryEntry.qml
GlossaryLetter 1.0 GlossaryLetter.qml
ImageLoader 1.0 ImageLoader.qml

View File

@ -12,6 +12,7 @@ import utils 1.0
Item {
id: root
objectName: "imageCropWorkflow"
property alias aspectRatio: imageCropper.aspectRatio
property alias windowStyle: imageCropper.windowStyle
@ -26,6 +27,11 @@ Item {
fileDialog.open()
}
function cropImage(imageUrl) {
imageCropper.source = imageUrl
imageCropperModal.open()
}
FileDialog {
id: fileDialog
@ -34,8 +40,7 @@ Item {
nameFilters: [qsTr("Supported image formats (%1)").arg("*.jpg *.jpeg *.jfif *.webp *.png *.heif")]
onAccepted: {
if (fileDialog.fileUrls.length > 0) {
imageCropper.source = fileDialog.fileUrls[0]
imageCropperModal.open()
cropImage(fileDialog.fileUrls[0])
}
}
} // FileDialog
@ -51,15 +56,16 @@ Item {
StatusImageCropPanel {
id: imageCropper
objectName: "profileImageCropper"
implicitHeight: root.roundedImage ? 350 : 370
anchors {
fill: parent
leftMargin: Style.current.bigPadding + Style.current.halfPadding / 2
rightMargin: Style.current.bigPadding + Style.current.halfPadding / 2
topMargin: Style.current.bigPadding
bottomMargin: Style.current.bigPadding
fill: parent
leftMargin: Style.current.bigPadding + Style.current.halfPadding / 2
rightMargin: Style.current.bigPadding + Style.current.halfPadding / 2
topMargin: Style.current.bigPadding
bottomMargin: Style.current.bigPadding
}
margins: root.roundedImage ? 10 : 20
@ -69,6 +75,7 @@ Item {
rightButtons: [
StatusButton {
objectName: "imageCropperAcceptButton"
text: root.acceptButtonText
enabled: imageCropper.sourceSize.width > 0 && imageCropper.sourceSize.height > 0

View File

@ -1,88 +0,0 @@
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()
}
}
}

View File

@ -29,6 +29,7 @@ StatusPopupMenu {
}
StatusMenuItem {
objectName: "userStatusViewMyProfileAction"
text: qsTr("View My Profile")
icon.name: "profile"
onTriggered: {

View File

@ -6,7 +6,6 @@ CommunityIntroDialog 1.0 CommunityIntroDialog.qml
ContactVerificationRequestPopup 1.0 ContactVerificationRequestPopup.qml
DownloadModal 1.0 DownloadModal.qml
DownloadPage 1.0 DownloadPage.qml
ImageCropperModal 1.0 ImageCropperModal.qml
InviteFriendsPopup 1.0 InviteFriendsPopup.qml
NicknamePopup 1.0 NicknamePopup.qml
ModalPopup 1.0 ModalPopup.qml