From e68895bce94091dbdc80a73b4470d878d82c1ba3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Tinkl?= Date: Mon, 20 Oct 2025 20:12:27 +0200 Subject: [PATCH 01/16] chore(Storybook): update macOS min version - raise to 14.0 like everywhere else --- storybook/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/storybook/CMakeLists.txt b/storybook/CMakeLists.txt index 00aacd5104d..896f9800f6d 100644 --- a/storybook/CMakeLists.txt +++ b/storybook/CMakeLists.txt @@ -29,7 +29,7 @@ if(MSVC) endif() if (APPLE) - set(MACOS_VERSION_MIN_FLAGS -mmacosx-version-min=11.0) + set(MACOS_VERSION_MIN_FLAGS -mmacosx-version-min=14.0) endif() find_package(QT NAMES Qt5 Qt6 REQUIRED COMPONENTS Core) From 5c42928553f11611a70983b8d35a033120425944 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Tinkl?= Date: Mon, 20 Oct 2025 20:16:53 +0200 Subject: [PATCH 02/16] chore(i18n): ignore generated files, update TS - ignore TS files inside build dirs; they are most likely not ours --- ui/i18n/CMakeLists.txt | 1 + ui/i18n/qml_base_en.ts | 11 +++++++++++ ui/i18n/qml_base_lokalise_en.ts | 13 +++++++++++++ ui/i18n/qml_cs.ts | 11 +++++++++++ ui/i18n/qml_ko.ts | 11 +++++++++++ 5 files changed, 47 insertions(+) diff --git a/ui/i18n/CMakeLists.txt b/ui/i18n/CMakeLists.txt index fd8a8dedb0f..dcc625d67ab 100644 --- a/ui/i18n/CMakeLists.txt +++ b/ui/i18n/CMakeLists.txt @@ -13,6 +13,7 @@ file(GLOB_RECURSE COLLECTED_SOURCE_FILES ${CMAKE_SOURCE_DIR}/../*.qml ) +list(FILTER COLLECTED_SOURCE_FILES EXCLUDE REGEX "${CMAKE_SOURCE_DIR}/../StatusQ/build/.*" ) qt6_add_lupdate( LUPDATE_TARGET update_application_translations # name of the cmake target diff --git a/ui/i18n/qml_base_en.ts b/ui/i18n/qml_base_en.ts index 7662ee17ec9..fea42423143 100644 --- a/ui/i18n/qml_base_en.ts +++ b/ui/i18n/qml_base_en.ts @@ -7691,6 +7691,13 @@ Please add it and try again. + + FeeRow + + Max. + + + FeesBox @@ -8974,6 +8981,10 @@ Are you sure you want to do this? PIN correct + + Keycard blocked + + %n attempt(s) remaining diff --git a/ui/i18n/qml_base_lokalise_en.ts b/ui/i18n/qml_base_lokalise_en.ts index 4a89e0ecd27..a0ad2a42742 100644 --- a/ui/i18n/qml_base_lokalise_en.ts +++ b/ui/i18n/qml_base_lokalise_en.ts @@ -9394,6 +9394,14 @@ Remove + + FeeRow + + Max. + FeeRow + Max. + + FeesBox @@ -10944,6 +10952,11 @@ KeycardEnterPinPage PIN correct + + Keycard blocked + KeycardEnterPinPage + Keycard blocked + %n attempt(s) remaining KeycardEnterPinPage diff --git a/ui/i18n/qml_cs.ts b/ui/i18n/qml_cs.ts index 82a790d59b0..308efe5b068 100644 --- a/ui/i18n/qml_cs.ts +++ b/ui/i18n/qml_cs.ts @@ -7718,6 +7718,13 @@ Please add it and try again. Odstranit + + FeeRow + + Max. + + + FeesBox @@ -9010,6 +9017,10 @@ Are you sure you want to do this? PIN correct + + Keycard blocked + + %n attempt(s) remaining diff --git a/ui/i18n/qml_ko.ts b/ui/i18n/qml_ko.ts index 589856af3ab..94efdfeb37a 100644 --- a/ui/i18n/qml_ko.ts +++ b/ui/i18n/qml_ko.ts @@ -7664,6 +7664,13 @@ Please add it and try again. 제거 + + FeeRow + + Max. + + + FeesBox @@ -8939,6 +8946,10 @@ Are you sure you want to do this? PIN correct PIN이 올바릅니다 + + Keycard blocked + Keycard가 차단됨 + %n attempt(s) remaining From 2e0fb1866d14d5ad227a8f7b45f514a4325a2041 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Tinkl?= Date: Mon, 20 Oct 2025 20:22:53 +0200 Subject: [PATCH 03/16] fix(Onboarding): add a bottom margin for the SafeArea Fixes #19074 --- ui/main.qml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ui/main.qml b/ui/main.qml index 21c868ceaa0..5b89401d3d2 100644 --- a/ui/main.qml +++ b/ui/main.qml @@ -441,6 +441,7 @@ StatusWindow { anchors.topMargin: Qt.platform.os === SQUtils.Utils.mac ? 0 : parent.SafeArea.margins.top anchors.leftMargin: parent.SafeArea.margins.left anchors.rightMargin: parent.SafeArea.margins.right + anchors.bottomMargin: parent.SafeArea.margins.bottom sourceComponent: onboardingV2 } @@ -479,8 +480,6 @@ StatusWindow { id: onboardingLayout objectName: "startupOnboardingLayout" - bottomPadding: applicationWindow.SafeArea.margins.bottom - isKeycardEnabled: featureFlagsStore.keycardEnabled networkChecksEnabled: true From a06d20b4d3b47de2c9db1d08f5e41dafbf543f30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Tinkl?= Date: Mon, 20 Oct 2025 20:26:06 +0200 Subject: [PATCH 04/16] fix(LoginScreen): don't use bottom sheet for profile selector --- ui/app/AppLayouts/Onboarding/controls/LoginUserSelector.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/app/AppLayouts/Onboarding/controls/LoginUserSelector.qml b/ui/app/AppLayouts/Onboarding/controls/LoginUserSelector.qml index 5a36dd0d142..dd93d8c1865 100644 --- a/ui/app/AppLayouts/Onboarding/controls/LoginUserSelector.qml +++ b/ui/app/AppLayouts/Onboarding/controls/LoginUserSelector.qml @@ -90,7 +90,7 @@ Control { id: dropdown objectName: "dropdown" - closePolicy: Popup.CloseOnPressOutsideParent | Popup.CloseOnEscape + bottomSheetAllowed: false directParent: root relativeY: root.height + 2 From 2beedb9efd1597173789e6486dc3bf9551992c11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Tinkl?= Date: Mon, 20 Oct 2025 20:28:01 +0200 Subject: [PATCH 05/16] fix(HomePage): Home Screen uses around 200MB of memory - replace Glow with RectangularShadow effect, the latter being faster and less resource hungry Fixes #19086 --- .../HomePage/delegates/HomePageGridItem.qml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/ui/app/AppLayouts/HomePage/delegates/HomePageGridItem.qml b/ui/app/AppLayouts/HomePage/delegates/HomePageGridItem.qml index 713ba74de71..48a5857d329 100644 --- a/ui/app/AppLayouts/HomePage/delegates/HomePageGridItem.qml +++ b/ui/app/AppLayouts/HomePage/delegates/HomePageGridItem.qml @@ -1,7 +1,7 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts -import Qt5Compat.GraphicalEffects +import QtQuick.Effects import StatusQ.Core import StatusQ.Controls @@ -43,9 +43,12 @@ AbstractButton { signal pinRequested - layer.enabled: true - layer.effect: Glow { - samples: 33 + RectangularShadow { + anchors.fill: background + z: background.z - 1 + offset.x: 5 + offset.y: 10 + radius: Theme.defaultPadding spread: 0.1 color: root.hovered ? Theme.palette.backdropColor : Theme.palette.dropShadow Behavior on color { ColorAnimation { duration: Theme.AnimationDuration.Fast } } From ab7aead8ca894b49ba3588c0e3f46dc37a5ca6b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Tinkl?= Date: Mon, 20 Oct 2025 20:30:54 +0200 Subject: [PATCH 06/16] fix(StatusToolTip): tooltips are shown over multiple lines - fixup the size calculations - don't use hardcoded metrics Fixes #19059 --- .../src/StatusQ/Controls/StatusToolTip.qml | 31 ++++++++++++------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/ui/StatusQ/src/StatusQ/Controls/StatusToolTip.qml b/ui/StatusQ/src/StatusQ/Controls/StatusToolTip.qml index 521a4fb9ead..a548aa92192 100644 --- a/ui/StatusQ/src/StatusQ/Controls/StatusToolTip.qml +++ b/ui/StatusQ/src/StatusQ/Controls/StatusToolTip.qml @@ -2,10 +2,11 @@ import QtQuick import QtQuick.Controls import StatusQ.Core +import StatusQ.Core.Utils import StatusQ.Core.Theme ToolTip { - id: statusToolTip + id: root enum Orientation { Top, @@ -20,18 +21,24 @@ ToolTip { property alias arrow: arrow property color color: Theme.palette.black - implicitWidth: Math.min(maxWidth, implicitContentWidth + 16) - padding: 8 - margins: 8 - delay: 200 + implicitWidth: Math.min(maxWidth, + Math.max(implicitBackgroundWidth + leftInset + rightInset, + implicitContentWidth + leftPadding + rightPadding) + ) + padding: Theme.halfPadding + horizontalPadding: padding + 4 + margins: Theme.halfPadding + delay: Utils.isMobile ? Application.styleHints.mousePressAndHoldInterval + : 200 + background: Item { id: statusToolTipBackground Rectangle { id: statusToolTipContentBackground - color: statusToolTip.color - radius: 8 + color: root.color + radius: Theme.radius anchors.fill: parent - anchors.bottomMargin: 8 + anchors.bottomMargin: Theme.halfPadding } Rectangle { id: arrow @@ -45,10 +52,10 @@ ToolTip { return statusToolTipBackground.width / 2 - width / 2 + offset } if (orientation === StatusToolTip.Orientation.Left) { - return statusToolTipContentBackground.width - (width / 2) - 8 + offset + return statusToolTipContentBackground.width - (width / 2) - root.padding + offset } if (orientation === StatusToolTip.Orientation.Right) { - return -width/2 + 8 + offset + return -width/2 + root.padding + offset } } y: { @@ -65,14 +72,14 @@ ToolTip { } } contentItem: StatusBaseText { - text: statusToolTip.text + text: root.text color: Theme.palette.white linkColor: Theme.palette.white wrapMode: Text.Wrap font.pixelSize: Theme.additionalTextSize font.weight: Font.Medium horizontalAlignment: Text.AlignHCenter - bottomPadding: 8 + bottomPadding: Theme.halfPadding textFormat: Text.RichText elide: Text.ElideRight } From 3530bbfa57ff9a107ffafb67335f34eddcad1492 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Tinkl?= Date: Mon, 20 Oct 2025 20:36:15 +0200 Subject: [PATCH 07/16] fix(Wallet Settings) Token lists show 0 tokens - fill the popup properties in `Instantiator.onObjectAdded` - remove unused signal Fixes #19071 --- .../pages/SupportedTokenListsPanelPage.qml | 4 -- .../panels/SupportedTokenListsPanel.qml | 51 ++++++++++--------- 2 files changed, 26 insertions(+), 29 deletions(-) diff --git a/storybook/pages/SupportedTokenListsPanelPage.qml b/storybook/pages/SupportedTokenListsPanelPage.qml index add231f65c8..e05bff0370a 100644 --- a/storybook/pages/SupportedTokenListsPanelPage.qml +++ b/storybook/pages/SupportedTokenListsPanelPage.qml @@ -6,7 +6,6 @@ import Storybook import Models import AppLayouts.Profile.panels -import AppLayouts.Profile.stores import StatusQ @@ -39,7 +38,6 @@ SplitView { Logs { id: logs } - Pane { SplitView.fillWidth: true SplitView.fillHeight: true @@ -48,8 +46,6 @@ SplitView { anchors.fill: parent sourcesOfTokensModel: root.sourcesOfTokensModel tokensListModel: root.tokensProxyModel - - onItemClicked: logs.logEvent("SupportedTokenListsPanel::onItemClicked --> Key --> " + key) } } diff --git a/ui/app/AppLayouts/Profile/panels/SupportedTokenListsPanel.qml b/ui/app/AppLayouts/Profile/panels/SupportedTokenListsPanel.qml index 83fa36f10fc..d422bac371b 100644 --- a/ui/app/AppLayouts/Profile/panels/SupportedTokenListsPanel.qml +++ b/ui/app/AppLayouts/Profile/panels/SupportedTokenListsPanel.qml @@ -6,7 +6,6 @@ import StatusQ.Core import StatusQ.Components import StatusQ.Controls import StatusQ.Core.Theme -import StatusQ.Core.Utils as SQUtils import SortFilterProxyModel import shared.controls @@ -20,11 +19,10 @@ StatusListView { required property var sourcesOfTokensModel // Expected roles: key, name, updatedAt, source, version, tokensCount, image required property var tokensListModel // Expected roles: name, symbol, image, chainName, explorerUrl - signal itemClicked(string key) - implicitHeight: contentHeight model: root.sourcesOfTokensModel spacing: Theme.halfPadding + delegate: StatusListItem { height: ProfileUtils.defaultDelegateHeight width: ListView.view.width @@ -65,36 +63,39 @@ StatusListView { required property string name required property string image required property string source - required property int updatedAt + required property double updatedAt required property string version required property int tokensCount - - Component.onCompleted: popup.open() + } + onObjectAdded: function(index, delegate) { + popupComp.createObject(root, { + title: delegate.name, + sourceImage: delegate.image, + sourceUrl: delegate.source, + sourceVersion: delegate.version, + updatedAt: delegate.updatedAt, + tokensCount: delegate.tokensCount + }).open() } } - TokenListPopup { - id: popup + Component { + id: popupComp + TokenListPopup { + destroyOnClose: true - sourceImage: delegate.image - sourceUrl: delegate.source - sourceVersion: delegate.version - updatedAt: delegate.updatedAt - tokensCount: delegate.tokensCount + tokensListModel: SortFilterProxyModel { + sourceModel: root.tokensListModel - title: delegate.name - - tokensListModel: SortFilterProxyModel { - sourceModel: root.tokensListModel - - // Filter by source - filters: RegExpFilter { - roleName: "sources" - pattern: "\;" + keyFilter.value + "\;" + // Filter by source + filters: RegExpFilter { + roleName: "sources" + pattern: "\;" + keyFilter.value + "\;" + } } - } - onLinkClicked: (link) => Global.openLink(link) - onClosed: keyFilter.value = "" + onLinkClicked: (link) => Global.openLink(link) + onClosed: keyFilter.value = "" + } } } From af480d3e254eeb86842c74d9df3ef9e6d049f232 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Tinkl?= Date: Mon, 20 Oct 2025 20:38:50 +0200 Subject: [PATCH 08/16] fix(StatusNavBarTabButton): handle the popup menu using `ContextMenu` - fixes the Mute submenu being closed prematurely due to a default close handler; on Android the submenus do not cascade, so once `this` menu gets closed/replaced by the other submenu, the `closeHandler` used to kill the whole hierarchy - simplifies the context menu code a great deal; reacts to both mouse right click and touch longpress events - add an example to StoryBook Fixes #19070 --- storybook/pages/StatusNavBarTabButtonPage.qml | 36 +++++++++++++++++ .../Controls/StatusNavBarTabButton.qml | 40 +++++-------------- .../chat/menuItems/MuteChatMenuItem.qml | 2 - 3 files changed, 45 insertions(+), 33 deletions(-) diff --git a/storybook/pages/StatusNavBarTabButtonPage.qml b/storybook/pages/StatusNavBarTabButtonPage.qml index f9ef772b2d4..eda7d0b7bb4 100644 --- a/storybook/pages/StatusNavBarTabButtonPage.qml +++ b/storybook/pages/StatusNavBarTabButtonPage.qml @@ -4,6 +4,9 @@ import QtQuick.Controls import StatusQ.Controls import StatusQ.Components.private import StatusQ.Core.Theme +import StatusQ.Popups + +import shared.controls.chat.menuItems import Models import Storybook @@ -13,6 +16,7 @@ SplitView { ButtonGroup { buttons: column.children + onClicked: button => console.info("Clicked button:", button.tooltip.text) } Rectangle { @@ -82,6 +86,38 @@ SplitView { // when: ctrlNewBadgeGradient.checked // } } + StatusNavBarTabButton { + icon.name: "info" + tooltip.text: "With context menu" + thirdpartyServicesEnabled: thirdpartyServicesCtrl.checked + popupMenu: popupMenuComp + } + } + } + + Component { + id: popupMenuComp + StatusMenu { + StatusAction { + text: qsTr("Invite People") + icon.name: "share-ios" + } + + StatusAction { + text: qsTr("Community Info") + icon.name: "info" + } + + StatusAction { + text: qsTr("Community Rules") + icon.name: "text" + } + + StatusMenuSeparator {} + + MuteChatMenuItem { + title: qsTr("Mute Community") + } } } diff --git a/ui/StatusQ/src/StatusQ/Controls/StatusNavBarTabButton.qml b/ui/StatusQ/src/StatusQ/Controls/StatusNavBarTabButton.qml index 7a2605f4876..c0196176b26 100644 --- a/ui/StatusQ/src/StatusQ/Controls/StatusNavBarTabButton.qml +++ b/ui/StatusQ/src/StatusQ/Controls/StatusNavBarTabButton.qml @@ -1,9 +1,9 @@ import QtQuick +import QtQuick.Controls import StatusQ.Core import StatusQ.Components import StatusQ.Controls -import StatusQ.Popups import StatusQ.Core.Theme StatusIconTabButton { @@ -62,36 +62,14 @@ StatusIconTabButton { border.width: 2 } - StatusMouseArea { - anchors.fill: parent - cursorShape: Qt.PointingHandCursor - acceptedButtons: Qt.LeftButton | Qt.RightButton - onClicked: function(mouse) { - if (mouse.button === Qt.RightButton) { - if (!!popupMenuSlot.sourceComponent && !popupMenuSlot.active) - popupMenuSlot.active = true - if (popupMenuSlot.active) { - statusNavBarTabButton.highlighted = true - let btnWidth = statusNavBarTabButton.width - popupMenuSlot.item.popup(parent.x + btnWidth + 4, -2) - } - } else if (mouse.button === Qt.LeftButton) { - statusNavBarTabButton.toggle() - statusNavBarTabButton.clicked() - } - } + function openContextMenu(pos) { + if (!popupMenu) + return + const menu = popupMenu.createObject(statusNavBarTabButton) + statusTooltip.hide() + menu.popup(pos) } - Loader { - id: popupMenuSlot - sourceComponent: statusNavBarTabButton.popupMenu - active: false - onLoaded: { - popupMenuSlot.item.closeHandler = function () { - statusNavBarTabButton.highlighted = false - popupMenuSlot.active = false - } - } - } + ContextMenu.onRequested: pos => openContextMenu(pos) + onPressAndHold: openContextMenu(Qt.point(statusNavBarTabButton.pressX, statusNavBarTabButton.pressY)) } - diff --git a/ui/imports/shared/controls/chat/menuItems/MuteChatMenuItem.qml b/ui/imports/shared/controls/chat/menuItems/MuteChatMenuItem.qml index 3cc6c7304b4..1d857aba76d 100644 --- a/ui/imports/shared/controls/chat/menuItems/MuteChatMenuItem.qml +++ b/ui/imports/shared/controls/chat/menuItems/MuteChatMenuItem.qml @@ -4,8 +4,6 @@ import QtQuick.Controls import utils import StatusQ.Popups -import shared.controls.chat.menuItems - StatusMenu { property bool isCommunityChat: false From 9c1f135d1e6c6cdc5f3ac3e702ef4d644f29ccb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Tinkl?= Date: Mon, 20 Oct 2025 20:45:36 +0200 Subject: [PATCH 09/16] fix(Settings): Advanced view's layout is slightly broken - fix the layout and usage of StatusSettingsLineButton component - hide some of the invalid entries on mobile Fixes #19072 --- ui/app/AppLayouts/Profile/ProfileLayout.qml | 1 + .../AppLayouts/Profile/views/AdvancedView.qml | 23 ++++++++++++++++++- .../AppLayouts/Profile/views/BrowserView.qml | 4 +--- .../status/StatusSettingsLineButton.qml | 2 ++ 4 files changed, 26 insertions(+), 4 deletions(-) diff --git a/ui/app/AppLayouts/Profile/ProfileLayout.qml b/ui/app/AppLayouts/Profile/ProfileLayout.qml index 7cc7d47f765..fcf4ff769cf 100644 --- a/ui/app/AppLayouts/Profile/ProfileLayout.qml +++ b/ui/app/AppLayouts/Profile/ProfileLayout.qml @@ -455,6 +455,7 @@ StatusSectionLayout { advancedStore: root.advancedStore walletStore: root.walletStore isFleetSelectionEnabled: fleetSelectionEnabled + isBrowserEnabled: root.isBrowserEnabled sectionTitle: settingsEntriesModel.getNameForSubsection(Constants.settingsSubsection.advanced) contentWidth: d.contentWidth } diff --git a/ui/app/AppLayouts/Profile/views/AdvancedView.qml b/ui/app/AppLayouts/Profile/views/AdvancedView.qml index 8515535ce49..aa269431b3f 100644 --- a/ui/app/AppLayouts/Profile/views/AdvancedView.qml +++ b/ui/app/AppLayouts/Profile/views/AdvancedView.qml @@ -36,6 +36,7 @@ SettingsContentBase { property WalletStore walletStore property bool isFleetSelectionEnabled + property bool isBrowserEnabled: true Item { id: advancedContainer @@ -54,6 +55,7 @@ SettingsContentBase { width: root.contentWidth StatusSettingsLineButton { + width: parent.width text: qsTr("Fleet") currentValue: root.advancedStore.fleet onClicked: fleetModal.open() @@ -62,12 +64,14 @@ SettingsContentBase { StatusSettingsLineButton { id: labelScrolling + width: parent.width text: qsTr("Chat scrolling") currentValue: root.advancedStore.isCustomScrollingEnabled ? qsTr("Custom") : qsTr("System") onClicked: scrollingModal.open() } StatusSettingsLineButton { + width: parent.width text: qsTr("Minimize on close") isSwitch: true checked: !localAccountSensitiveSettings.quitOnClose @@ -75,6 +79,7 @@ SettingsContentBase { } StatusSettingsLineButton { + width: parent.width text: qsTr("Mainnet data verified by Nimbus") isSwitch: true checked: root.advancedStore.isNimbusProxyEnabled @@ -125,9 +130,11 @@ SettingsContentBase { } StatusSettingsLineButton { + visible: root.isBrowserEnabled // feature flag + width: parent.width text: qsTr("Web/dApp Browser") isSwitch: true - checked: localAccountSensitiveSettings.isBrowserEnabled + checked: localAccountSensitiveSettings.isBrowserEnabled // user setting onToggled: { if (checked) { confirmationPopup.experimentalFeature = root.advancedStore.experimentalFeatures.browser @@ -139,6 +146,7 @@ SettingsContentBase { } StatusSettingsLineButton { + width: parent.width text: qsTr("Node Management") isSwitch: true checked: localAccountSensitiveSettings.nodeManagementEnabled @@ -153,6 +161,7 @@ SettingsContentBase { } StatusSettingsLineButton { + width: parent.width text: qsTr("Archive Protocol Enabled") visible: !SQUtils.Utils.isMobile isSwitch: true @@ -163,6 +172,7 @@ SettingsContentBase { } StatusSettingsLineButton { + width: parent.width text: qsTr("ENS Community Permissions Enabled") isSwitch: true checked: root.advancedStore.ensCommunityPermissionsEnabled @@ -186,6 +196,8 @@ SettingsContentBase { } StatusSettingsLineButton { + visible: !SQUtils.Utils.isMobile + width: parent.width text: qsTr("Enable creation of sharded communities") isSwitch: true checked: root.advancedStore.isWakuV2ShardedCommunitiesEnabled @@ -287,6 +299,7 @@ SettingsContentBase { } StatusSettingsLineButton { + width: parent.width text: qsTr("Full developer mode") enabled: { return !localAccountSensitiveSettings.downloadChannelMessagesEnabled || @@ -303,6 +316,7 @@ SettingsContentBase { } StatusSettingsLineButton { + width: parent.width text: qsTr("Enable translations") isSwitch: true checked: localAppSettings.translationsEnabled @@ -325,6 +339,7 @@ SettingsContentBase { } StatusSettingsLineButton { + width: parent.width text: qsTr("Download messages") isSwitch: true checked: localAccountSensitiveSettings.downloadChannelMessagesEnabled @@ -334,6 +349,7 @@ SettingsContentBase { } StatusSettingsLineButton { + width: parent.width text: qsTr("Debug") isSwitch: true enabled: !root.advancedStore.isRuntimeLogLevelSet @@ -359,6 +375,7 @@ SettingsContentBase { } StatusSettingsLineButton { + width: parent.width text: qsTr("Auto message") isSwitch: true checked: root.advancedStore.isAutoMessageEnabled @@ -368,6 +385,7 @@ SettingsContentBase { } StatusSettingsLineButton { + width: parent.width objectName: "manageCommunitiesOnTestnetButton" text: qsTr("Manage communities on testnet") isSwitch: true @@ -378,6 +396,7 @@ SettingsContentBase { } StatusSettingsLineButton { + width: parent.width text: qsTr("Enable community tokens refreshing") isSwitch: true checked: root.advancedStore.refreshTokenEnabled @@ -387,6 +406,7 @@ SettingsContentBase { } StatusSettingsLineButton { + width: parent.width text: qsTr("How many log files to keep archived") currentValue: root.advancedStore.logMaxBackups.toString() onClicked: { @@ -395,6 +415,7 @@ SettingsContentBase { } StatusSettingsLineButton { + width: parent.width id: rpcStatsButton text: qsTr("RPC statistics") onClicked: rpcStatsModal.open() diff --git a/ui/app/AppLayouts/Profile/views/BrowserView.qml b/ui/app/AppLayouts/Profile/views/BrowserView.qml index 50f3e1a4a2a..df1515630e2 100644 --- a/ui/app/AppLayouts/Profile/views/BrowserView.qml +++ b/ui/app/AppLayouts/Profile/views/BrowserView.qml @@ -45,10 +45,8 @@ SettingsContentBase { anchors.rightMargin: Theme.padding } - // TODO: Replace with StatusQ StatusListItem component StatusSettingsLineButton { - anchors.leftMargin: 0 - anchors.rightMargin: 0 + width: parent.width text: qsTr("Search engine used in the address bar") currentValue: { switch (accountSettings.shouldShowBrowserSearchEngine) { diff --git a/ui/imports/shared/status/StatusSettingsLineButton.qml b/ui/imports/shared/status/StatusSettingsLineButton.qml index 3994ff395a0..ace26133aef 100644 --- a/ui/imports/shared/status/StatusSettingsLineButton.qml +++ b/ui/imports/shared/status/StatusSettingsLineButton.qml @@ -15,6 +15,8 @@ ItemDelegate { property int badgeValue property int badgeRadius: 9 + implicitHeight: 64 + horizontalPadding: Theme.padding spacing: Theme.padding From 89dc7970266b59fc73f0338a32ce677cd4e065e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Tinkl?= Date: Mon, 20 Oct 2025 20:47:19 +0200 Subject: [PATCH 10/16] fix(StatusChatInput): also display the Send button for Payments Fixes #19049 --- ui/imports/shared/status/StatusChatInput.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/imports/shared/status/StatusChatInput.qml b/ui/imports/shared/status/StatusChatInput.qml index d98c18ee981..7513355ee42 100644 --- a/ui/imports/shared/status/StatusChatInput.qml +++ b/ui/imports/shared/status/StatusChatInput.qml @@ -1440,7 +1440,7 @@ Rectangle { implicitWidth: 32 icon.name: "send" type: StatusQ.StatusFlatRoundButton.Type.Tertiary - visible: messageInputField.length > 0 || control.fileUrlsAndSources.length > 0 + visible: messageInputField.length > 0 || control.fileUrlsAndSources.length > 0 || !!control.paymentRequestModel onClicked: { control.onKeyPress({modifiers: d.kbdModifierToSendMessage, key: Qt.Key_Return}) } From 9536097e177a3b9f8ac787a04ee87df8ddd72cc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Tinkl?= Date: Mon, 20 Oct 2025 23:21:07 +0200 Subject: [PATCH 11/16] fix(Settings/Wallet): adjust the Beta tag padding - this is a one off prob, not fixing the complete task Iterates: #19068 --- ui/app/AppLayouts/Profile/views/wallet/MainView.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/app/AppLayouts/Profile/views/wallet/MainView.qml b/ui/app/AppLayouts/Profile/views/wallet/MainView.qml index d1c5aec1be3..ae928efde1b 100644 --- a/ui/app/AppLayouts/Profile/views/wallet/MainView.qml +++ b/ui/app/AppLayouts/Profile/views/wallet/MainView.qml @@ -150,7 +150,7 @@ Column { id: accountOrderBetaTag anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left - anchors.leftMargin: accountOrderItem.statusListItemTitle.width + Theme.bigPadding + anchors.leftMargin: accountOrderItem.statusListItemTitle.width + parent.leftPadding tooltipText: qsTr("Under construction, you might experience some minor issues") cursorShape: Qt.PointingHandCursor } @@ -177,7 +177,7 @@ Column { id: manageTokensBetaTag anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left - anchors.leftMargin: manageTokensItem.statusListItemTitle.width + Theme.bigPadding + anchors.leftMargin: manageTokensItem.statusListItemTitle.width + parent.leftPadding tooltipText: qsTr("Under construction, you might experience some minor issues") cursorShape: Qt.PointingHandCursor } From 6525505549836a38268581b63717a8799ab0ad51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Tinkl?= Date: Mon, 20 Oct 2025 23:29:41 +0200 Subject: [PATCH 12/16] fix: [Tablet] File access support on Android - adjust the permissions/features according to the docu - do not use a `TMPDIR` when saving/resizing a file from NIM, it doesn't work on mobile; use one from QStandardPaths which has an Android specific implementation (https://github.com/qt/qtbase/blob/dev/src/corelib/io/qstandardpaths_android.cpp) Fixes #18076 Fixes #19053 --- mobile/android/qt6/AndroidManifest.xml | 18 +++++++++--------- src/app_service/service/chat/async_tasks.nim | 8 +------- src/app_service/service/chat/service.nim | 2 -- vendor/DOtherSide | 2 +- vendor/nimqml | 2 +- 5 files changed, 12 insertions(+), 20 deletions(-) diff --git a/mobile/android/qt6/AndroidManifest.xml b/mobile/android/qt6/AndroidManifest.xml index c912e3bcaad..5b71ca3acf4 100644 --- a/mobile/android/qt6/AndroidManifest.xml +++ b/mobile/android/qt6/AndroidManifest.xml @@ -11,19 +11,19 @@ + + + + + + - - - - - + - - - - + + Date: Tue, 21 Oct 2025 00:13:48 +0200 Subject: [PATCH 13/16] chore(ci): use nproc in MAKEFLAGS - instead of hardcoding 4 CPU cores --- ci/Jenkinsfile.android | 1 + ci/Jenkinsfile.ios | 2 +- ci/Jenkinsfile.linux | 2 +- ci/Jenkinsfile.linux-nix | 2 +- ci/Jenkinsfile.macos | 2 +- ci/Jenkinsfile.tests-ui | 2 +- 6 files changed, 6 insertions(+), 5 deletions(-) diff --git a/ci/Jenkinsfile.android b/ci/Jenkinsfile.android index 78ddb5d4ae2..3605a361dbc 100644 --- a/ci/Jenkinsfile.android +++ b/ci/Jenkinsfile.android @@ -63,6 +63,7 @@ pipeline { /* Control output the filename */ APP_TYPE = "${utils.getAppType()}" PLATFORM = "android/arm64" + MAKEFLAGS = "-j${utils.getProcCount()} V=${params.VERBOSE}" QT_VERSION = "6.9.2" QT_ANDROID_PATH = "/opt/qt/${env.QT_VERSION}/android_arm64_v8a" QMAKE = "/opt/qt/${env.QT_VERSION}/android_arm64_v8a/bin/qmake" diff --git a/ci/Jenkinsfile.ios b/ci/Jenkinsfile.ios index 80cf3785c2d..b4a0917a451 100644 --- a/ci/Jenkinsfile.ios +++ b/ci/Jenkinsfile.ios @@ -45,7 +45,7 @@ pipeline { GOTMPDIR = "${env.WORKSPACE_TMP}" PLATFORM = "ios/${getArch()}" /* Improve make performance */ - MAKEFLAGS = "-j4 V=${params.VERBOSE}" + MAKEFLAGS = "-j${utils.getProcCount()} V=${params.VERBOSE}" QT_VERSION="6.9.2" QMAKE = "/Users/admin/${QT_VERSION}/ios/bin/qmake" QT_HOST_PATH = "/Users/admin/${QT_VERSION}/macos" diff --git a/ci/Jenkinsfile.linux b/ci/Jenkinsfile.linux index 36100bfdfa0..f236df0a1ff 100644 --- a/ci/Jenkinsfile.linux +++ b/ci/Jenkinsfile.linux @@ -76,7 +76,7 @@ pipeline { environment { PLATFORM = "linux/${getArch()}${(params.USE_NWAKU ?: false) ? '-nwaku' : ''}" /* Improve make performance */ - MAKEFLAGS = "-j4 V=${params.VERBOSE}" + MAKEFLAGS = "-j${utils.getProcCount()} V=${params.VERBOSE}" /* Avoid weird bugs caused by stale cache. */ QML_DISABLE_DISK_CACHE = "true" /* Set USE_NWAKU before VERSION since version.sh uses it */ diff --git a/ci/Jenkinsfile.linux-nix b/ci/Jenkinsfile.linux-nix index 1f636061d33..06a3b383432 100644 --- a/ci/Jenkinsfile.linux-nix +++ b/ci/Jenkinsfile.linux-nix @@ -59,7 +59,7 @@ pipeline { environment { PLATFORM = "linux-nix/${getArch()}" /* Improve make performance */ - MAKEFLAGS = "-j4 V=${params.VERBOSE}" + MAKEFLAGS = "-j${utils.getProcCount()} V=${params.VERBOSE}" /* Avoid weird bugs caused by stale cache. */ QML_DISABLE_DISK_CACHE = "true" /* Control output the filename */ diff --git a/ci/Jenkinsfile.macos b/ci/Jenkinsfile.macos index 6dd8424d361..11b03fb61d8 100644 --- a/ci/Jenkinsfile.macos +++ b/ci/Jenkinsfile.macos @@ -70,7 +70,7 @@ pipeline { environment { PLATFORM = "macos/${getArch()}${(params.USE_NWAKU ?: false) ? '-nwaku' : ''}" /* Improve make performance */ - MAKEFLAGS = "-j4 V=${params.VERBOSE}" + MAKEFLAGS = "-j${utils.getProcCount()} V=${params.VERBOSE}" QT_VERSION="6.9.2" QMAKE = "/Users/admin/${QT_VERSION}/macos/bin/qmake" /* QMAKE = sh(script: "which qmake", returnStdout: true).trim() */ diff --git a/ci/Jenkinsfile.tests-ui b/ci/Jenkinsfile.tests-ui index 4c40d037b1e..7e836f33083 100644 --- a/ci/Jenkinsfile.tests-ui +++ b/ci/Jenkinsfile.tests-ui @@ -47,7 +47,7 @@ pipeline { environment { PLATFORM = 'tests/ui' /* Improve make performance */ - MAKEFLAGS = "-j4 V=${params.VERBOSE}" + MAKEFLAGS = "-j${utils.getProcCount()} V=${params.VERBOSE}" /* Makefile assumes the compiler folder is included */ QTDIR = "/opt/qt/6.9.2/gcc_64" PATH = "${env.QTDIR}/bin:${env.PATH}" From 37e93af4a1f7ede433f1e10d0aecdb2236e67d02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Tinkl?= Date: Tue, 21 Oct 2025 00:15:18 +0200 Subject: [PATCH 14/16] fix(mobile): set the `production` variant by default - as we do in the desktop Makefile; this entails a lot of other dependant options, like logging and enabling dev features in the app --- mobile/scripts/buildNimStatusClient.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/scripts/buildNimStatusClient.sh b/mobile/scripts/buildNimStatusClient.sh index 1384dca0d20..ca6aa116f9f 100755 --- a/mobile/scripts/buildNimStatusClient.sh +++ b/mobile/scripts/buildNimStatusClient.sh @@ -58,6 +58,7 @@ env $FEATURE_FLAGS ./vendor/nimbus-build-system/scripts/env.sh nim c "${PLATFORM --cpu:"$CARCH" \ --noMain:on \ -d:release \ + -d:production \ --clang.exe="$CC" \ --clang.linkerexe="$CC" \ --dynlibOverrideAll \ @@ -67,4 +68,3 @@ env $FEATURE_FLAGS ./vendor/nimbus-build-system/scripts/env.sh nim c "${PLATFORM mkdir -p "$LIB_DIR" cp "$STATUS_DESKTOP/bin/libnim_status_client$LIB_EXT" "$LIB_DIR/libnim_status_client$LIB_EXT" - From 72f77fe04441d2d071b0f56753f2a79869715c0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Tinkl?= Date: Tue, 21 Oct 2025 11:27:01 +0200 Subject: [PATCH 15/16] fix(AppMain): Node section is not visible in the primary navbar - fix a regression where the Node section got removed from the primary (left) navbar Fixes #19050 --- ui/app/mainui/AppMain.qml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ui/app/mainui/AppMain.qml b/ui/app/mainui/AppMain.qml index 0d9561d973f..8f5496aec84 100644 --- a/ui/app/mainui/AppMain.qml +++ b/ui/app/mainui/AppMain.qml @@ -1239,6 +1239,11 @@ Item { value: Constants.appSection.browser enabled: d.isBrowserEnabled } + ValueFilter { + roleName: "sectionType" + value: Constants.appSection.node + enabled: localAccountSensitiveSettings.nodeManagementEnabled + } }, ValueFilter { roleName: "enabled" From ee07f3d5f906fa57ae8a92bd68df55553180a9fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Tinkl?= Date: Thu, 23 Oct 2025 12:28:59 +0200 Subject: [PATCH 16/16] chore: mobile MVP fixes - fixup Android permissions and local file access and improve the error reporting - fix saving images to local files (Download image) - tooltip metrics - Browser settings subsection properly enabled/disabled - handle the Back gesture in Onboarding - update TS files --- ci/Jenkinsfile.tests-ui | 6 +- mobile/android/qt6/AndroidManifest.xml | 5 +- .../main/profile_section/advanced/module.nim | 2 +- ui/StatusQ/include/StatusQ/urlutils.h | 4 ++ .../src/StatusQ/Components/StatusMessage.qml | 2 +- .../Controls/StatusNavBarTabButton.qml | 2 +- .../src/StatusQ/Controls/StatusToolTip.qml | 12 ++-- .../Popups/Dialog/StatusFileDialog.qml | 8 ++- ui/StatusQ/src/systemutilsinternal.cpp | 35 +++++------- ui/StatusQ/src/urlutils.cpp | 57 +++++++++++++++---- .../Onboarding/OnboardingLayout.qml | 7 +++ ui/app/AppLayouts/Profile/ProfileLayout.qml | 2 +- .../Profile/helpers/SettingsEntriesModel.qml | 2 +- .../AppLayouts/Profile/views/AdvancedView.qml | 15 +++-- .../Profile/views/wallet/MainView.qml | 4 +- ui/app/mainui/AppMain.qml | 2 +- ui/app/mainui/Popups.qml | 2 + ui/i18n/qml_base_en.ts | 6 +- ui/i18n/qml_base_lokalise_en.ts | 9 ++- ui/i18n/qml_cs.ts | 6 +- ui/i18n/qml_ko.ts | 10 +++- .../shared/popups/ImageCropWorkflow.qml | 11 +++- .../StatusChatImageExtensionValidator.qml | 8 ++- ui/imports/shared/status/StatusChatInput.qml | 5 +- vendor/DOtherSide | 2 +- vendor/nimqml | 2 +- 26 files changed, 158 insertions(+), 68 deletions(-) diff --git a/ci/Jenkinsfile.tests-ui b/ci/Jenkinsfile.tests-ui index 7e836f33083..013708cfef5 100644 --- a/ci/Jenkinsfile.tests-ui +++ b/ci/Jenkinsfile.tests-ui @@ -70,8 +70,10 @@ pipeline { stage('Check Translations') { steps { - script { - checkTranslations() + catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') { + script { + checkTranslations() + } } } } diff --git a/mobile/android/qt6/AndroidManifest.xml b/mobile/android/qt6/AndroidManifest.xml index 5b71ca3acf4..7c554f0744c 100644 --- a/mobile/android/qt6/AndroidManifest.xml +++ b/mobile/android/qt6/AndroidManifest.xml @@ -19,8 +19,10 @@ + - + + @@ -36,7 +38,6 @@ android:icon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher_round" android:theme="@style/Theme.AppSplash" - android:requestLegacyExternalStorage="true" android:allowBackup="true" android:hasFragileUserData="true" android:fullBackupOnly="false"> diff --git a/src/app/modules/main/profile_section/advanced/module.nim b/src/app/modules/main/profile_section/advanced/module.nim index 789b4985ffd..5fa35f28c4e 100644 --- a/src/app/modules/main/profile_section/advanced/module.nim +++ b/src/app/modules/main/profile_section/advanced/module.nim @@ -63,7 +63,7 @@ method onFleetSet*(self: Module) = quit(QuitSuccess) # quits the app TODO: change this to logout instead when supported method getLogDir*(self: Module): string = - return url_fromLocalFile(constants.LOGDIR) + return constants.LOGDIR method getWakuV2LightClientEnabled*(self: Module): bool = return self.controller.getWakuV2LightClientEnabled() diff --git a/ui/StatusQ/include/StatusQ/urlutils.h b/ui/StatusQ/include/StatusQ/urlutils.h index 3fea917e6f1..0103aef852d 100644 --- a/ui/StatusQ/include/StatusQ/urlutils.h +++ b/ui/StatusQ/include/StatusQ/urlutils.h @@ -40,4 +40,8 @@ class UrlUtils : public QObject // ["jpg", "jpe", "jp", "jpeg", "png", "webp", "gif", "svg"] QStringList m_allImgExtensions; QStringList allValidImageExtensions() const { return m_allImgExtensions; } + +#ifdef Q_OS_ANDROID + QString resolveAndroidContentUrl(const QString& urlPath) const; +#endif }; diff --git a/ui/StatusQ/src/StatusQ/Components/StatusMessage.qml b/ui/StatusQ/src/StatusQ/Components/StatusMessage.qml index 6101ed18f82..d9887b011e6 100644 --- a/ui/StatusQ/src/StatusQ/Components/StatusMessage.qml +++ b/ui/StatusQ/src/StatusQ/Components/StatusMessage.qml @@ -334,7 +334,7 @@ Control { albumCount: root.messageDetails.albumCount > 0 ? root.messageDetails.albumCount : 1 imageWidth: Math.min(messageLayout.width / root.messageDetails.albumCount - 9 * (root.messageDetails.albumCount - 1), 144) shapeType: StatusImageMessage.ShapeType.LEFT_ROUNDED - onImageClicked: root.imageClicked(image, mouse, imageSource) + onImageClicked: (image, mouse, imageSource) => root.imageClicked(image, mouse, imageSource) } } } diff --git a/ui/StatusQ/src/StatusQ/Controls/StatusNavBarTabButton.qml b/ui/StatusQ/src/StatusQ/Controls/StatusNavBarTabButton.qml index c0196176b26..1b89f91042c 100644 --- a/ui/StatusQ/src/StatusQ/Controls/StatusNavBarTabButton.qml +++ b/ui/StatusQ/src/StatusQ/Controls/StatusNavBarTabButton.qml @@ -28,7 +28,7 @@ StatusIconTabButton { visible: statusNavBarTabButton.hovered && !!statusTooltip.text delay: 50 orientation: StatusToolTip.Orientation.Right - x: statusNavBarTabButton.width + 16 + x: statusNavBarTabButton.width + Theme.padding y: statusNavBarTabButton.height / 2 - height / 2 + 4 } diff --git a/ui/StatusQ/src/StatusQ/Controls/StatusToolTip.qml b/ui/StatusQ/src/StatusQ/Controls/StatusToolTip.qml index a548aa92192..095da79b4c3 100644 --- a/ui/StatusQ/src/StatusQ/Controls/StatusToolTip.qml +++ b/ui/StatusQ/src/StatusQ/Controls/StatusToolTip.qml @@ -25,8 +25,8 @@ ToolTip { Math.max(implicitBackgroundWidth + leftInset + rightInset, implicitContentWidth + leftPadding + rightPadding) ) - padding: Theme.halfPadding - horizontalPadding: padding + 4 + horizontalPadding: Theme.padding + verticalPadding: Theme.halfPadding margins: Theme.halfPadding delay: Utils.isMobile ? Application.styleHints.mousePressAndHoldInterval : 200 @@ -43,8 +43,8 @@ ToolTip { Rectangle { id: arrow color: statusToolTipContentBackground.color - height: 20 - width: 20 + height: Theme.padding + width: Theme.padding rotation: 45 radius: 1 x: { @@ -52,10 +52,10 @@ ToolTip { return statusToolTipBackground.width / 2 - width / 2 + offset } if (orientation === StatusToolTip.Orientation.Left) { - return statusToolTipContentBackground.width - (width / 2) - root.padding + offset + return statusToolTipContentBackground.width - statusToolTipContentBackground.radius - radius*2 + offset } if (orientation === StatusToolTip.Orientation.Right) { - return -width/2 + root.padding + offset + return -width/2 + radius*2 + offset } } y: { diff --git a/ui/StatusQ/src/StatusQ/Popups/Dialog/StatusFileDialog.qml b/ui/StatusQ/src/StatusQ/Popups/Dialog/StatusFileDialog.qml index fb7eb5a9515..5082dc2fa71 100644 --- a/ui/StatusQ/src/StatusQ/Popups/Dialog/StatusFileDialog.qml +++ b/ui/StatusQ/src/StatusQ/Popups/Dialog/StatusFileDialog.qml @@ -18,7 +18,9 @@ QObject { property alias currentFolder: dlg.currentFolder property string picturesShortcut: Utils.isIOS ? "assets-library://" : - StandardPaths.writableLocation(StandardPaths.PicturesLocation) + d.standardPictureLocations.length > 1 ? d.standardPictureLocations[1] // [0] is writable, don't need it here, we have StatusSaveFileDialog for that + : d.standardPictureLocations.length > 0 ? d.standardPictureLocations[0] + : "" signal accepted signal rejected @@ -33,6 +35,9 @@ QObject { QtObject { id: d + + readonly property list standardPictureLocations: StandardPaths.standardLocations(StandardPaths.PicturesLocation) + readonly property url resolvedFile: resolveFile(dlg.selectedFile) readonly property var resolvedFiles: resolveSelectedFiles(dlg.selectedFiles) @@ -45,6 +50,7 @@ QObject { resolvedLocalFile = "file:" + resolvedLocalFile return resolvedLocalFile } + function resolveSelectedFiles(selectedFiles) { if (selectedFiles.length === 0) return [] diff --git a/ui/StatusQ/src/systemutilsinternal.cpp b/ui/StatusQ/src/systemutilsinternal.cpp index 7c19c93b67e..17db141f1c1 100644 --- a/ui/StatusQ/src/systemutilsinternal.cpp +++ b/ui/StatusQ/src/systemutilsinternal.cpp @@ -1,23 +1,21 @@ -#ifdef Q_OS_ANDROID -#include -#include -#endif #include "StatusQ/systemutilsinternal.h" -#include #include #include #include #include #include #include +#include #ifdef Q_OS_ANDROID #include #include #endif +#ifdef Q_OS_IOS #include "ios_utils.h" +#endif class QuitFilter : public QObject { @@ -26,7 +24,7 @@ class QuitFilter : public QObject public: using QObject::QObject; - bool eventFilter(QObject* obj, QEvent* ev) + bool eventFilter(QObject* obj, QEvent* ev) override { if (ev->type() == QEvent::Quit) emit quit(ev->spontaneous()); @@ -60,15 +58,8 @@ void SystemUtilsInternal::restartApplication() const QMetaObject::invokeMethod(QCoreApplication::instance(), &QCoreApplication::exit, Qt::QueuedConnection, EXIT_SUCCESS); } -#if defined(Q_OS_IOS) -void save(const QByteArray& imageData) -{ - saveImageToPhotosAlbum(imageData); -} -#else void save(const QByteArray& imageData, const QString& targetDir) { - // Get current Date/Time information to use in naming of the image file const auto dateTimeString = QDateTime::currentDateTime().toString( QStringLiteral("dd-MM-yyyy_hh-mm-ss")); @@ -99,7 +90,6 @@ void save(const QByteArray& imageData, const QString& targetDir) "Downloading image failed while saving to file:" << targetFile; } -#endif void SystemUtilsInternal::downloadImageByUrl( const QUrl& url, const QString& path) const @@ -110,10 +100,15 @@ void SystemUtilsInternal::downloadImageByUrl( QNetworkReply *reply = manager.get(QNetworkRequest(url)); // accept both "file:/foo/bar" and "/foo/bar" - auto targetDir = QUrl::fromUserInput(path).toLocalFile(); + auto targetDir = QUrl::fromUserInput(path) +#ifndef Q_OS_ANDROID + .toLocalFile(); +#else + .toString(); // don't touch the "content://" URI +#endif if (targetDir.isEmpty()) - targetDir = QDir::homePath(); + targetDir = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation); QObject::connect(reply, &QNetworkReply::finished, this, [reply, targetDir] { if(reply->error() != QNetworkReply::NoError) { @@ -125,11 +120,11 @@ void SystemUtilsInternal::downloadImageByUrl( // Extract the image data to be able to load and save it const auto btArray = reply->readAll(); Q_ASSERT(!btArray.isEmpty()); - #ifdef Q_OS_IOS - save(btArray); - #else +#ifdef Q_OS_IOS + saveImageToPhotosAlbum(btArray); +#else save(btArray, targetDir); - #endif +#endif }); } diff --git a/ui/StatusQ/src/urlutils.cpp b/ui/StatusQ/src/urlutils.cpp index 169cb0be0d4..7821b4453db 100644 --- a/ui/StatusQ/src/urlutils.cpp +++ b/ui/StatusQ/src/urlutils.cpp @@ -1,10 +1,14 @@ #include "StatusQ/urlutils.h" +#include #include #include +#include #include +#ifdef Q_OS_IOS #include "ios_utils.h" +#endif namespace { constexpr auto webpMime = "image/webp"; @@ -30,34 +34,65 @@ UrlUtils::UrlUtils(QObject *parent): QObject(parent) { bool UrlUtils::isValidImageUrl(const QUrl &url) const { - QString mimeType; - if (url.isLocalFile()) - mimeType = m_mimeDb.mimeTypeForFile(url.toLocalFile(), QMimeDatabase::MatchContent).name(); - else - mimeType = m_mimeDb.mimeTypeForUrl(url).name(); + // don't convert "content:/" like URLs to an empty path + const auto filePath = url.isLocalFile() ? url.toLocalFile() : url.toString(); + const auto mimeType = m_mimeDb.mimeTypeForFile(filePath, QMimeDatabase::MatchContent).name(); return m_validImageMimeTypes.contains(mimeType); } qint64 UrlUtils::getFileSize(const QUrl& url) { - if (url.isLocalFile()) - return QFile(url.toLocalFile()).size(); - - return 0; + // don't convert "content:/" like URLs to an empty path + const auto filePath = url.isLocalFile() ? url.toLocalFile() : url.toString(); + return QFile(filePath).size(); // will return 0 for unknown file paths } QString UrlUtils::convertUrlToLocalPath(const QString &url) const { +#ifdef Q_OS_ANDROID + return resolveAndroidContentUrl(url); +#endif + const auto localFileOrUrl = urlFromUserInput(url); // accept both "file:/foo/bar" and "/foo/bar" if (localFileOrUrl.isLocalFile()) { - #ifdef Q_OS_IOS +#ifdef Q_OS_IOS return resolveIOSPhotoAsset(localFileOrUrl.toLocalFile()); - #endif // Q_OS_IOS +#endif return localFileOrUrl.toLocalFile(); } return {}; } +#ifdef Q_OS_ANDROID +QString UrlUtils::resolveAndroidContentUrl(const QString& urlPath) const { + // test if we already have a real (resolved) path + if (urlPath.startsWith('/')) + return urlPath; + + // test if we already have a real (resolved) path in URL form + if (urlPath.startsWith(QStringLiteral("file:/"))) + return urlFromUserInput(urlPath).toLocalFile(); + + QDir dir(urlPath); + if (urlPath.endsWith('/') || dir.exists()) + return urlPath; // a directory, just return it + + QFile fileIn(urlPath); + if (!fileIn.open(QIODevice::ReadOnly)) + return urlPath; + + // save to a temp file, and return the filepath + const auto newFilePath = QStandardPaths::writableLocation(QStandardPaths::TempLocation) + '/' + QUuid::createUuid().toString(QUuid::WithoutBraces); + QFile fileOut(newFilePath); + if (!fileOut.open(QIODevice::WriteOnly | QIODevice::Truncate)) + return urlPath; + if (fileOut.write(fileIn.readAll()) != -1) + return fileOut.fileName(); // return the real filename, not the virtual "content:/" URI + + return urlPath; +} +#endif + QStringList UrlUtils::convertUrlsToLocalPaths(const QStringList &urls) const { QStringList result; for (const auto& url: urls) { diff --git a/ui/app/AppLayouts/Onboarding/OnboardingLayout.qml b/ui/app/AppLayouts/Onboarding/OnboardingLayout.qml index d2a887bdc75..e39f633badc 100644 --- a/ui/app/AppLayouts/Onboarding/OnboardingLayout.qml +++ b/ui/app/AppLayouts/Onboarding/OnboardingLayout.qml @@ -257,6 +257,13 @@ Page { onClicked: onboardingFlow.popTopLevelItem() } + Keys.onPressed: function(e) { + if (e.key === Qt.Key_Back && backButton.visible) { + e.accepted = true + onboardingFlow.popTopLevelItem() + } + } + Connections { target: onboardingFlow.topLevelItem ignoreUnknownSignals: true diff --git a/ui/app/AppLayouts/Profile/ProfileLayout.qml b/ui/app/AppLayouts/Profile/ProfileLayout.qml index fcf4ff769cf..cce271b725c 100644 --- a/ui/app/AppLayouts/Profile/ProfileLayout.qml +++ b/ui/app/AppLayouts/Profile/ProfileLayout.qml @@ -150,7 +150,7 @@ StatusSectionLayout { id: settingsEntriesModel showWalletEntries: root.walletStore.isWalletEnabled - showBrowserEntries: root.isBrowserEnabled + showBrowserEntries: root.isBrowserEnabled && localAccountSensitiveSettings.isBrowserEnabled showBackUpSeed: !root.privacyStore.mnemonicBackedUp backUpSeedBadgeCount: root.profileStore.userDeclinedBackupBanner ? 0 : showBackUpSeed isKeycardEnabled: root.isKeycardEnabled diff --git a/ui/app/AppLayouts/Profile/helpers/SettingsEntriesModel.qml b/ui/app/AppLayouts/Profile/helpers/SettingsEntriesModel.qml index fe2971b9b19..6fb951dfd5b 100644 --- a/ui/app/AppLayouts/Profile/helpers/SettingsEntriesModel.qml +++ b/ui/app/AppLayouts/Profile/helpers/SettingsEntriesModel.qml @@ -217,7 +217,7 @@ SortFilterProxyModel { case Constants.settingsSubsection.ensUsernames: case Constants.settingsSubsection.wallet: return root.showWalletEntries - case Constants.settingsSubsection.browser: + case Constants.settingsSubsection.browserSettings: return root.showBrowserEntries case Constants.settingsSubsection.backUpSeed: return root.showBackUpSeed diff --git a/ui/app/AppLayouts/Profile/views/AdvancedView.qml b/ui/app/AppLayouts/Profile/views/AdvancedView.qml index aa269431b3f..3c593a721d5 100644 --- a/ui/app/AppLayouts/Profile/views/AdvancedView.qml +++ b/ui/app/AppLayouts/Profile/views/AdvancedView.qml @@ -11,6 +11,7 @@ import shared.popups import shared.status import shared.controls +import StatusQ import StatusQ.Components import StatusQ.Controls import StatusQ.Controls.Validators @@ -93,22 +94,28 @@ SettingsContentBase { anchors.right: parent.right anchors.leftMargin: Theme.padding anchors.rightMargin: Theme.padding - text: qsTr("Application Logs") + text: qsTr("Application Logs") + " (" + root.advancedStore.logDir() + ")" font.underline: mouseArea.containsMouse color: Theme.palette.primaryColor1 topPadding: 23 + wrapMode: Text.Wrap + elide: Text.ElideRight StatusMouseArea { id: mouseArea anchors.fill: parent cursorShape: Qt.PointingHandCursor hoverEnabled: true - onClicked: { - Qt.openUrlExternally(root.advancedStore.logDir()) - } + onClicked: logsFolderDialog.open() } } + StatusFolderDialog { + id: logsFolderDialog + title: qsTr("Application Logs") + currentFolder: root.advancedStore.logDir() + } + Item { id: spacer1 height: Theme.bigPadding diff --git a/ui/app/AppLayouts/Profile/views/wallet/MainView.qml b/ui/app/AppLayouts/Profile/views/wallet/MainView.qml index ae928efde1b..fcc9b42cfeb 100644 --- a/ui/app/AppLayouts/Profile/views/wallet/MainView.qml +++ b/ui/app/AppLayouts/Profile/views/wallet/MainView.qml @@ -150,7 +150,7 @@ Column { id: accountOrderBetaTag anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left - anchors.leftMargin: accountOrderItem.statusListItemTitle.width + parent.leftPadding + anchors.leftMargin: accountOrderItem.statusListItemTitle.width + parent.leftPadding + Theme.bigPadding tooltipText: qsTr("Under construction, you might experience some minor issues") cursorShape: Qt.PointingHandCursor } @@ -177,7 +177,7 @@ Column { id: manageTokensBetaTag anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left - anchors.leftMargin: manageTokensItem.statusListItemTitle.width + parent.leftPadding + anchors.leftMargin: manageTokensItem.statusListItemTitle.width + parent.leftPadding + Theme.bigPadding tooltipText: qsTr("Under construction, you might experience some minor issues") cursorShape: Qt.PointingHandCursor } diff --git a/ui/app/mainui/AppMain.qml b/ui/app/mainui/AppMain.qml index 8f5496aec84..1a2d31d67df 100644 --- a/ui/app/mainui/AppMain.qml +++ b/ui/app/mainui/AppMain.qml @@ -827,7 +827,7 @@ Item { id: d readonly property int activeSectionType: appMain.rootStore.activeSectionType - readonly property bool isBrowserEnabled: featureFlagsStore.browserEnabled && localAccountSensitiveSettings.isBrowserEnabled + readonly property bool isBrowserEnabled: appMain.featureFlagsStore.browserEnabled && localAccountSensitiveSettings.isBrowserEnabled function openHomePage() { appMain.rootStore.setActiveSectionBySectionType(Constants.appSection.homePage) diff --git a/ui/app/mainui/Popups.qml b/ui/app/mainui/Popups.qml index a2f4a27dfcd..e206cd82cc8 100644 --- a/ui/app/mainui/Popups.qml +++ b/ui/app/mainui/Popups.qml @@ -1,3 +1,4 @@ +import QtCore import QtQuick import QtQuick.Layouts import QtQuick.Window @@ -933,6 +934,7 @@ QtObject { title: qsTr("Please choose a directory") modality: Qt.NonModal + currentFolder: StandardPaths.writableLocation(StandardPaths.PicturesLocation) onAccepted: { SystemUtils.downloadImageByUrl(imageSource, selectedFolder) diff --git a/ui/i18n/qml_base_en.ts b/ui/i18n/qml_base_en.ts index fea42423143..73dabe34f1c 100644 --- a/ui/i18n/qml_base_en.ts +++ b/ui/i18n/qml_base_en.ts @@ -8446,7 +8446,7 @@ L2 fee: %2 - Format of the image you chose is not supported. Most probably you picked a file that is invalid, corrupted or has a wrong file extension. + Format of the image you chose is not supported. Most probably you picked a file that is invalid, corrupted or has a wrong file extension. The requested file was: %1 @@ -15915,6 +15915,10 @@ to load Format not supported. + + Format not supported. File: %1 + + Upload %1 only diff --git a/ui/i18n/qml_base_lokalise_en.ts b/ui/i18n/qml_base_lokalise_en.ts index a0ad2a42742..10838ed3ecb 100644 --- a/ui/i18n/qml_base_lokalise_en.ts +++ b/ui/i18n/qml_base_lokalise_en.ts @@ -10311,9 +10311,9 @@ Image format not supported - Format of the image you chose is not supported. Most probably you picked a file that is invalid, corrupted or has a wrong file extension. + Format of the image you chose is not supported. Most probably you picked a file that is invalid, corrupted or has a wrong file extension. The requested file was: %1 ImageCropWorkflow - Format of the image you chose is not supported. Most probably you picked a file that is invalid, corrupted or has a wrong file extension. + Format of the image you chose is not supported. Most probably you picked a file that is invalid, corrupted or has a wrong file extension. The requested file was: %1 Supported image extensions: %1 @@ -19373,6 +19373,11 @@ StatusChatImageExtensionValidator Format not supported. + + Format not supported. File: %1 + StatusChatImageExtensionValidator + Format not supported. File: %1 + Upload %1 only StatusChatImageExtensionValidator diff --git a/ui/i18n/qml_cs.ts b/ui/i18n/qml_cs.ts index 308efe5b068..8429c2b50aa 100644 --- a/ui/i18n/qml_cs.ts +++ b/ui/i18n/qml_cs.ts @@ -8474,7 +8474,7 @@ L2 poplatek: %2 - Format of the image you chose is not supported. Most probably you picked a file that is invalid, corrupted or has a wrong file extension. + Format of the image you chose is not supported. Most probably you picked a file that is invalid, corrupted or has a wrong file extension. The requested file was: %1 @@ -15983,6 +15983,10 @@ to load Format not supported. + + Format not supported. File: %1 + + Upload %1 only diff --git a/ui/i18n/qml_ko.ts b/ui/i18n/qml_ko.ts index 94efdfeb37a..5946a99d50e 100644 --- a/ui/i18n/qml_ko.ts +++ b/ui/i18n/qml_ko.ts @@ -1205,7 +1205,7 @@ Application Logs - 애플리케이션 로그 + Experimental features @@ -8419,8 +8419,8 @@ L2 fee: %2 지원하지 않는 이미지 형식입니다 - Format of the image you chose is not supported. Most probably you picked a file that is invalid, corrupted or has a wrong file extension. - 선택한 이미지의 형식은 지원되지 않습니다. 아마도 잘못된 파일, 손상된 파일이거나 파일 확장자가 올바르지 않은 경우일 수 있습니다. + Format of the image you chose is not supported. Most probably you picked a file that is invalid, corrupted or has a wrong file extension. The requested file was: %1 + Supported image extensions: %1 @@ -15851,6 +15851,10 @@ to load Format not supported. 지원되지 않는 형식입니다. + + Format not supported. File: %1 + + Upload %1 only %1만 업로드 diff --git a/ui/imports/shared/popups/ImageCropWorkflow.qml b/ui/imports/shared/popups/ImageCropWorkflow.qml index 17f4f28625e..56ff5fce9ff 100644 --- a/ui/imports/shared/popups/ImageCropWorkflow.qml +++ b/ui/imports/shared/popups/ImageCropWorkflow.qml @@ -44,24 +44,29 @@ Item { nameFilters: [qsTr("Supported image formats (%1)").arg(UrlUtils.validImageNameFilters)] onAccepted: { if (fileDialog.selectedFiles.length > 0) { - const url = fileDialog.selectedFiles[0] + const url = fileDialog.selectedFile if (Utils.isValidDragNDropImage(url)) cropImage(url) - else + else { + errorDialog.fileOpened = url errorDialog.open() + } } } } // FileDialog StatusDialog { id: errorDialog + + property string fileOpened + title: qsTr("Image format not supported") width: 480 contentItem: ColumnLayout { StatusBaseText { Layout.fillWidth: true wrapMode: Text.WordWrap - text: qsTr("Format of the image you chose is not supported. Most probably you picked a file that is invalid, corrupted or has a wrong file extension.") + text: qsTr("Format of the image you chose is not supported. Most probably you picked a file that is invalid, corrupted or has a wrong file extension. The requested file was: %1").arg(errorDialog.fileOpened) } StatusBaseText { Layout.fillWidth: true diff --git a/ui/imports/shared/status/StatusChatImageExtensionValidator.qml b/ui/imports/shared/status/StatusChatImageExtensionValidator.qml index 9a6bf558741..676fefe3f49 100644 --- a/ui/imports/shared/status/StatusChatImageExtensionValidator.qml +++ b/ui/imports/shared/status/StatusChatImageExtensionValidator.qml @@ -7,13 +7,19 @@ import utils StatusChatImageValidator { id: root - errorMessage: qsTr("Format not supported.") + errorMessage: !!lastFailedImgPath ? qsTr("Format not supported. File: %1").arg(lastFailedImgPath) : qsTr("Format not supported.") secondaryErrorMessage: qsTr("Upload %1 only").arg(UrlUtils.validPreferredImageExtensions.map(ext => ext.toUpperCase() + "s").join(", ")) + property string lastFailedImgPath + onImagesChanged: { let isValid = true root.validImages = images.filter(img => { const isImage = Utils.isValidDragNDropImage(img) + if (!isImage) + root.lastFailedImgPath = img + else + root.lastFailedImgPath = "" isValid = isValid && isImage return isImage }) diff --git a/ui/imports/shared/status/StatusChatInput.qml b/ui/imports/shared/status/StatusChatInput.qml index 7513355ee42..f97ad161231 100644 --- a/ui/imports/shared/status/StatusChatInput.qml +++ b/ui/imports/shared/status/StatusChatInput.qml @@ -21,6 +21,8 @@ import StatusQ.Core.Utils as StatusQUtils import StatusQ.Components import StatusQ.Controls as StatusQ +import QtModelsToolkit + Rectangle { id: control objectName: "statusChatInput" @@ -1440,7 +1442,8 @@ Rectangle { implicitWidth: 32 icon.name: "send" type: StatusQ.StatusFlatRoundButton.Type.Tertiary - visible: messageInputField.length > 0 || control.fileUrlsAndSources.length > 0 || !!control.paymentRequestModel + visible: messageInputField.length > 0 || control.fileUrlsAndSources.length > 0 || + (!!control.paymentRequestModel && control.paymentRequestModel.ModelCount.count > 0) onClicked: { control.onKeyPress({modifiers: d.kbdModifierToSendMessage, key: Qt.Key_Return}) } diff --git a/vendor/DOtherSide b/vendor/DOtherSide index 07836e0daa3..51f6e4c3411 160000 --- a/vendor/DOtherSide +++ b/vendor/DOtherSide @@ -1 +1 @@ -Subproject commit 07836e0daa39565954062c025a4bb6f4dfec6f47 +Subproject commit 51f6e4c34112e27fbebfb1a77557cd4fd93f2c48 diff --git a/vendor/nimqml b/vendor/nimqml index a9d6f2a72d7..629898020d5 160000 --- a/vendor/nimqml +++ b/vendor/nimqml @@ -1 +1 @@ -Subproject commit a9d6f2a72d7c21f3924effdcdfdf2a42c9ec2900 +Subproject commit 629898020d590ba01b223db15623b6dd90f4c8ef