diff --git a/storybook/pages/AboutViewPage.qml b/storybook/pages/AboutViewPage.qml index 81a74e2b60..9b312de4eb 100644 --- a/storybook/pages/AboutViewPage.qml +++ b/storybook/pages/AboutViewPage.qml @@ -21,6 +21,8 @@ SplitView { contentWidth: parent.width store: QtObject { + readonly property bool isProduction: false + function checkForUpdates() { logs.logEvent("store::checkForUpdates") } diff --git a/storybook/pages/CommunitiesPortalLayoutPage.qml b/storybook/pages/CommunitiesPortalLayoutPage.qml index 651db8f258..d9e7704b95 100644 --- a/storybook/pages/CommunitiesPortalLayoutPage.qml +++ b/storybook/pages/CommunitiesPortalLayoutPage.qml @@ -10,10 +10,17 @@ import Storybook 1.0 import Models 1.0 import utils 1.0 +import mainui 1.0 SplitView { + id: root Logs { id: logs } + Popups { + popupParent: root + rootStore: QtObject {} + } + SplitView { orientation: Qt.Vertical SplitView.fillWidth: true @@ -40,16 +47,6 @@ SplitView { } } - // TODO: onCompleted handler and localAccountSensitiveSettings are here to allow opening - // "Import Community" and "Create New Community" popups. However those popups shouldn't - // be tightly coupled with `CommunitiesPortalLayout` so it should be refactored in the next step. - // Pressing buttons "Import using key" and "Create new community" should only request for opening - // dialogs, and in Storybook it should be logged in the same way as calls to stores. - // Mentioned popups should have their own pages in the Storybook. - Component.onCompleted: { - Global.appMain = this - } - QtObject { id: localAccountSensitiveSettings readonly property bool isDiscordImportToolEnabled: false diff --git a/storybook/pages/ProfileDialogViewPage.qml b/storybook/pages/ProfileDialogViewPage.qml index 9deb788c79..dee299078a 100644 --- a/storybook/pages/ProfileDialogViewPage.qml +++ b/storybook/pages/ProfileDialogViewPage.qml @@ -6,6 +6,9 @@ import Storybook 1.0 import utils 1.0 import shared.views 1.0 +import mainui 1.0 + +import StatusQ 0.1 SplitView { id: root @@ -91,6 +94,11 @@ SplitView { Logs { id: logs } + Popups { + popupParent: root + rootStore: QtObject {} + } + SplitView { orientation: Qt.Vertical SplitView.fillWidth: true @@ -114,10 +122,6 @@ SplitView { publicKey: switchOwnProfile.checked ? "0xdeadbeef" : "0xrandomguy" - Component.onCompleted: { - Global.appMain = root // FIXME this is here for the popups to work - } - profileStore: QtObject { readonly property string pubkey: "0xdeadbeef" readonly property string ensName: name.text @@ -286,7 +290,11 @@ SplitView { TextField { Layout.fillWidth: true id: bio - text: "Hello from MockMainModule, I am a mock user and this is my bio." + text: "Hi, I am Alex. I'm an indie developer who mainly works on web products. + +I worked for several different companies and created a couple of my own products from scratch. Currently building Telescope and Prepacked. + +Say hi, or find me on Twitter, GitHub, or Mastodon." } } } diff --git a/test/ui-test/testSuites/suite_settings/tst_userIdentity/test.feature b/test/ui-test/testSuites/suite_settings/tst_userIdentity/test.feature index 24803ab0b6..7d16671351 100644 --- a/test/ui-test/testSuites/suite_settings/tst_userIdentity/test.feature +++ b/test/ui-test/testSuites/suite_settings/tst_userIdentity/test.feature @@ -9,8 +9,6 @@ Feature: User Identity And the user signs up with username "tester123" and password "TesTEr16843/!@00" And the user lands on the signed in app - @mayfail - # FIXME test is broken on step `the user's social links are empty`. Issue #9499 Scenario Outline: The user sets display name, bio and social links Given the user opens app settings screen And the user opens the profile settings diff --git a/ui/StatusQ/src/StatusQ/Controls/Validators/StatusUrlValidator.qml b/ui/StatusQ/src/StatusQ/Controls/Validators/StatusUrlValidator.qml index 896c1c2aba..0fb77072bc 100644 --- a/ui/StatusQ/src/StatusQ/Controls/Validators/StatusUrlValidator.qml +++ b/ui/StatusQ/src/StatusQ/Controls/Validators/StatusUrlValidator.qml @@ -2,7 +2,6 @@ import StatusQ.Core.Utils 0.1 import StatusQ.Controls 0.1 StatusValidator { - name: "url" errorMessage: qsTr("Please enter a valid URL") @@ -11,5 +10,3 @@ StatusValidator { return Utils.isURL(value); } } - - diff --git a/ui/StatusQ/src/StatusQ/Controls/Validators/StatusValidator.qml b/ui/StatusQ/src/StatusQ/Controls/Validators/StatusValidator.qml index 549a882951..fc8dc1c5ce 100644 --- a/ui/StatusQ/src/StatusQ/Controls/Validators/StatusValidator.qml +++ b/ui/StatusQ/src/StatusQ/Controls/Validators/StatusValidator.qml @@ -1,9 +1,6 @@ -import QtQuick 2.13 -import StatusQ.Controls 0.1 +import QtQuick 2.14 QtObject { - id: statusValidator - property string name: "" property string errorMessage: qsTr("invalid input") property var validatorObj diff --git a/ui/StatusQ/src/typesregistration.cpp b/ui/StatusQ/src/typesregistration.cpp index 50fbaee8ff..6c9a2e8352 100644 --- a/ui/StatusQ/src/typesregistration.cpp +++ b/ui/StatusQ/src/typesregistration.cpp @@ -3,6 +3,7 @@ #include "StatusQ/QClipboardProxy.h" #include "StatusQ/statussyntaxhighlighter.h" #include "StatusQ/statuswindow.h" +#include "StatusQ/rxvalidator.h" #include @@ -12,4 +13,5 @@ void registerStatusQTypes() qmlRegisterSingletonType("StatusQ", 0 , 1, "QClipboardProxy", &QClipboardProxy::qmlInstance); qmlRegisterType("StatusQ", 0 , 1, "StatusSyntaxHighlighter"); + qmlRegisterType("StatusQ", 0 , 1, "RXValidator"); } diff --git a/ui/app/AppLayouts/Browser/popups/AddFavoriteModal.qml b/ui/app/AppLayouts/Browser/popups/AddFavoriteModal.qml index bfe31d2d44..af3c03d69d 100644 --- a/ui/app/AppLayouts/Browser/popups/AddFavoriteModal.qml +++ b/ui/app/AppLayouts/Browser/popups/AddFavoriteModal.qml @@ -79,8 +79,6 @@ ModalPopup { id: urlInput anchors.left: parent.left anchors.right: parent.right - leftPadding: 0 - rightPadding: 0 label: qsTr("URL") input.text: ogUrl placeholderText: qsTr("Paste URL") diff --git a/ui/app/AppLayouts/Chat/ChatLayout.qml b/ui/app/AppLayouts/Chat/ChatLayout.qml index 9ea81bd79a..abbdd2048e 100644 --- a/ui/app/AppLayouts/Chat/ChatLayout.qml +++ b/ui/app/AppLayouts/Chat/ChatLayout.qml @@ -61,16 +61,12 @@ StackLayout { hasAddedContacts: root.contactsStore.myContactsModel.count > 0 chatCommunitySectionModule: root.rootStore.chatCommunitySectionModule community: root.rootStore.mainModuleInst ? root.rootStore.mainModuleInst.activeSection - || {} : {} + || ({}) : ({}) - onBackToCommunityClicked: root.currentIndex = 0 + onBackToCommunityClicked: root.currentIndex = 0 - // TODO: remove me when migration to new settings is done - onOpenLegacyPopupClicked: Global.openPopup(Global.communityProfilePopup, { - "store": root.rootStore, - "community": community, - "communitySectionModule": chatCommunitySectionModule - }) + // TODO: remove me when migration to new settings is done + onOpenLegacyPopupClicked: Global.openCommunityProfilePopupRequested(root.rootStore, community, chatCommunitySectionModule) } } } diff --git a/ui/app/AppLayouts/Chat/popups/ChatCommandModal.qml b/ui/app/AppLayouts/Chat/popups/ChatCommandModal.qml index d6fd56b9da..a41f3fc22b 100644 --- a/ui/app/AppLayouts/Chat/popups/ChatCommandModal.qml +++ b/ui/app/AppLayouts/Chat/popups/ChatCommandModal.qml @@ -1,7 +1,6 @@ import QtQuick 2.13 import QtQuick.Controls 2.13 import QtQuick.Layouts 1.13 -import QtQuick.Dialogs 1.3 import utils 1.0 import shared.controls 1.0 @@ -18,8 +17,8 @@ StatusModal { property var store property var contactsStore - property string commandTitle: "Send" - property string finalButtonLabel: "Request address" + property string commandTitle: qsTr("Send") + property string finalButtonLabel: qsTr("Request address") property var sendChatCommand: function () {} property bool isRequested: false diff --git a/ui/app/AppLayouts/Chat/stores/RootStore.qml b/ui/app/AppLayouts/Chat/stores/RootStore.qml index aa07bfbae4..af28a30515 100644 --- a/ui/app/AppLayouts/Chat/stores/RootStore.qml +++ b/ui/app/AppLayouts/Chat/stores/RootStore.qml @@ -37,7 +37,7 @@ QtObject { // Contact requests related part property var contactRequestsModel: chatCommunitySectionModule.contactRequestsModel - property var loadingHistoryMessagesInProgress: chatCommunitySectionModule.loadingHistoryMessagesInProgress + property bool loadingHistoryMessagesInProgress: chatCommunitySectionModule.loadingHistoryMessagesInProgress property var advancedModule: profileSectionModule.advancedModule diff --git a/ui/app/AppLayouts/Chat/views/ChatContentView.qml b/ui/app/AppLayouts/Chat/views/ChatContentView.qml index 0fbcf8b24f..f3d24f2e84 100644 --- a/ui/app/AppLayouts/Chat/views/ChatContentView.qml +++ b/ui/app/AppLayouts/Chat/views/ChatContentView.qml @@ -99,12 +99,7 @@ ColumnLayout { console.warn("error on open pinned messages limit reached from message context menu - chat content module is not set") return } - Global.openPopup(Global.pinnedMessagesPopup, { - store: rootStore, - messageStore: messageStore, - pinnedMessagesModel: chatContentModule.pinnedMessagesModel, - messageToPin: messageId - }) + Global.openPinnedMessagesPopupRequested(rootStore, messageStore, chatContentModule.pinnedMessagesModel, messageId) } onToggleReaction: { @@ -243,8 +238,7 @@ ColumnLayout { chatInput.fileUrlsAndSources )) { - Global.sendMessageSound.stop(); - Qt.callLater(Global.sendMessageSound.play); + Global.playSendMessageSound() chatInput.textInput.clear(); chatInput.textInput.textFormat = TextEdit.PlainText; diff --git a/ui/app/AppLayouts/Chat/views/ChatHeaderContentView.qml b/ui/app/AppLayouts/Chat/views/ChatHeaderContentView.qml index 9cd1726cc9..f2fedb0f99 100644 --- a/ui/app/AppLayouts/Chat/views/ChatHeaderContentView.qml +++ b/ui/app/AppLayouts/Chat/views/ChatHeaderContentView.qml @@ -309,12 +309,7 @@ Item { console.warn("error on open pinned messages - chat content module is not set") return } - Global.openPopup(Global.pinnedMessagesPopup, { - store: rootStore, - messageStore: messageStore, - pinnedMessagesModel: chatContentModule.pinnedMessagesModel, - messageToPin: "" - }) + Global.openPinnedMessagesPopupRequested(rootStore, messageStore, chatContentModule.pinnedMessagesModel, "") } onUnmute: { if(!chatContentModule) { diff --git a/ui/app/AppLayouts/Profile/popups/SocialLinksModal.qml b/ui/app/AppLayouts/Profile/popups/SocialLinksModal.qml index 40afa9d3f1..db6ae40e2a 100644 --- a/ui/app/AppLayouts/Profile/popups/SocialLinksModal.qml +++ b/ui/app/AppLayouts/Profile/popups/SocialLinksModal.qml @@ -66,6 +66,7 @@ StatusDialog { StatusScrollView { id: scrollView + implicitHeight: contentHeight + topPadding + bottomPadding anchors.fill: parent padding: 0 diff --git a/ui/app/AppLayouts/Profile/stores/AdvancedStore.qml b/ui/app/AppLayouts/Profile/stores/AdvancedStore.qml index 7580360c7d..c503653534 100644 --- a/ui/app/AppLayouts/Profile/stores/AdvancedStore.qml +++ b/ui/app/AppLayouts/Profile/stores/AdvancedStore.qml @@ -13,7 +13,6 @@ QtObject { property bool isTelemetryEnabled: advancedModule? advancedModule.isTelemetryEnabled : false property bool isAutoMessageEnabled: advancedModule? advancedModule.isAutoMessageEnabled : false property bool isDebugEnabled: advancedModule? advancedModule.isDebugEnabled : false - property bool isCommunityHistoryArchiveSupportEnabled: advancedModule? advancedModule.isCommunityHistoryArchiveSupportEnabled : false property bool isWakuV2StoreEnabled: advancedModule ? advancedModule.isWakuV2StoreEnabled : false property var customNetworksModel: advancedModule? advancedModule.customNetworksModel : [] diff --git a/ui/app/AppLayouts/Profile/views/NotificationsView.qml b/ui/app/AppLayouts/Profile/views/NotificationsView.qml index f71ca94c49..27bd766f4a 100644 --- a/ui/app/AppLayouts/Profile/views/NotificationsView.qml +++ b/ui/app/AppLayouts/Profile/views/NotificationsView.qml @@ -471,8 +471,7 @@ SettingsContentBase { value = appSettings.volume volumeSlider.valueChanged.connect(() => { // play a sound preview, but not on startup - Global.notificationSound.stop() - Global.notificationSound.play() + Global.playNotificationSound() }); } } diff --git a/ui/app/mainui/AppMain.qml b/ui/app/mainui/AppMain.qml index bf0288d3e9..1e23d3dfb6 100644 --- a/ui/app/mainui/AppMain.qml +++ b/ui/app/mainui/AppMain.qml @@ -4,17 +4,14 @@ import QtQuick.Layouts 1.13 import QtMultimedia 5.13 import Qt.labs.qmlmodels 1.0 import Qt.labs.platform 1.1 -import Qt.labs.settings 1.0 import QtQml.Models 2.14 import AppLayouts.Wallet 1.0 import AppLayouts.Node 1.0 import AppLayouts.Browser 1.0 import AppLayouts.Chat 1.0 -import AppLayouts.Chat.popups 1.0 import AppLayouts.Chat.views 1.0 import AppLayouts.Profile 1.0 -import AppLayouts.Profile.popups 1.0 import AppLayouts.CommunitiesPortal 1.0 import utils 1.0 @@ -51,15 +48,11 @@ Item { // set from main.qml property var sysPalette - property var activePopupComponents: [] - - signal closeProfilePopup() - Connections { target: rootStore.mainModuleInst function onDisplayUserProfile(publicKey: string) { - Global.openProfilePopup(publicKey) + popups.openProfilePopup(publicKey) } function onDisplayKeycardSharedModuleFlow() { @@ -83,47 +76,24 @@ Item { } function onOpenActivityCenter() { - Global.openPopup(activityCenterPopupComponent) + popups.openPopup(activityCenterPopupComponent) } } Popups { + id: popups + popupParent: appMain rootStore: appMain.rootStore - - Component.onCompleted: { - Global.openSendIDRequestPopup.connect(openSendIDRequestPopup) - Global.openOutgoingIDRequestPopup.connect(openOutgoingIDRequestPopup) - Global.openIncomingIDRequestPopup.connect(openIncomingIDRequestPopup) - Global.openInviteFriendsToCommunityPopup.connect(openInviteFriendsToCommunityPopup) - Global.openContactRequestPopup.connect(openContactRequestPopup) - } } Connections { + id: globalConns target: Global + function onOpenLinkInBrowser(link: string) { changeAppSectionBySectionId(Constants.appSection.browser) Qt.callLater(() => browserLayoutContainer.item.openUrlInNewTab(link)); } - function onOpenChooseBrowserPopup(link: string) { - Global.openPopup(chooseBrowserPopupComponent, {link: link}); - } - function onOpenDownloadModalRequested(available: bool, version: string, url: string) { - const downloadPage = downloadPageComponent.createObject(appMain, - { - newVersionAvailable: available, - downloadURL: url, - currentVersion: appMain.rootStore.profileSectionStore.getCurrentVersion(), - newVersion: version - }) - return downloadPage - } - - function onOpenImagePopup(image, contextMenu) { - var popup = imagePopupComponent.createObject(appMain) - popup.contextMenu = contextMenu - popup.openPopup(image) - } function onOpenCreateChatView() { createChatView.opened = true @@ -133,76 +103,44 @@ Item { createChatView.opened = false } - function onOpenProfilePopupRequested(publicKey: string, parentPopup) { - if (Global.profilePopupOpened) { - appMain.closeProfilePopup() - } - Global.openPopup(profilePopupComponent, {publicKey: publicKey, parentPopup: parentPopup}) - Global.profilePopupOpened = true - } - function onOpenNicknamePopupRequested(publicKey: string,nickname: string, subtitle: string) { - Global.openPopup(nicknamePopupComponent, {publicKey: publicKey, nickname: nickname, "header.subTitle": subtitle}) - } - function onBlockContactRequested(publicKey: string, contactName: string) { - Global.openPopup(blockContactConfirmationComponent, {contactName: contactName, contactAddress: publicKey}) - } - function onUnblockContactRequested(publicKey: string, contactName: string) { - Global.openPopup(unblockContactConfirmationComponent, {contactName: contactName, contactAddress: publicKey}) + function onOpenActivityCenterPopupRequested() { + popups.openPopup(activityCenterPopupComponent) } - function onOpenActivityCenterPopupRequested(publicKey: string, contactName: string) { - Global.openPopup(activityCenterPopupComponent) - } - - function onOpenChangeProfilePicPopup(cb) { - var popup = changeProfilePicComponent.createObject(appMain, {callback: cb}); - popup.chooseImageToCrop(); - } - function onOpenBackUpSeedPopup() { - Global.openPopup(backupSeedModalComponent) - } function onDisplayToastMessage(title: string, subTitle: string, icon: string, loading: bool, ephNotifType: int, url: string) { - appMain.rootStore.mainModuleInst.displayEphemeralNotification(title, subTitle, icon, loading, ephNotifType, url); - } - function onOpenEditDisplayNamePopup() { - Global.openPopup(displayNamePopupComponent) - } - - function onOpenPopupRequested(popupComponent, params) { - - if (activePopupComponents.includes(popupComponent)) { - return; - } - - const popup = popupComponent.createObject(appMain, params); - popup.open(); - - activePopupComponents.push(popupComponent); - - popup.closed.connect(() => { - const removeIndex = activePopupComponents.indexOf(popupComponent); - if (removeIndex !== -1) { - activePopupComponents.splice(removeIndex, 1); - } - }) - return popup; + appMain.rootStore.mainModuleInst.displayEphemeralNotification(title, subTitle, icon, loading, ephNotifType, url) } function onOpenLink(link: string) { // Qt sometimes inserts random HTML tags; and this will break on invalid URL inside QDesktopServices::openUrl(link) - link = appMain.rootStore.plainText(link); + link = appMain.rootStore.plainText(link) if (appMain.rootStore.showBrowserSelector) { - Global.openChooseBrowserPopup(link); + popups.openChooseBrowserPopup(link) } else { if (appMain.rootStore.openLinksInStatus) { - Global.changeAppSectionBySectionType(Constants.appSection.browser); - Global.openLinkInBrowser(link); + globalConns.onAppSectionBySectionTypeChanged(Constants.appSection.browser) + globalConns.onOpenLinkInBrowser(link) } else { - Qt.openUrlExternally(link); + Qt.openUrlExternally(link) } } } + function onPlaySendMessageSound() { + sendMessageSound.stop() + sendMessageSound.play() + } + + function onPlayNotificationSound() { + notificationSound.stop() + notificationSound.play() + } + + function onPlayErrorSound() { + errorSound.stop() + errorSound.play() + } + function onSetNthEnabledSectionActive(nthSection: int) { if(!appMain.rootStore.mainModuleInst) return @@ -224,124 +162,22 @@ Item { appMain.rootStore.mainModuleInst.setActiveSectionById(sectionId) } - Component { - id: backupSeedModalComponent - BackupSeedModal { - anchors.centerIn: parent - privacyStore: appMain.rootStore.profileSectionStore.privacyStore - onClosed: destroy() - } - } - - Component { - id: displayNamePopupComponent - DisplayNamePopup { - anchors.centerIn: parent - profileStore: appMain.rootStore.profileSectionStore.profileStore - onClosed: { - destroy() - } - } - } - - Component { - id: downloadPageComponent - DownloadPage { - onClosed: { - destroy(); - } - } - } - - Component { - id: imagePopupComponent - StatusImageModal { - id: imagePopup - onClicked: { - if (mouse.button === Qt.LeftButton) { - imagePopup.close() - } else if(mouse.button === Qt.RightButton) { - contextMenu.imageSource = imagePopup.imageSource - contextMenu.hideEmojiPicker = true - contextMenu.isRightClickOnImage = true - contextMenu.parent = imagePopup.contentItem - contextMenu.show() - } - } - onClosed: destroy() - } - } - - Component { - id: profilePopupComponent - ProfileDialog { - id: profilePopup - profileStore: appMain.rootStore.profileSectionStore.profileStore - contactsStore: appMain.rootStore.profileSectionStore.contactsStore - - onClosed: { - if (profilePopup.parentPopup) { - profilePopup.parentPopup.close() - } - Global.profilePopupOpened = false - destroy() - } - - Component.onCompleted: { - appMain.closeProfilePopup.connect(profilePopup.close) - } - } - } - - Component { - id: changeProfilePicComponent - ImageCropWorkflow { - title: qsTr("Profile Picture") - acceptButtonText: qsTr("Make this my Profile Pic") - onImageCropped: { - if (callback) { - callback(image, - cropRect.x.toFixed(), - cropRect.y.toFixed(), - (cropRect.x + cropRect.width).toFixed(), - (cropRect.y + cropRect.height).toFixed()) - return - } - - appMain.rootStore.profileSectionStore.profileStore.uploadImage(image, - cropRect.x.toFixed(), - cropRect.y.toFixed(), - (cropRect.x + cropRect.width).toFixed(), - (cropRect.y + cropRect.height).toFixed()); - } - } - } - Audio { id: sendMessageSound store: rootStore source: "qrc:/imports/assets/audio/send_message.wav" - Component.onCompleted: { - Global.sendMessageSound = this; - } } Audio { id: notificationSound store: rootStore source: "qrc:/imports/assets/audio/notification.wav" - Component.onCompleted: { - Global.notificationSound = this; - } } Audio { id: errorSound source: "qrc:/imports/assets/audio/error.mp3" store: rootStore - Component.onCompleted: { - Global.errorSound = this; - } } Loader { @@ -452,7 +288,7 @@ Item { icon.name: "share-ios" enabled: model.canManageUsers onTriggered: { - Global.openInviteFriendsToCommunityPopup(model, + popups.openInviteFriendsToCommunityPopup(model, communityContextMenu.chatCommunitySectionModule, null) } @@ -461,11 +297,7 @@ Item { StatusAction { text: qsTr("View Community") icon.name: "group-chat" - onTriggered: Global.openPopup(communityProfilePopup, { - store: appMain.rootStore, - community: model, - communitySectionModule: communityContextMenu.chatCommunitySectionModule - }) + onTriggered: popups.openCommunityProfilePopup(appMain.rootStore, model, communityContextMenu.chatCommunitySectionModule) } StatusMenuSeparator {} @@ -670,9 +502,7 @@ Item { text: qsTr("Secure your seed phrase") buttonText: qsTr("Back up now") - onClicked: { - Global.openBackUpSeedPopup(); - } + onClicked: popups.openBackUpSeedPopup() onCloseClicked: { appMain.rootStore.profileSectionStore.profileStore.userDeclinedBackupBanner = true @@ -719,7 +549,7 @@ Item { return "" } - onLinkActivated: Global.openPopup(communitiesPortalLayoutContainer.discordImportProgressPopup) + onLinkActivated: popups.openPopup(communitiesPortalLayoutContainer.discordImportProgressPopup) progressValue: progress closeBtnVisible: finished || stopped buttonText: finished && !errors ? qsTr("Visit your Community") : "" @@ -880,6 +710,8 @@ Item { Loader { id: personalChatLayoutLoader + asynchronous: true + active: appView.currentIndex === Constants.appViewStackIndex.chat sourceComponent: { if (appMain.rootStore.mainModuleInst.chatsLoadingFailed) { return errorStateComponent @@ -939,11 +771,11 @@ Item { } onImportCommunityClicked: { - Global.openPopup(communitiesPortalLayoutContainer.importCommunitiesPopup); + popups.openPopup(communitiesPortalLayoutContainer.importCommunitiesPopup); } onCreateCommunityClicked: { - Global.openPopup(communitiesPortalLayoutContainer.createCommunitiesPopup); + popups.openPopup(communitiesPortalLayoutContainer.createCommunitiesPopup); } Component.onCompleted: { @@ -1075,51 +907,9 @@ Item { } } // ColumnLayout - Component { - id: chooseBrowserPopupComponent - ChooseBrowserPopup { - onClosed: { - destroy() - } - } - } - - Component { - id: communityProfilePopup - - CommunityProfilePopup { - anchors.centerIn: parent - contactsStore: appMain.rootStore.contactStore - hasAddedContacts: appMain.rootStore.hasAddedContacts - - onClosed: { - destroy() - } - } - } - - Component { - id: pinnedMessagesPopupComponent - PinnedMessagesPopup { - id: pinnedMessagesPopup - emojiReactionsModel: appMain.rootStore.emojiReactionsModel - onClosed: destroy() - } - } - - Component { - id: genericConfirmationDialog - ConfirmationDialog { - onClosed: { - destroy() - } - } - } - Component { id: activityCenterPopupComponent ActivityCenterPopup { - id: activityCenter // TODO get screen size // Taken from old code top bar height was fixed there to 56 property int _buttonSize: 56 @@ -1131,41 +921,6 @@ Item { } } - Component { - id: nicknamePopupComponent - NicknamePopup { - onEditDone: { - if (nickname !== newNickname) { - appMain.rootStore.contactStore.changeContactNickname(publicKey, newNickname) - } - close() - } - onClosed: destroy() - } - } - - Component { - id: unblockContactConfirmationComponent - UnblockContactConfirmationDialog { - onUnblockButtonClicked: { - appMain.rootStore.contactStore.unblockContact(contactAddress) - close() - } - onClosed: destroy() - } - } - - Component { - id: blockContactConfirmationComponent - BlockContactConfirmationDialog { - onBlockButtonClicked: { - appMain.rootStore.contactStore.blockContact(contactAddress) - close() - } - onClosed: destroy() - } - } - // Add SendModal here as it is used by the Wallet as well as the Browser Loader { id: sendModal @@ -1354,9 +1109,6 @@ Item { } Component.onCompleted: { - Global.appMain = this; - Global.pinnedMessagesPopup = pinnedMessagesPopupComponent; - Global.communityProfilePopup = communityProfilePopup; const whitelist = appMain.rootStore.messagingStore.getLinkPreviewWhitelist() try { const whiteListedSites = JSON.parse(whitelist) diff --git a/ui/app/mainui/Popups.qml b/ui/app/mainui/Popups.qml index 550c782832..87a2b801a8 100644 --- a/ui/app/mainui/Popups.qml +++ b/ui/app/mainui/Popups.qml @@ -1,18 +1,119 @@ -import QtQuick 2.14 +import QtQuick 2.15 import AppLayouts.Chat.popups 1.0 +import AppLayouts.Profile.popups 1.0 + import shared.popups 1.0 +import shared.status 1.0 import utils 1.0 QtObject { id: root - /* required */ property var rootStore + required property var popupParent + required property var rootStore + + property var activePopupComponents: [] + + Component.onCompleted: { + Global.openSendIDRequestPopup.connect(openSendIDRequestPopup) + Global.openOutgoingIDRequestPopup.connect(openOutgoingIDRequestPopup) + Global.openIncomingIDRequestPopup.connect(openIncomingIDRequestPopup) + Global.openInviteFriendsToCommunityPopup.connect(openInviteFriendsToCommunityPopup) + Global.openContactRequestPopup.connect(openContactRequestPopup) + Global.openChooseBrowserPopup.connect(openChooseBrowserPopup) + Global.openDownloadModalRequested.connect(openDownloadModal) + Global.openImagePopup.connect(openImagePopup) + Global.openProfilePopupRequested.connect(openProfilePopup) + Global.openNicknamePopupRequested.connect(openNicknamePopup) + Global.blockContactRequested.connect(openBlockContactPopup) + Global.unblockContactRequested.connect(openUnblockContactPopup) + Global.openChangeProfilePicPopup.connect(openChangeProfilePicPopup) + Global.openBackUpSeedPopup.connect(openBackUpSeedPopup) + Global.openEditDisplayNamePopup.connect(openEditDisplayNamePopup) + Global.openPinnedMessagesPopupRequested.connect(openPinnedMessagesPopup) + Global.openCommunityProfilePopupRequested.connect(openCommunityProfilePopup) + Global.openPopupRequested.connect(openPopup) + } + + function openPopup(popupComponent, params = {}, cb = null) { + if (activePopupComponents.includes(popupComponent)) { + return + } + + const popup = popupComponent.createObject(popupParent, params) + popup.open() + + if (cb) + cb(popup) + + activePopupComponents.push(popupComponent) + + popup.closed.connect(() => { + const removeIndex = activePopupComponents.indexOf(popupComponent) + if (removeIndex !== -1) { + activePopupComponents.splice(removeIndex, 1) + } + }) + } + + function openChooseBrowserPopup(link: string) { + openPopup(chooseBrowserPopupComponent, {link: link}) + } + + function openDownloadModal(available: bool, version: string, url: string) { + const popupProperties = { + newVersionAvailable: available, + downloadURL: url, + currentVersion: rootStore.profileSectionStore.getCurrentVersion(), + newVersion: version + } + openPopup(downloadPageComponent, popupProperties) + } + + function openImagePopup(image, contextMenu) { + var popup = imagePopupComponent.createObject(popupParent) + popup.contextMenu = contextMenu + popup.openPopup(image) + } + + function openProfilePopup(publicKey: string, parentPopup) { + openPopup(profilePopupComponent, {publicKey: publicKey, parentPopup: parentPopup}) + } + + function openNicknamePopup(publicKey: string, nickname: string, subtitle: string) { + openPopup(nicknamePopupComponent, {publicKey: publicKey, nickname: nickname, "header.subTitle": subtitle}) + } + + function openBlockContactPopup(publicKey: string, contactName: string) { + openPopup(blockContactConfirmationComponent, {contactName: contactName, contactAddress: publicKey}) + } + + function openUnblockContactPopup(publicKey: string, contactName: string) { + openPopup(unblockContactConfirmationComponent, {contactName: contactName, contactAddress: publicKey}) + } + + function openChangeProfilePicPopup(cb) { + var popup = changeProfilePicComponent.createObject(popupParent, {callback: cb}); + popup.chooseImageToCrop() + } + + function openBackUpSeedPopup() { + openPopup(backupSeedModalComponent) + } + + function openEditDisplayNamePopup() { + openPopup(displayNamePopupComponent) + } + + function openCommunityProfilePopup(store, community, communitySectionModule) { + openPopup(communityProfilePopup, { store: store, community: community, communitySectionModule: communitySectionModule}) + } function openSendIDRequestPopup(publicKey, cb) { const contactDetails = Utils.getContactDetailsAsJson(publicKey, false) - const popup = Global.openPopup(sendIDRequestPopupComponent, { + openPopup(sendIDRequestPopupComponent, { userPublicKey: publicKey, userDisplayName: contactDetails.displayName, userIcon: contactDetails.largeImage, @@ -20,9 +121,7 @@ QtObject { "header.title": qsTr("Verify %1's Identity").arg(contactDetails.displayName), challengeText: qsTr("Ask a question that only the real %1 will be able to answer e.g. a question about a shared experience, or ask %1 to enter a code or phrase you have sent to them via a different communication channel (phone, post, etc...).").arg(contactDetails.displayName), buttonText: qsTr("Send verification request") - }) - if (cb) - cb(popup) + }, cb) } function openOutgoingIDRequestPopup(publicKey, cb) { @@ -38,9 +137,7 @@ QtObject { verificationRequestedAt: verificationDetails.requestedAt, verificationRepliedAt: verificationDetails.repliedAt } - const popup = Global.openPopup(contactOutgoingVerificationRequestPopupComponent, popupProperties) - if (cb) - cb(popup) + openPopup(contactOutgoingVerificationRequestPopupComponent, popupProperties, cb) } catch (e) { console.error("Error getting or parsing verification data", e) } @@ -52,16 +149,11 @@ QtObject { publicKey: publicKey } - const popup = Global.openPopup(contactVerificationRequestPopupComponent, popupProperties) - if (cb) { - cb(popup) - } + openPopup(contactVerificationRequestPopupComponent, popupProperties, cb) } function openInviteFriendsToCommunityPopup(community, communitySectionModule, cb) { - const popup = Global.openPopup(inviteFriendsToCommunityPopup, { community, communitySectionModule }) - if (cb) - cb(popup) + openPopup(inviteFriendsToCommunityPopup, { community: community, communitySectionModule: communitySectionModule }, cb) } function openContactRequestPopup(publicKey, cb) { @@ -73,12 +165,19 @@ QtObject { userIsEnsVerified: contactDetails.ensVerified } - const popup = Global.openPopup(sendContactRequestPopupComponent, popupProperties) - if (cb) - cb(popup) + openPopup(sendContactRequestPopupComponent, popupProperties, cb) } - readonly property list _d: [ + function openPinnedMessagesPopup(store, messageStore, pinnedMessagesModel, messageToPin) { + openPopup(pinnedMessagesPopup, { store: store, messageStore: messageStore, + pinnedMessagesModel: pinnedMessagesModel, messageToPin: messageToPin}) + } + + function openCommunityPopup(store, community, chatCommunitySectionModule) { + openPopup(communityProfilePopup, {store: store, community: community, chatCommunitySectionModule: chatCommunitySectionModule}) + } + + readonly property list _components: [ Component { id: contactVerificationRequestPopupComponent ContactVerificationRequestPopup { @@ -136,6 +235,153 @@ QtObject { onAccepted: root.rootStore.profileSectionStore.contactsStore.sendContactRequest(userPublicKey, message) onClosed: destroy() } + }, + + Component { + id: backupSeedModalComponent + BackupSeedModal { + anchors.centerIn: parent + privacyStore: rootStore.profileSectionStore.privacyStore + onClosed: destroy() + } + }, + + Component { + id: displayNamePopupComponent + DisplayNamePopup { + anchors.centerIn: parent + profileStore: rootStore.profileSectionStore.profileStore + onClosed: destroy() + } + }, + + Component { + id: downloadPageComponent + DownloadPage { + onClosed: destroy() + } + }, + + Component { + id: imagePopupComponent + StatusImageModal { + id: imagePopup + onClicked: { + if (mouse.button === Qt.LeftButton) { + imagePopup.close() + } else if(mouse.button === Qt.RightButton) { + contextMenu.imageSource = imagePopup.imageSource + contextMenu.hideEmojiPicker = true + contextMenu.isRightClickOnImage = true + contextMenu.parent = imagePopup.contentItem + contextMenu.show() + } + } + onClosed: destroy() + } + }, + + Component { + id: profilePopupComponent + ProfileDialog { + id: profilePopup + profileStore: rootStore.profileSectionStore.profileStore + contactsStore: rootStore.profileSectionStore.contactsStore + + onClosed: { + if (profilePopup.parentPopup) { + profilePopup.parentPopup.close() + } + destroy() + } + } + }, + + Component { + id: changeProfilePicComponent + ImageCropWorkflow { + title: qsTr("Profile Picture") + acceptButtonText: qsTr("Make this my Profile Pic") + onImageCropped: { + if (callback) { + callback(image, + cropRect.x.toFixed(), + cropRect.y.toFixed(), + (cropRect.x + cropRect.width).toFixed(), + (cropRect.y + cropRect.height).toFixed()) + return + } + + rootStore.profileSectionStore.profileStore.uploadImage(image, + cropRect.x.toFixed(), + cropRect.y.toFixed(), + (cropRect.x + cropRect.width).toFixed(), + (cropRect.y + cropRect.height).toFixed()); + } + onDone: destroy() + } + }, + + Component { + id: chooseBrowserPopupComponent + ChooseBrowserPopup { + onClosed: destroy() + } + }, + + Component { + id: communityProfilePopup + + CommunityProfilePopup { + anchors.centerIn: parent + contactsStore: rootStore.contactStore + hasAddedContacts: rootStore.hasAddedContacts + + onClosed: destroy() + } + }, + + Component { + id: pinnedMessagesPopup + PinnedMessagesPopup { + emojiReactionsModel: rootStore.emojiReactionsModel + onClosed: destroy() + } + }, + + Component { + id: nicknamePopupComponent + NicknamePopup { + onEditDone: { + if (nickname !== newNickname) { + rootStore.contactStore.changeContactNickname(publicKey, newNickname) + } + close() + } + onClosed: destroy() + } + }, + + Component { + id: unblockContactConfirmationComponent + UnblockContactConfirmationDialog { + onUnblockButtonClicked: { + rootStore.contactStore.unblockContact(contactAddress) + close() + } + onClosed: destroy() + } + }, + + Component { + id: blockContactConfirmationComponent + BlockContactConfirmationDialog { + onBlockButtonClicked: { + rootStore.contactStore.blockContact(contactAddress) + close() + } + onClosed: destroy() + } } ] } diff --git a/ui/app/mainui/qmldir b/ui/app/mainui/qmldir index 7b9fdcbf4d..df4aa53e29 100644 --- a/ui/app/mainui/qmldir +++ b/ui/app/mainui/qmldir @@ -1,2 +1,3 @@ AppMain 1.0 AppMain.qml SplashScreen 1.0 SplashScreen.qml +Popups 1.0 Popups.qml diff --git a/ui/imports/shared/popups/ImageCropWorkflow.qml b/ui/imports/shared/popups/ImageCropWorkflow.qml index 77b0274eab..049d2c08a3 100644 --- a/ui/imports/shared/popups/ImageCropWorkflow.qml +++ b/ui/imports/shared/popups/ImageCropWorkflow.qml @@ -23,6 +23,7 @@ Item { property bool roundedImage: true signal imageCropped(var image, var cropRect) + signal done() function chooseImageToCrop() { fileDialog.open() @@ -87,6 +88,7 @@ Item { } } ] + onClosed: root.done() } // StatusModal } // Item diff --git a/ui/imports/shared/popups/NicknamePopup.qml b/ui/imports/shared/popups/NicknamePopup.qml index 58950fb5df..0974af5b94 100644 --- a/ui/imports/shared/popups/NicknamePopup.qml +++ b/ui/imports/shared/popups/NicknamePopup.qml @@ -1,5 +1,4 @@ -import QtQuick 2.13 -import QtQuick.Controls 2.13 +import QtQuick 2.14 import utils 1.0 import shared.controls 1.0 @@ -61,11 +60,9 @@ StatusModal { charLimit: maxNicknameLength validationMode: StatusInput.ValidationMode.IgnoreInvalidInput validators: [ - StatusRegularExpressionValidator { + StatusValidator { validatorObj: RXValidator { regularExpression: /^[\w\d_ -]*$/u } - validate: function (value) { - return validatorObj.test(value) - } + validate: (value) => validatorObj.test(value) } ] Keys.onReleased: { diff --git a/ui/imports/shared/popups/keycard/helpers/KeyPairUnknownItem.qml b/ui/imports/shared/popups/keycard/helpers/KeyPairUnknownItem.qml index 7c9568ba64..04326fb59b 100644 --- a/ui/imports/shared/popups/keycard/helpers/KeyPairUnknownItem.qml +++ b/ui/imports/shared/popups/keycard/helpers/KeyPairUnknownItem.qml @@ -129,12 +129,10 @@ Rectangle { StatusBaseText { Layout.alignment: Qt.AlignVCenter text: { - if (Global.appMain) { - return "%1%2".arg(SharedStore.RootStore.currencyStore.currentCurrencySymbol) - .arg(Utils.toLocaleString(parseFloat(model.account.balance.amount).toFixed(2), appSettings.locale, {"model.account.currency": true})) - } - // without language/model refactor no way to read currency symbol or `appSettings.locale` before user logs in - return "$%1".arg(Utils.toLocaleString(parseFloat(model.account.balance.amount).toFixed(2), localAppSettings.language, {"model.account.currency": true})) + return LocaleUtils.currencyAmountToLocaleString({ + amount: parseFloat(model.account.balance.amount), + symbol: SharedStore.RootStore.currencyStore.currentCurrencySymbol, + displayDecimals: 2}) } wrapMode: Text.WordWrap font.pixelSize: Constants.keycard.general.fontSize2 diff --git a/ui/imports/shared/popups/keycard/states/ManageAccounts.qml b/ui/imports/shared/popups/keycard/states/ManageAccounts.qml index 0cb5e32dcf..7644e4c425 100644 --- a/ui/imports/shared/popups/keycard/states/ManageAccounts.qml +++ b/ui/imports/shared/popups/keycard/states/ManageAccounts.qml @@ -161,12 +161,10 @@ Item { ColumnLayout { StatusBaseText { text: { - if (Global.appMain) { - return "Balance: %1%2".arg(SharedStore.RootStore.currencyStore.currentCurrencySymbol) - .arg(Utils.toLocaleString(root.sharedKeycardModule.keyPairHelper.observedAccount.balance.toFixed(2), appSettings.locale, {"model.account.currency": true})) - } - // without language/model refactor no way to read currency symbol or `appSettings.locale` before user logs in - return "Balance: $%1".arg(Utils.toLocaleString(root.sharedKeycardModule.keyPairHelper.observedAccount.balance.toFixed(2), localAppSettings.language, {"model.account.currency": true})) + return qsTr("Balance: %1").arg(LocaleUtils.currencyAmountToLocaleString({ + amount: parseFloat(root.sharedKeycardModule.keyPairHelper.observedAccount.balance), + symbol: SharedStore.RootStore.currencyStore.currentCurrencySymbol, + displayDecimals: 2})) } wrapMode: Text.WordWrap font.pixelSize: Constants.keycard.general.fontSize2 diff --git a/ui/imports/shared/stores/qmldir b/ui/imports/shared/stores/qmldir index cae58916be..d3db0cb72f 100644 --- a/ui/imports/shared/stores/qmldir +++ b/ui/imports/shared/stores/qmldir @@ -4,3 +4,4 @@ TransactionStore 1.0 TransactionStore.qml BIP39_en 1.0 BIP39_en.qml TokenBalanceHistoryStore 1.0 TokenBalanceHistoryStore.qml TokenMarketValuesStore 1.0 TokenMarketValuesStore.qml +ChartStoreBase 1.0 ChartStoreBase.qml diff --git a/ui/imports/shared/views/ProfileDialogView.qml b/ui/imports/shared/views/ProfileDialogView.qml index 9229b7e840..769f3dad6b 100644 --- a/ui/imports/shared/views/ProfileDialogView.qml +++ b/ui/imports/shared/views/ProfileDialogView.qml @@ -104,6 +104,15 @@ Pane { } } + readonly property var conns3: Connections { + target: root.contactsStore.sentContactRequestsModel + + function onItemChanged(pubKey) { + if (pubKey === root.publicKey) + d.reload() + } + } + readonly property var timer: Timer { id: timer } @@ -171,8 +180,7 @@ Pane { size: StatusButton.Size.Small text: qsTr("Send Contact Request") onClicked: { - Global.openContactRequestPopup(root.publicKey, - popup => popup.accepted.connect(d.reload)) + Global.openContactRequestPopup(root.publicKey, null) } } } @@ -395,8 +403,7 @@ Pane { d.contactDetails.trustStatus === Constants.trustStatus.untrustworthy // we have an action button otherwise onTriggered: { moreMenu.close() - Global.openContactRequestPopup(root.publicKey, - popup => popup.closed.connect(d.reload)) + Global.openContactRequestPopup(root.publicKey, null) } } StatusAction { diff --git a/ui/imports/shared/views/chat/MessageContextMenuView.qml b/ui/imports/shared/views/chat/MessageContextMenuView.qml index bfa55dcd12..961f51e22d 100644 --- a/ui/imports/shared/views/chat/MessageContextMenuView.qml +++ b/ui/imports/shared/views/chat/MessageContextMenuView.qml @@ -365,14 +365,14 @@ StatusMenu { root.store.copyToClipboard(root.unparsedText) close() } - enabled: root.messageContentType === Constants.messageContentType.messageType && !root.isProfile && !emojiContainer.visible + enabled: root.messageContentType === Constants.messageContentType.messageType && replyToMenuItem.enabled } StatusAction { id: copyMessageIdAction text: qsTr("Copy Message Id") icon.name: "copy" - enabled: root.isDebugEnabled && !root.isProfile && !root.pinnedPopup && !emojiContainer.visible + enabled: root.isDebugEnabled && replyToMenuItem.enabled onTriggered: { root.store.copyToClipboard(root.messageId) close() diff --git a/ui/imports/shared/views/chat/MessageView.qml b/ui/imports/shared/views/chat/MessageView.qml index b156d31d67..813284005a 100644 --- a/ui/imports/shared/views/chat/MessageView.qml +++ b/ui/imports/shared/views/chat/MessageView.qml @@ -467,7 +467,6 @@ Loader { disableHover: root.disableHover || (root.chatLogView && root.chatLogView.moving) || (root.messageContextMenu && root.messageContextMenu.opened) || - Global.profilePopupOpened || Global.popupOpened hideQuickActions: root.isChatBlocked || @@ -816,12 +815,7 @@ Loader { return; } - Global.openPopup(Global.pinnedMessagesPopup, { - store: root.rootStore, - messageStore: messageStore, - pinnedMessagesModel: chatContentModule.pinnedMessagesModel, - messageToPin: root.messageId - }); + Global.openPinnedMessagesPopupRequested(root.rootStore, messageStore, chatContentModule.pinnedMessagesModel, root.messageId) } } }, diff --git a/ui/imports/utils/Global.qml b/ui/imports/utils/Global.qml index 91f63ef23c..f443b96ee2 100644 --- a/ui/imports/utils/Global.qml +++ b/ui/imports/utils/Global.qml @@ -6,19 +6,14 @@ QtObject { id: root property var dragArea - property var appMain property var applicationWindow property bool popupOpened: false property int settingsSubsection: Constants.settingsSubsection.profile property var userProfile - property var pinnedMessagesPopup - property var communityProfilePopup - property bool profilePopupOpened: false - property var sendMessageSound - property var notificationSound - property var errorSound + signal openPinnedMessagesPopupRequested(var store, var messageStore, var pinnedMessagesModel, string messageToPin) + signal openCommunityProfilePopupRequested(var store, var community, var chatCommunitySectionModule) signal openLinkInBrowser(string link) signal openChooseBrowserPopup(string link) @@ -31,7 +26,7 @@ QtObject { signal displayToastMessage(string title, string subTitle, string icon, bool loading, int ephNotifType, string url) - signal openPopupRequested(var popupComponent, var params); + signal openPopupRequested(var popupComponent, var params) signal openNicknamePopupRequested(string publicKey, string nickname, string subtitle) signal openDownloadModalRequested(bool available, string version, string url) signal openChangeProfilePicPopup(var cb) @@ -51,6 +46,10 @@ QtObject { signal setNthEnabledSectionActive(int nthSection) signal appSectionBySectionTypeChanged(int sectionType, int subsection) + signal playSendMessageSound() + signal playNotificationSound() + signal playErrorSound() + function openProfilePopup(publicKey, parentPopup) { root.openProfilePopupRequested(publicKey, parentPopup) } @@ -70,9 +69,4 @@ QtObject { function changeAppSectionBySectionType(sectionType, subsection = 0) { root.appSectionBySectionTypeChanged(sectionType, subsection); } - - function playErrorSound() { - if (root.errorSound) - root.errorSound.play(); - } }