From d0658feb2674a3c9b4f1d132087b7a51c2c2b76b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Tinkl?= Date: Mon, 29 Apr 2024 14:02:19 +0200 Subject: [PATCH] feat: disable language/translations selection - make it conditionally available for testing via Settings/Advanced/Enable translations - display a tooltip and infobox explaining the current and future situation - upon app start, force the language to "en" if the translations are not enabled; and suggest app restart when they get disabled Fixes #14527 --- src/app/global/local_app_settings.nim | 54 ++++++++++++------- src/nim_status_client.nim | 6 +++ ...ySettingsPage.qml => LanguageViewPage.qml} | 34 ++++++++---- .../StatusQ/Components/StatusListPicker.qml | 6 ++- .../StatusQ/Controls/StatusPickerButton.qml | 28 ++++------ .../src/StatusQ/Controls/StatusWarningBox.qml | 17 +++--- ui/app/AppLayouts/Profile/ProfileLayout.qml | 1 + .../AppLayouts/Profile/views/AdvancedView.qml | 26 ++++++++- .../AppLayouts/Profile/views/LanguageView.qml | 53 +++++++++++++----- 9 files changed, 157 insertions(+), 68 deletions(-) rename storybook/pages/{LanguageCurrencySettingsPage.qml => LanguageViewPage.qml} (82%) diff --git a/src/app/global/local_app_settings.nim b/src/app/global/local_app_settings.nim index 43a1b25bc..69d6a9938 100644 --- a/src/app/global/local_app_settings.nim +++ b/src/app/global/local_app_settings.nim @@ -4,7 +4,7 @@ import ../../constants # Local App Settings keys: const LAS_KEY_LANGUAGE* = "global/language" -const DEFAULT_LOCALE = "en" +const DEFAULT_LAS_KEY_LANGUAGE* = "en" const LAS_KEY_THEME* = "global/theme" const DEFAULT_THEME = 2 #system theme, from qml const LAS_KEY_GEOMETRY = "global/app_geometry" @@ -20,6 +20,8 @@ const LAS_KEY_FAKE_LOADING_SCREEN_ENABLED = "global/fake_loading_screen" let DEFAULT_FAKE_LOADING_SCREEN_ENABLED = defined(production) and not TEST_MODE_ENABLED #enabled in production, disabled in development and e2e tests const LAS_KEY_SHARDED_COMMUNITIES_ENABLED = "global/sharded_communities" const DEFAULT_LAS_KEY_SHARDED_COMMUNITIES_ENABLED = false +const LAS_KEY_TRANSLATIONS_ENABLED = "global/translations_enabled" +const DEFAULT_LAS_KEY_TRANSLATIONS_ENABLED = false QtObject: type LocalAppSettings* = ref object of QObject @@ -42,7 +44,7 @@ QtObject: proc languageChanged*(self: LocalAppSettings) {.signal.} proc getLanguage*(self: LocalAppSettings): string {.slot.} = - self.settings.value(LAS_KEY_LANGUAGE, newQVariant(DEFAULT_LOCALE)).stringVal + self.settings.value(LAS_KEY_LANGUAGE, newQVariant(DEFAULT_LAS_KEY_LANGUAGE)).stringVal proc setLanguage*(self: LocalAppSettings, value: string) {.slot.} = self.settings.setValue(LAS_KEY_LANGUAGE, newQVariant(value)) self.languageChanged() @@ -127,22 +129,6 @@ QtObject: write = setScrollDeceleration notify = scrollDecelerationChanged - proc removeKey*(self: LocalAppSettings, key: string) = - if(self.settings.isNil): - return - - self.settings.remove(key) - - case key: - of LAS_KEY_LANGUAGE: self.languageChanged() - of LAS_KEY_THEME: self.themeChanged() - of LAS_KEY_GEOMETRY: self.geometryChanged() - of LAS_KEY_VISIBILITY: self.visibilityChanged() - of LAS_KEY_SCROLL_VELOCITY: self.scrollVelocityChanged() - of LAS_KEY_SCROLL_DECELERATION: self.scrollDecelerationChanged() - of LAS_KEY_CUSTOM_MOUSE_SCROLLING_ENABLED: self.isCustomMouseScrollingEnabledChanged() - - proc getTestEnvironment*(self: LocalAppSettings): bool {.slot.} = return TEST_MODE_ENABLED @@ -174,3 +160,35 @@ QtObject: read = getWakuV2ShardedCommunitiesEnabled write = setWakuV2ShardedCommunitiesEnabled notify = wakuV2ShardedCommunitiesEnabledChanged + + proc translationsEnabledChanged*(self: LocalAppSettings) {.signal.} + proc getTranslationsEnabled*(self: LocalAppSettings): bool {.slot.} = + self.settings.value(LAS_KEY_TRANSLATIONS_ENABLED, newQVariant(DEFAULT_LAS_KEY_TRANSLATIONS_ENABLED)).boolVal + proc setTranslationsEnabled*(self: LocalAppSettings, value: bool) {.slot.} = + if value == self.getTranslationsEnabled: + return + self.settings.setValue(LAS_KEY_TRANSLATIONS_ENABLED, newQVariant(value)) + self.translationsEnabledChanged() + + QtProperty[bool] translationsEnabled: + read = getTranslationsEnabled + write = setTranslationsEnabled + notify = translationsEnabledChanged + + proc removeKey*(self: LocalAppSettings, key: string) = + if(self.settings.isNil): + return + + self.settings.remove(key) + + case key: + of LAS_KEY_LANGUAGE: self.languageChanged() + of LAS_KEY_THEME: self.themeChanged() + of LAS_KEY_GEOMETRY: self.geometryChanged() + of LAS_KEY_VISIBILITY: self.visibilityChanged() + of LAS_KEY_SCROLL_VELOCITY: self.scrollVelocityChanged() + of LAS_KEY_SCROLL_DECELERATION: self.scrollDecelerationChanged() + of LAS_KEY_CUSTOM_MOUSE_SCROLLING_ENABLED: self.isCustomMouseScrollingEnabledChanged() + of LAS_KEY_FAKE_LOADING_SCREEN_ENABLED: self.fakeLoadingScreenEnabledChanged() + of LAS_KEY_SHARDED_COMMUNITIES_ENABLED: self.wakuV2ShardedCommunitiesEnabledChanged() + of LAS_KEY_TRANSLATIONS_ENABLED: self.translationsEnabledChanged() diff --git a/src/nim_status_client.nim b/src/nim_status_client.nim index e5a3a64a5..156b1627c 100644 --- a/src/nim_status_client.nim +++ b/src/nim_status_client.nim @@ -6,6 +6,7 @@ import app/core/main import constants as main_constants import app/global/global_singleton +import app/global/local_app_settings import app/boot/app_controller when defined(macosx) and defined(arm64): @@ -132,6 +133,11 @@ proc mainProc() = let app = newQGuiApplication() + # force default language ("en") if not "Settings/Advanced/Enable translations" + if not singletonInstance.localAppSettings.getTranslationsEnabled(): + if singletonInstance.localAppSettings.getLanguage() != DEFAULT_LAS_KEY_LANGUAGE: + singletonInstance.localAppSettings.setLanguage(DEFAULT_LAS_KEY_LANGUAGE) + # Required by the WalletConnectSDK view right after creating the QGuiApplication instance initializeWebView() diff --git a/storybook/pages/LanguageCurrencySettingsPage.qml b/storybook/pages/LanguageViewPage.qml similarity index 82% rename from storybook/pages/LanguageCurrencySettingsPage.qml rename to storybook/pages/LanguageViewPage.qml index cabbff18e..d334b885b 100644 --- a/storybook/pages/LanguageCurrencySettingsPage.qml +++ b/storybook/pages/LanguageViewPage.qml @@ -8,10 +8,19 @@ import AppLayouts.Profile.stores 1.0 import Storybook 1.0 import utils 1.0 +import mainui 1.0 SplitView { + id: root + Logs { id: logs } + Popups { + popupParent: root + rootStore: QtObject {} + communityTokensStore: QtObject {} + } + SplitView { orientation: Qt.Vertical SplitView.fillWidth: true @@ -19,8 +28,9 @@ SplitView { LanguageView { SplitView.fillWidth: true SplitView.fillHeight: true - contentWidth: parent.width + contentWidth: parent.width - 150 + languageSelectionEnabled: ctrlLanguageSelectionEnabled.checked languageStore: LanguageStore { property string currentLanguage: "en" @@ -41,6 +51,14 @@ SplitView { state: 2 selected: true } + ListElement { + locale: "cs_CZ" + name: "Czech" + shortName: "čeština" + flag: "🇨🇿" + state: 1 + selected: false + } } function changeLanguage(language) { @@ -92,17 +110,13 @@ SplitView { SplitView.preferredHeight: 200 logsView.logText: logs.logText + + Switch { + id: ctrlLanguageSelectionEnabled + text: "Language selection enabled" + } } } - - Control { - SplitView.minimumWidth: 300 - SplitView.preferredWidth: 300 - - font.pixelSize: 13 - - // model editor will go here - } } // category: Components diff --git a/ui/StatusQ/src/StatusQ/Components/StatusListPicker.qml b/ui/StatusQ/src/StatusQ/Components/StatusListPicker.qml index bd56b8759..18e83acb8 100644 --- a/ui/StatusQ/src/StatusQ/Components/StatusListPicker.qml +++ b/ui/StatusQ/src/StatusQ/Components/StatusListPicker.qml @@ -118,9 +118,11 @@ Item { */ property int menuAlignment: StatusListPicker.MenuAlignment.Right + readonly property alias button: btn + /*! \qmlproperty enum StatusListPicker::MenuAlignment - This property holds the allignment of the menu in terms of the button + This property holds the alignment of the menu in terms of the button values can be Left, Right or Center */ enum MenuAlignment { @@ -216,7 +218,7 @@ Item { contentColor: Theme.palette.primaryColor1 text: d.selectedItemsText font.pixelSize: 13 - type: StatusPickerButton.Type.Down + type: StatusPickerButton.PickerType.Down onClicked: { picker.visible = !picker.visible diff --git a/ui/StatusQ/src/StatusQ/Controls/StatusPickerButton.qml b/ui/StatusQ/src/StatusQ/Controls/StatusPickerButton.qml index 1c09ffb1d..53d8960ba 100644 --- a/ui/StatusQ/src/StatusQ/Controls/StatusPickerButton.qml +++ b/ui/StatusQ/src/StatusQ/Controls/StatusPickerButton.qml @@ -5,71 +5,65 @@ import QtQuick.Layouts 1.15 import StatusQ.Core 0.1 import StatusQ.Core.Theme 0.1 import StatusQ.Components 0.1 +import StatusQ.Controls 0.1 -Button { +StatusButton { id: root property color bgColor: Theme.palette.baseColor2 property color contentColor: Theme.palette.baseColor1 - property var type: StatusPickerButton.Type.Next + property int type: StatusPickerButton.PickerType.Next /*! \qmlproperty StatusAssetSettings StatusPickerButton::asset This property holds the image settings information. */ - property StatusAssetSettings asset: StatusAssetSettings { + asset { width: 20 height: 20 imgIsIdenticon: false } - enum Type { + enum PickerType { Next, Down } - implicitWidth: 446 - implicitHeight: 44 - font.pixelSize: 15 horizontalPadding: 16 + verticalPadding: 3 spacing: 4 icon.width: 16 icon.height: 16 - background:Rectangle { + background: Rectangle { radius: 8 color: root.bgColor } + opacity: !root.interactive || !root.enabled ? 0.5 : 1 contentItem: RowLayout { - clip: true spacing: root.spacing StatusIcon { icon: "tiny/chevron-down" - visible: root.type === StatusPickerButton.Type.Down - Layout.alignment: Qt.AlignVCenter + visible: root.type === StatusPickerButton.PickerType.Down color: !Qt.colorEqual(root.contentColor, Theme.palette.baseColor1) ? root.contentColor : Theme.palette.directColor1 width: root.icon.width height: root.icon.height } StatusRoundedImage { visible: root.asset.name.toString() !== "" - Layout.alignment: Qt.AlignVCenter Layout.preferredWidth: root.asset.width Layout.preferredHeight: root.asset.height image.source: root.asset.name } StatusBaseText { Layout.fillWidth: true - Layout.alignment: Qt.AlignVCenter - font.pixelSize: root.font.pixelSize + font: root.font color: root.contentColor text: root.text - clip: true elide: Text.ElideRight } StatusIcon { icon: "tiny/chevron-right" - visible: root.type === StatusPickerButton.Type.Next - Layout.alignment: Qt.AlignVCenter + visible: root.type === StatusPickerButton.PickerType.Next color: !Qt.colorEqual(root.contentColor, Theme.palette.baseColor1) ? root.contentColor : Theme.palette.directColor1 width: root.icon.width height: root.icon.height diff --git a/ui/StatusQ/src/StatusQ/Controls/StatusWarningBox.qml b/ui/StatusQ/src/StatusQ/Controls/StatusWarningBox.qml index 1954cca1e..cb199e4d1 100644 --- a/ui/StatusQ/src/StatusQ/Controls/StatusWarningBox.qml +++ b/ui/StatusQ/src/StatusQ/Controls/StatusWarningBox.qml @@ -10,9 +10,9 @@ import StatusQ.Core.Theme 0.1 \inqmlmodule StatusQ.Controls \since StatusQ.Controls 0.1 \brief Displays a customizable WarningBox component. - Inherits \l{https://doc.qt.io/qt-5/qml-qtquick-controls2-control.html}{Item}. + Inherits \l{https://doc.qt.io/qt-5/qml-qtquick-controls2-control.html}{Control}. - The \c StatusWarningBox displays an customizable WarningBox for users to show an icon and text. + The \c StatusWarningBox displays a customizable WarningBox for users to show an icon and text. For example: \qml @@ -30,7 +30,6 @@ import StatusQ.Core.Theme 0.1 Control { id: root - implicitWidth: 614 padding: 16 /*! @@ -70,6 +69,12 @@ Control { */ property int textSize: Theme.primaryTextFontSize + /*! + \qmlproperty Component StatusWarningBox::extraContentComponent + This property lets you add some extra component on the trailing side (like a button) + */ + property alias extraContentComponent: extraContent.sourceComponent + background: Rectangle { radius: 8 opacity: 0.5 @@ -93,12 +98,12 @@ Control { StatusBaseText { id: warningText Layout.fillWidth: true - Layout.preferredHeight: contentHeight wrapMode: Text.WordWrap - verticalAlignment: Text.AlignVCenter font.pixelSize: root.textSize color: root.textColor } + Loader { + id: extraContent + } } } - diff --git a/ui/app/AppLayouts/Profile/ProfileLayout.qml b/ui/app/AppLayouts/Profile/ProfileLayout.qml index 20e1686c2..37c6ba98f 100644 --- a/ui/app/AppLayouts/Profile/ProfileLayout.qml +++ b/ui/app/AppLayouts/Profile/ProfileLayout.qml @@ -278,6 +278,7 @@ StatusSectionLayout { implicitWidth: parent.width implicitHeight: parent.height + languageSelectionEnabled: localAppSettings.translationsEnabled languageStore: root.store.languageStore currencyStore: root.currencyStore sectionTitle: root.store.getNameForSubsection(Constants.settingsSubsection.language) diff --git a/ui/app/AppLayouts/Profile/views/AdvancedView.qml b/ui/app/AppLayouts/Profile/views/AdvancedView.qml index 7feb16649..5e8128f98 100644 --- a/ui/app/AppLayouts/Profile/views/AdvancedView.qml +++ b/ui/app/AppLayouts/Profile/views/AdvancedView.qml @@ -339,6 +339,30 @@ SettingsContentBase { width: parent.width } + StatusSettingsLineButton { + anchors.leftMargin: 0 + anchors.rightMargin: 0 + text: qsTr("Enable translations") + isSwitch: true + switchChecked: localAppSettings.translationsEnabled + onClicked: { + localAppSettings.translationsEnabled = !localAppSettings.translationsEnabled + if (!checked) + Global.openPopup(disableLanguagesPopupComponent) + } + } + + Component { + id: disableLanguagesPopupComponent + ConfirmationDialog { + destroyOnClose: true + headerSettings.title: qsTr("Language reset") + confirmationText: qsTr("Display language will be switched back to English. You must restart the application for changes to take effect.") + confirmButtonLabel: qsTr("Restart") + onConfirmButtonClicked: Utils.restartApplication() + } + } + // TODO: replace with StatusQ component StatusSettingsLineButton { anchors.leftMargin: 0 @@ -479,7 +503,7 @@ SettingsContentBase { onConfirmButtonClicked: { root.advancedStore.toggleIsGoerliEnabled() close() - Qt.quit() + Utils.restartApplication() } onCancelButtonClicked: { close() diff --git a/ui/app/AppLayouts/Profile/views/LanguageView.qml b/ui/app/AppLayouts/Profile/views/LanguageView.qml index 4deef6037..31c0064b2 100644 --- a/ui/app/AppLayouts/Profile/views/LanguageView.qml +++ b/ui/app/AppLayouts/Profile/views/LanguageView.qml @@ -1,11 +1,12 @@ -import QtQuick 2.13 -import QtQuick.Controls 2.13 -import QtQuick.Layouts 1.13 +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 StatusQ 0.1 import StatusQ.Core 0.1 import StatusQ.Core.Theme 0.1 import StatusQ.Core.Utils 0.1 as StatusQUtils @@ -23,6 +24,7 @@ SettingsContentBase { property LanguageStore languageStore property var currencyStore + property bool languageSelectionEnabled objectName: "languageView" onVisibleChanged: { if(!visible) root.setViewIdleState()} @@ -93,6 +95,16 @@ SettingsContentBase { Item { Layout.fillWidth: true } StatusListPicker { id: languagePicker + + button.interactive: root.languageSelectionEnabled + StatusToolTip { + y: parent.height + Style.current.padding + margins: 0 + visible: !root.languageSelectionEnabled && languagePicker.button.hovered + orientation: StatusToolTip.Orientation.Bottom + text: qsTr("Translations coming soon") + } + property string newKey function descriptionForState(state) { @@ -105,13 +117,13 @@ SettingsContentBase { inputList: SortFilterProxyModel { sourceModel: root.languageStore.languageModel - // !Don't use proxy roles cause they harm performance a lot! // "category" is the only role that can't be mocked by StatusListPicker::proxy // due to StatusListPicker internal implementation limitation (ListView's section.property) proxyRoles: [ - ExpressionRole { + FastExpressionRole { name: "category" expression: languagePicker.descriptionForState(model.state) + expectedRoles: ["state"] } ] @@ -129,7 +141,7 @@ SettingsContentBase { proxy { key: (model) => model.locale name: (model) => model.name - shortName: (model) => model.native + shortName: (model) => model.native || model.shortName symbol: (model) => "" imageSource: (model) => StatusQUtils.Emoji.iconSource(model.flag) selected: (model) => model.locale === root.languageStore.currentLanguage @@ -143,13 +155,28 @@ SettingsContentBase { onItemPickerChanged: { if(selected && root.languageStore.currentLanguage !== key) { root.changeLanguage(key) - languageConfirmationDialog.active = true - languageConfirmationDialog.item.open() + Global.openPopup(languageConfirmationDialog) } } } } + StatusWarningBox { + Layout.fillWidth: true + Layout.bottomMargin: Style.current.padding + borderColor: Theme.palette.baseColor2 + textColor: Theme.palette.directColor1 + icon: "group-chat" + iconColor: Theme.palette.baseColor1 + text: qsTr("We need your help to translate Status, so that together we can bring privacy and free speech to the people everywhere, including those who need it most.") + extraContentComponent: StatusFlatButton { + icon.name: "external-link" + text: qsTr("Learn more") + size: StatusBaseButton.Size.Small + onClicked: Global.openLinkWithConfirmation(Constants.externalStatusLinkWithHttps + '/translations', Constants.externalStatusLinkWithHttps) + } + } + Separator { Layout.fillWidth: true Layout.bottomMargin: Style.current.padding @@ -158,8 +185,7 @@ SettingsContentBase { // Time format options: Column { Layout.fillWidth: true - Layout.topMargin: Style.current.padding - spacing: Style.current.padding + spacing: Constants.settingsSection.itemSpacing StatusBaseText { text: qsTr("Time Format") } @@ -183,15 +209,14 @@ SettingsContentBase { } } - Loader { + Component { id: languageConfirmationDialog - active: false - sourceComponent: ConfirmationDialog { + ConfirmationDialog { + destroyOnClose: true headerSettings.title: qsTr("Change language") confirmationText: qsTr("Display language has been changed. You must restart the application for changes to take effect.") confirmButtonLabel: qsTr("Restart") onConfirmButtonClicked: { - languageConfirmationDialog.active = false Utils.restartApplication() } }