diff --git a/src/app/modules/startup/module.nim b/src/app/modules/startup/module.nim index 3aa337a8c5f..4acf827ce78 100644 --- a/src/app/modules/startup/module.nim +++ b/src/app/modules/startup/module.nim @@ -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)) \ No newline at end of file + self.view.setCurrentStartupState(newWelcomeState(FlowType.General, nil)) diff --git a/test/ui-test/fixtures/images/doggo.jpeg b/test/ui-test/fixtures/images/doggo.jpeg new file mode 100644 index 00000000000..a6da3c26918 Binary files /dev/null and b/test/ui-test/fixtures/images/doggo.jpeg differ diff --git a/test/ui-test/src/drivers/SquishDriver.py b/test/ui-test/src/drivers/SquishDriver.py index 4f191ec65a8..236b02ed5e7 100755 --- a/test/ui-test/src/drivers/SquishDriver.py +++ b/test/ui-test/src/drivers/SquishDriver.py @@ -28,6 +28,7 @@ # 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") diff --git a/test/ui-test/src/drivers/SquishDriverVerification.py b/test/ui-test/src/drivers/SquishDriverVerification.py index f4055aa0ef7..04aa80f9673 100644 --- a/test/ui-test/src/drivers/SquishDriverVerification.py +++ b/test/ui-test/src/drivers/SquishDriverVerification.py @@ -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}) diff --git a/test/ui-test/src/screens/StatusMainScreen.py b/test/ui-test/src/screens/StatusMainScreen.py index 711d84b53ae..49df5e5ddc8 100644 --- a/test/ui-test/src/screens/StatusMainScreen.py +++ b/test/ui-test/src/screens/StatusMainScreen.py @@ -16,12 +16,11 @@ 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" diff --git a/test/ui-test/src/screens/StatusWelcomeScreen.py b/test/ui-test/src/screens/StatusWelcomeScreen.py index 1be99849158..21bf0901842 100644 --- a/test/ui-test/src/screens/StatusWelcomeScreen.py +++ b/test/ui-test/src/screens/StatusWelcomeScreen.py @@ -14,6 +14,7 @@ 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 @@ def input_username_and_password_and_finalize_sign_up(self, username: str, passwo 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 @@ def validate_password_strength(self, strength: str): 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) diff --git a/test/ui-test/src/utils/FileManager.py b/test/ui-test/src/utils/FileManager.py index 3f78232b173..606c877bc87 100644 --- a/test/ui-test/src/utils/FileManager.py +++ b/test/ui-test/src/utils/FileManager.py @@ -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)) + + + diff --git a/test/ui-test/testSuites/global_shared/scripts/bdd_hooks.py b/test/ui-test/testSuites/global_shared/scripts/bdd_hooks.py index 39efccf1f7d..5bd87368b4c 100644 --- a/test/ui-test/testSuites/global_shared/scripts/bdd_hooks.py +++ b/test/ui-test/testSuites/global_shared/scripts/bdd_hooks.py @@ -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): diff --git a/test/ui-test/testSuites/global_shared/scripts/global_names.py b/test/ui-test/testSuites/global_shared/scripts/global_names.py index f289763876c..bb07732eb86 100644 --- a/test/ui-test/testSuites/global_shared/scripts/global_names.py +++ b/test/ui-test/testSuites/global_shared/scripts/global_names.py @@ -9,6 +9,10 @@ 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 @@ 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} \ No newline at end of file diff --git a/test/ui-test/testSuites/suite_status/shared/scripts/sections/login_names.py b/test/ui-test/testSuites/global_shared/scripts/login_names.py similarity index 85% rename from test/ui-test/testSuites/suite_status/shared/scripts/sections/login_names.py rename to test/ui-test/testSuites/global_shared/scripts/login_names.py index 64e501b7449..00a8110ebe4 100644 --- a/test/ui-test/testSuites/suite_status/shared/scripts/sections/login_names.py +++ b/test/ui-test/testSuites/global_shared/scripts/login_names.py @@ -7,3 +7,4 @@ 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} diff --git a/test/ui-test/testSuites/global_shared/scripts/onboarding_names.py b/test/ui-test/testSuites/global_shared/scripts/onboarding_names.py index 98f084d939f..f29293bbe64 100644 --- a/test/ui-test/testSuites/global_shared/scripts/onboarding_names.py +++ b/test/ui-test/testSuites/global_shared/scripts/onboarding_names.py @@ -17,6 +17,9 @@ 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} diff --git a/test/ui-test/testSuites/suite_onboarding/shared/scripts/names.py b/test/ui-test/testSuites/suite_onboarding/shared/scripts/names.py index 7ca976cf202..c9a9d23286a 100644 --- a/test/ui-test/testSuites/suite_onboarding/shared/scripts/names.py +++ b/test/ui-test/testSuites/suite_onboarding/shared/scripts/names.py @@ -3,3 +3,4 @@ from objectmaphelper import * from scripts.onboarding_names import * +from scripts.login_names import * diff --git a/test/ui-test/testSuites/suite_onboarding/shared/searchImages/.gitkeep b/test/ui-test/testSuites/suite_onboarding/shared/searchImages/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/ui-test/testSuites/suite_onboarding/shared/steps/signUpSteps.py b/test/ui-test/testSuites/suite_onboarding/shared/steps/signUpSteps.py index ab9edca6888..7889dec4abc 100644 --- a/test/ui-test/testSuites/suite_onboarding/shared/steps/signUpSteps.py +++ b/test/ui-test/testSuites/suite_onboarding/shared/steps/signUpSteps.py @@ -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") + diff --git a/test/ui-test/testSuites/suite_onboarding/tst_statusSignUp/test.feature b/test/ui-test/testSuites/suite_onboarding/tst_statusSignUp/test.feature index 8e98f1152e6..563d3f787bd 100644 --- a/test/ui-test/testSuites/suite_onboarding/tst_statusSignUp/test.feature +++ b/test/ui-test/testSuites/suite_onboarding/tst_statusSignUp/test.feature @@ -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 \ No newline at end of file + 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 diff --git a/test/ui-test/testSuites/suite_status/shared/scripts/names.py b/test/ui-test/testSuites/suite_status/shared/scripts/names.py index e18f359b331..0dd640e95e8 100644 --- a/test/ui-test/testSuites/suite_status/shared/scripts/names.py +++ b/test/ui-test/testSuites/suite_status/shared/scripts/names.py @@ -8,7 +8,7 @@ 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 * diff --git a/test/ui-test/testSuites/suite_status/shared/scripts/sections/settings_names.py b/test/ui-test/testSuites/suite_status/shared/scripts/sections/settings_names.py index 6d099e69d2a..8107674db4f 100644 --- a/test/ui-test/testSuites/suite_status/shared/scripts/sections/settings_names.py +++ b/test/ui-test/testSuites/suite_status/shared/scripts/sections/settings_names.py @@ -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 @@ class SettingsSubsection(Enum): 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} diff --git a/ui/app/AppLayouts/Onboarding/views/InsertDetailsView.qml b/ui/app/AppLayouts/Onboarding/views/InsertDetailsView.qml index bbbd04f65f0..9997b46b385 100644 --- a/ui/app/AppLayouts/Onboarding/views/InsertDetailsView.qml +++ b/ui/app/AppLayouts/Onboarding/views/InsertDetailsView.qml @@ -88,6 +88,7 @@ Item { StatusSmartIdenticon { anchors.left: parent.left id: userImage + objectName: "welcomeScreenUserProfileImage" image { width: 86 height: 86 diff --git a/ui/app/AppLayouts/Onboarding/views/LoginView.qml b/ui/app/AppLayouts/Onboarding/views/LoginView.qml index 77668c012cf..eb8761f657e 100644 --- a/ui/app/AppLayouts/Onboarding/views/LoginView.qml +++ b/ui/app/AppLayouts/Onboarding/views/LoginView.qml @@ -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 diff --git a/ui/app/AppLayouts/Profile/popups/ChangeProfilePicModal.qml b/ui/app/AppLayouts/Profile/popups/ChangeProfilePicModal.qml deleted file mode 100644 index fd895a666f5..00000000000 --- a/ui/app/AppLayouts/Profile/popups/ChangeProfilePicModal.qml +++ /dev/null @@ -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] - } - } - } - } -} - diff --git a/ui/app/AppLayouts/Profile/popups/qmldir b/ui/app/AppLayouts/Profile/popups/qmldir index dc6b65595a8..a341a4d3ac6 100644 --- a/ui/app/AppLayouts/Profile/popups/qmldir +++ b/ui/app/AppLayouts/Profile/popups/qmldir @@ -1,2 +1 @@ -ChangeProfilePicModal 1.0 ChangeProfilePicModal.qml BackupSeedModal 1.0 BackupSeedModal.qml diff --git a/ui/imports/shared/controls/ImageCropper.qml b/ui/imports/shared/controls/ImageCropper.qml deleted file mode 100644 index 4c57c6d5e38..00000000000 --- a/ui/imports/shared/controls/ImageCropper.qml +++ /dev/null @@ -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() - } - } -} diff --git a/ui/imports/shared/controls/chat/ProfileHeader.qml b/ui/imports/shared/controls/chat/ProfileHeader.qml index 429135de0c3..0388fc4e467 100644 --- a/ui/imports/shared/controls/chat/ProfileHeader.qml +++ b/ui/imports/shared/controls/chat/ProfileHeader.qml @@ -72,6 +72,7 @@ Item { UserImage { id: userImage + objectName: "myProfileModalUserImage" name: root.displayName pubkey: root.pubkey image: root.icon diff --git a/ui/imports/shared/controls/qmldir b/ui/imports/shared/controls/qmldir index 8ae2b0199a8..e89bd65eb86 100644 --- a/ui/imports/shared/controls/qmldir +++ b/ui/imports/shared/controls/qmldir @@ -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 diff --git a/ui/imports/shared/panels/CropCornerRectangle.qml b/ui/imports/shared/panels/CropCornerRectangle.qml deleted file mode 100644 index ca38e4ec2bf..00000000000 --- a/ui/imports/shared/panels/CropCornerRectangle.qml +++ /dev/null @@ -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() - } -} diff --git a/ui/imports/shared/panels/qmldir b/ui/imports/shared/panels/qmldir index 96521389e8c..c02304ed1d0 100644 --- a/ui/imports/shared/panels/qmldir +++ b/ui/imports/shared/panels/qmldir @@ -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 diff --git a/ui/imports/shared/popups/ImageCropWorkflow.qml b/ui/imports/shared/popups/ImageCropWorkflow.qml index fc93586f4c7..cce773e5e83 100644 --- a/ui/imports/shared/popups/ImageCropWorkflow.qml +++ b/ui/imports/shared/popups/ImageCropWorkflow.qml @@ -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 diff --git a/ui/imports/shared/popups/ImageCropperModal.qml b/ui/imports/shared/popups/ImageCropperModal.qml deleted file mode 100644 index 429a7e42eda..00000000000 --- a/ui/imports/shared/popups/ImageCropperModal.qml +++ /dev/null @@ -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() - } - } -} diff --git a/ui/imports/shared/popups/UserStatusContextMenu.qml b/ui/imports/shared/popups/UserStatusContextMenu.qml index 3c6153c63b8..c34e8091f27 100644 --- a/ui/imports/shared/popups/UserStatusContextMenu.qml +++ b/ui/imports/shared/popups/UserStatusContextMenu.qml @@ -29,6 +29,7 @@ StatusPopupMenu { } StatusMenuItem { + objectName: "userStatusViewMyProfileAction" text: qsTr("View My Profile") icon.name: "profile" onTriggered: { diff --git a/ui/imports/shared/popups/qmldir b/ui/imports/shared/popups/qmldir index a3ed9ea5063..42b0b008149 100644 --- a/ui/imports/shared/popups/qmldir +++ b/ui/imports/shared/popups/qmldir @@ -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