From 15f23dfb87ad821274ea23d4f635cfbb992a037c Mon Sep 17 00:00:00 2001 From: Michal Iskierko Date: Mon, 15 Jul 2024 15:34:40 +0200 Subject: [PATCH] feat(@desktop/metrics): Add metrics management page Add new settings page for metrics management - Privacy and security Add popup to enable/disable metrics on Onboarding and Privacy screens Add MetricsStore in QML Issue #15490 --- storybook/pages/MetricsEnablePopupPage.qml | 39 +++++++ .../popups/CreateCategoryPopup.qml | 5 +- .../Onboarding/OnboardingLayout.qml | 3 + .../Onboarding/stores/StartupStore.qml | 8 -- .../Onboarding/views/KeysMainView.qml | 47 ++++++-- ui/app/AppLayouts/Profile/ProfileLayout.qml | 15 +++ .../Profile/stores/ProfileSectionStore.qml | 3 + .../Profile/views/PrivacyAndSecurityView.qml | 62 ++++++++++ ui/app/AppLayouts/Profile/views/qmldir | 1 + ui/app/mainui/AppMain.qml | 2 + .../shared/popups/MetricsEnablePopup.qml | 109 ++++++++++++++++++ ui/imports/shared/popups/qmldir | 1 + ui/imports/shared/stores/MetricsStore.qml | 13 +++ ui/imports/shared/stores/qmldir | 3 +- ui/imports/utils/Constants.qml | 5 +- 15 files changed, 289 insertions(+), 27 deletions(-) create mode 100644 storybook/pages/MetricsEnablePopupPage.qml create mode 100644 ui/app/AppLayouts/Profile/views/PrivacyAndSecurityView.qml create mode 100644 ui/imports/shared/popups/MetricsEnablePopup.qml create mode 100644 ui/imports/shared/stores/MetricsStore.qml diff --git a/storybook/pages/MetricsEnablePopupPage.qml b/storybook/pages/MetricsEnablePopupPage.qml new file mode 100644 index 0000000000..f52bbe7c44 --- /dev/null +++ b/storybook/pages/MetricsEnablePopupPage.qml @@ -0,0 +1,39 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 + +import shared.popups 1.0 + +import Storybook 1.0 + +SplitView { + orientation: Qt.Vertical + + Item { + + SplitView.fillWidth: true + SplitView.fillHeight: true + + PopupBackground { + anchors.fill: parent + } + + Button { + anchors.centerIn: parent + text: "Reopen" + + onClicked: popup.open() + } + } + + MetricsEnablePopup { + id: popup + anchors.centerIn: parent + modal: false + visible: true + isOnboarding: true + } +} + +// category: Popups + +// https://www.figma.com/design/idUoxN7OIW2Jpp3PMJ1Rl8/%E2%9A%99%EF%B8%8F-Settings-%7C-Desktop?node-id=24721-503547&t=a7IsC44aG7YQuInQ-0 diff --git a/ui/app/AppLayouts/Communities/popups/CreateCategoryPopup.qml b/ui/app/AppLayouts/Communities/popups/CreateCategoryPopup.qml index af7bedded6..3c3a3dc958 100644 --- a/ui/app/AppLayouts/Communities/popups/CreateCategoryPopup.qml +++ b/ui/app/AppLayouts/Communities/popups/CreateCategoryPopup.qml @@ -201,10 +201,7 @@ StatusModal { type: StatusBaseButton.Type.Danger text: qsTr("Delete Category") onClicked: { - Global.openPopup(deleteCategoryConfirmationDialogComponent, { - "headerSettings.title": qsTr("Delete '%1' category").arg(nameInput.text), - confirmationText: qsTr("Are you sure you want to delete '%1' category? Channels inside the category won’t be deleted.").arg(nameInput.text) - }) + Global.openPopup(deleteCategoryConfirmationDialogComponent) } }, StatusButton { diff --git a/ui/app/AppLayouts/Onboarding/OnboardingLayout.qml b/ui/app/AppLayouts/Onboarding/OnboardingLayout.qml index e855a0d5bc..be45d78a00 100644 --- a/ui/app/AppLayouts/Onboarding/OnboardingLayout.qml +++ b/ui/app/AppLayouts/Onboarding/OnboardingLayout.qml @@ -8,6 +8,7 @@ import StatusQ.Core.Theme 0.1 import utils 1.0 import shared.popups.keycard 1.0 +import shared.stores 1.0 import "controls" import "views" @@ -18,6 +19,7 @@ OnboardingBasePage { id: root property var startupStore: StartupStore {} + property var metricsStore: MetricsStore {} backButtonVisible: root.startupStore.currentStartupState ? root.startupStore.currentStartupState.displayBackButton : false @@ -233,6 +235,7 @@ following the \"Add existing Status user\" flow, using your seed phrase.") id: keysMainViewComponent KeysMainView { startupStore: root.startupStore + metricsStore: root.metricsStore } } diff --git a/ui/app/AppLayouts/Onboarding/stores/StartupStore.qml b/ui/app/AppLayouts/Onboarding/stores/StartupStore.qml index 21ec5ca20e..e24b060022 100644 --- a/ui/app/AppLayouts/Onboarding/stores/StartupStore.qml +++ b/ui/app/AppLayouts/Onboarding/stores/StartupStore.qml @@ -115,14 +115,6 @@ QtObject { return root.startupModuleInst.getSeedPhrase() } - function toggleCentralizedMetrics(enabled) { - metrics.toggleCentralizedMetrics(enabled) - } - - function isCentralizedMetricsEnabled() { - return metrics.isCentralizedMetricsEnabled() - } - function validateLocalPairingConnectionString(connectionString) { return root.startupModuleInst.validateLocalPairingConnectionString(connectionString) } diff --git a/ui/app/AppLayouts/Onboarding/views/KeysMainView.qml b/ui/app/AppLayouts/Onboarding/views/KeysMainView.qml index 2e6b8bc16c..e0ebe86f0a 100644 --- a/ui/app/AppLayouts/Onboarding/views/KeysMainView.qml +++ b/ui/app/AppLayouts/Onboarding/views/KeysMainView.qml @@ -10,6 +10,9 @@ import StatusQ.Components 0.1 import shared 1.0 import shared.panels 1.0 +import shared.popups 1.0 +import shared.stores 1.0 + import "../popups" import "../controls" import "../stores" @@ -20,12 +23,12 @@ Item { id: root property StartupStore startupStore + property MetricsStore metricsStore Component.onCompleted: { if (button1.visible) { button1.forceActiveFocus() } - enableCentricMetrics.checked = root.startupStore.isCentralizedMetricsEnabled() } QtObject { @@ -36,6 +39,16 @@ Item { readonly property int infoTextWidth: d.infoWidth - 2 * d.infoMargin readonly property int imgKeysWH: 160 readonly property int imgSeedPhraseWH: 257 + + + function showMetricsAndRunAction(action) { + if (root.startupStore.currentStartupState.stateType === Constants.startupState.welcomeNewStatusUser) { + metricsEnablePopup.actionOnClose = action + metricsEnablePopup.visible = true + } else { + action() + } + } } ColumnLayout { anchors.centerIn: parent @@ -220,12 +233,12 @@ Item { Layout.alignment: Qt.AlignHCenter visible: text !== "" onClicked: { - root.startupStore.doPrimaryAction() + d.showMetricsAndRunAction(root.startupStore.doPrimaryAction) } Keys.onPressed: { if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { event.accepted = true - root.startupStore.doPrimaryAction() + d.showMetricsAndRunAction(root.startupStore.doPrimaryAction) } } } @@ -248,7 +261,7 @@ Item { parent.font.underline = false } onClicked: { - root.startupStore.doSecondaryAction() + d.showMetricsAndRunAction(root.startupStore.doSecondaryAction) } } } @@ -284,7 +297,7 @@ Item { Qt.openUrlExternally(button3.link) return } - root.startupStore.doTertiaryAction() + d.showMetricsAndRunAction(root.startupStore.doTertiaryAction) } } } @@ -302,19 +315,29 @@ Item { } } - StatusCheckBox { - id: enableCentricMetrics - text: qsTr("Enable centric metrics") - Layout.alignment: Qt.AlignHCenter - onToggled: root.startupStore.toggleCentralizedMetrics(checked) - } - Item { Layout.fillWidth: true Layout.fillHeight: true } } + component MetricsEnablePopupWithActionOnClose: MetricsEnablePopup { + property var actionOnClose + } + + MetricsEnablePopupWithActionOnClose { + id: metricsEnablePopup + isOnboarding: true + + function finalAction(enable) { + if (!!actionOnClose) + actionOnClose() + root.metricsStore.toggleCentralizedMetrics(enable) + } + + onAccepted: finalAction(true) + onRejected: finalAction(false) + } states: [ State { diff --git a/ui/app/AppLayouts/Profile/ProfileLayout.qml b/ui/app/AppLayouts/Profile/ProfileLayout.qml index 56690ee960..4cf416b9a3 100644 --- a/ui/app/AppLayouts/Profile/ProfileLayout.qml +++ b/ui/app/AppLayouts/Profile/ProfileLayout.qml @@ -44,6 +44,7 @@ StatusSectionLayout { required property WalletAssetsStore walletAssetsStore required property CollectiblesStore collectiblesStore required property SharedStores.CurrenciesStore currencyStore + required property SharedStores.MetricsStore metricsStore backButtonName: root.store.backButtonName notificationCount: activityCenterStore.unreadNotificationsCount @@ -457,6 +458,20 @@ StatusSectionLayout { } } } + + Loader { + active: false + asynchronous: true + sourceComponent: PrivacyAndSecurityView { + metricsStore: root.metricsStore + + implicitWidth: parent.width + implicitHeight: parent.height + + sectionTitle: root.store.getNameForSubsection(Constants.settingsSubsection.privacyAndSecurity) + contentWidth: d.contentWidth + } + } } showRightPanel: d.isProfilePanelActive && d.sideBySidePreviewAvailable diff --git a/ui/app/AppLayouts/Profile/stores/ProfileSectionStore.qml b/ui/app/AppLayouts/Profile/stores/ProfileSectionStore.qml index 2e3d756122..36f6f5c63b 100644 --- a/ui/app/AppLayouts/Profile/stores/ProfileSectionStore.qml +++ b/ui/app/AppLayouts/Profile/stores/ProfileSectionStore.qml @@ -133,6 +133,9 @@ QtObject { property ListModel settingsMenuItems: ListModel { Component.onCompleted: { + append({subsection: Constants.settingsSubsection.privacyAndSecurity, + text: qsTr("Privacy and security"), + icon: "security"}) append({subsection: Constants.settingsSubsection.appearance, text: qsTr("Appearance"), icon: "appearance"}) diff --git a/ui/app/AppLayouts/Profile/views/PrivacyAndSecurityView.qml b/ui/app/AppLayouts/Profile/views/PrivacyAndSecurityView.qml new file mode 100644 index 0000000000..6a25502848 --- /dev/null +++ b/ui/app/AppLayouts/Profile/views/PrivacyAndSecurityView.qml @@ -0,0 +1,62 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 + +import utils 1.0 +import shared.panels 1.0 +import shared.popups 1.0 +import shared.stores 1.0 + +import StatusQ 0.1 +import StatusQ.Core 0.1 +import StatusQ.Core.Theme 0.1 +import StatusQ.Core.Utils 0.1 as StatusQUtils +import StatusQ.Components 0.1 +import StatusQ.Controls 0.1 + +import AppLayouts.Profile.stores 1.0 + +import "../popups" + +import SortFilterProxyModel 0.2 + +SettingsContentBase { + id: root + + required property MetricsStore metricsStore + + Component.onCompleted: { + enableMetricsSwitch.checked = metricsStore.isCentralizedMetricsEnabled() + } + + function enableMetrics(enable) { + enableMetricsSwitch.checked = enable + metricsStore.toggleCentralizedMetrics(enable) + } + + ColumnLayout { + StatusListItem { + Layout.preferredWidth: root.contentWidth + title: qsTr("Share usage data with Status") + components: [ + StatusSwitch { + id: enableMetricsSwitch + onClicked: { + Global.openPopup(metricsEnabledPopupComponent) + } + } + ] + onClicked: { + Global.openPopup(metricsEnabledPopupComponent) + } + } + + Component { + id: metricsEnabledPopupComponent + MetricsEnablePopup { + onAccepted: root.enableMetrics(true) + onRejected: root.enableMetrics(false) + } + } + } +} diff --git a/ui/app/AppLayouts/Profile/views/qmldir b/ui/app/AppLayouts/Profile/views/qmldir index 6df10a22c7..ad22d57a44 100644 --- a/ui/app/AppLayouts/Profile/views/qmldir +++ b/ui/app/AppLayouts/Profile/views/qmldir @@ -6,3 +6,4 @@ CommunitiesView 1.0 CommunitiesView.qml LanguageView 1.0 LanguageView.qml NotificationsView 1.0 NotificationsView.qml SyncingView 1.0 SyncingView.qml +PrivacyAndSecurityView 1.0 PrivacyAndSecurityView.qml diff --git a/ui/app/mainui/AppMain.qml b/ui/app/mainui/AppMain.qml index 530f5f1ad4..821436037a 100644 --- a/ui/app/mainui/AppMain.qml +++ b/ui/app/mainui/AppMain.qml @@ -72,6 +72,7 @@ Item { property NetworkConnectionStore networkConnectionStore: NetworkConnectionStore {} property CommunityTokensStore communityTokensStore: CommunityTokensStore {} property CommunitiesStore communitiesStore: CommunitiesStore {} + property MetricsStore metricsStore: MetricsStore {} readonly property WalletStore.TokensStore tokensStore: WalletStore.RootStore.tokensStore readonly property WalletStore.WalletAssetsStore walletAssetsStore: WalletStore.RootStore.walletAssetsStore readonly property WalletStore.CollectiblesStore walletCollectiblesStore: WalletStore.RootStore.collectiblesStore @@ -1334,6 +1335,7 @@ Item { walletAssetsStore: appMain.walletAssetsStore collectiblesStore: appMain.walletCollectiblesStore currencyStore: appMain.currencyStore + metricsStore: appMain.metricsStore } } diff --git a/ui/imports/shared/popups/MetricsEnablePopup.qml b/ui/imports/shared/popups/MetricsEnablePopup.qml new file mode 100644 index 0000000000..dbda57d9e9 --- /dev/null +++ b/ui/imports/shared/popups/MetricsEnablePopup.qml @@ -0,0 +1,109 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 + +import StatusQ.Core 0.1 +import StatusQ.Controls 0.1 +import StatusQ.Popups 0.1 +import StatusQ.Popups.Dialog 0.1 +import StatusQ.Components 0.1 +import StatusQ.Core.Theme 0.1 + +import utils 1.0 + +StatusModal { + id: root + + property bool isOnboarding: false + + width: 640 + title: qsTr("Help us improve Status") + hasCloseButton: true + verticalPadding: 20 + + closePolicy: Popup.CloseOnEscape + + component Paragraph: StatusBaseText { + lineHeightMode: Text.FixedHeight + lineHeight: 22 + visible: true + wrapMode: Text.Wrap + } + + component AgreementSection: ColumnLayout { + property alias title: titleItem.text + property alias body: bodyItem.text + spacing: 8 + Paragraph { + id: titleItem + Layout.fillWidth: true + Layout.fillHeight: true + font.weight: Font.Bold + } + + Paragraph { + id: bodyItem + Layout.fillWidth: true + Layout.fillHeight: true + } + } + + StatusScrollView { + id: scrollView + anchors.fill: parent + contentWidth: availableWidth + + ColumnLayout { + id: layout + width: scrollView.availableWidth + spacing: 20 + + Paragraph { + Layout.fillWidth: true + Layout.fillHeight: true + text: qsTr("Collecting usage data helps us improve Status.") + } + + AgreementSection { + title: qsTr("What we will receive:") + body: qsTr(" • IP address + • Universally Unique Identifiers of device + • Logs of actions within the app, including button presses and screen visits") + } + + AgreementSection { + title: qsTr("What we won’t receive:") + body: qsTr(" • Your profile information + • Your addresses + • Information you input and send") + } + + Paragraph { + Layout.fillWidth: true + Layout.fillHeight: true + text: qsTr("Usage data will be shared from all profiles added to device. %1").arg(root.isOnboarding ? "Sharing usage data can be turned off anytime in Settings / Privacy and Security." : "") + } + } + } + + rightButtons: [ + StatusButton { + text: qsTr("Share usage data") + onClicked: { + root.accept() + } + objectName: "shareMetricsButton" + } + ] + + leftButtons: [ + StatusButton { + text: qsTr("Not now") + onClicked: { + root.reject() + } + objectName: "notShareMetricsButton" + normalColor: "transparent" + } + ] +} diff --git a/ui/imports/shared/popups/qmldir b/ui/imports/shared/popups/qmldir index 49035c8e0b..c4421c5613 100644 --- a/ui/imports/shared/popups/qmldir +++ b/ui/imports/shared/popups/qmldir @@ -31,5 +31,6 @@ SendContactRequestModal 1.0 SendContactRequestModal.qml SettingsDirtyToastMessage 1.0 SettingsDirtyToastMessage.qml UnblockContactConfirmationDialog 1.0 UnblockContactConfirmationDialog.qml UserAgreementPopup 1.0 UserAgreementPopup.qml +MetricsEnablePopup 1.0 MetricsEnablePopup.qml UserStatusContextMenu 1.0 UserStatusContextMenu.qml ImageContextMenu 1.0 ImageContextMenu.qml diff --git a/ui/imports/shared/stores/MetricsStore.qml b/ui/imports/shared/stores/MetricsStore.qml new file mode 100644 index 0000000000..b0f94b61ef --- /dev/null +++ b/ui/imports/shared/stores/MetricsStore.qml @@ -0,0 +1,13 @@ +import QtQml 2.15 + +QtObject { + id: root + + function toggleCentralizedMetrics(enabled) { + metrics.toggleCentralizedMetrics(enabled) + } + + function isCentralizedMetricsEnabled() { + return metrics.isCentralizedMetricsEnabled() + } +} diff --git a/ui/imports/shared/stores/qmldir b/ui/imports/shared/stores/qmldir index 4a4d78ba72..4e8b5ada5b 100644 --- a/ui/imports/shared/stores/qmldir +++ b/ui/imports/shared/stores/qmldir @@ -6,5 +6,6 @@ ChartStoreBase 1.0 ChartStoreBase.qml PermissionsStore 1.0 PermissionsStore.qml TokenBalanceHistoryStore 1.0 TokenBalanceHistoryStore.qml TokenMarketValuesStore 1.0 TokenMarketValuesStore.qml +MetricsStore 1.0 MetricsStore.qml NetworkConnectionStore 1.0 NetworkConnectionStore.qml -DAppsStore 1.0 DAppsStore.qml \ No newline at end of file +DAppsStore 1.0 DAppsStore.qml diff --git a/ui/imports/utils/Constants.qml b/ui/imports/utils/Constants.qml index 2ed019f705..56ba376bba 100644 --- a/ui/imports/utils/Constants.qml +++ b/ui/imports/utils/Constants.qml @@ -349,10 +349,11 @@ QtObject { readonly property int keycard: 14 readonly property int about_terms: 15 // a subpage under "About" readonly property int about_privacy: 16 // a subpage under "About" + readonly property int privacyAndSecurity: 17 // special treatment; these do not participate in the main settings' StackLayout - readonly property int signout: 17 - readonly property int backUpSeed: 18 + readonly property int signout: 18 + readonly property int backUpSeed: 19 } readonly property QtObject walletSettingsSubsection: QtObject {