From bbdb6014454bf10d798db8570f2b5372298d165e Mon Sep 17 00:00:00 2001 From: Noelia Date: Thu, 2 Jul 2026 19:31:13 +0200 Subject: [PATCH] feat(TokenListPopup): Migrate to StatusAdaptiveDialog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces StatusDialog with StatusAdaptiveDialog as the base type. The StatusListView content is detected as a Flickable (Strategy A) and placed directly in the content viewport without double-wrapping. Column widths for symbol and address are now relative fractions of the dialog width instead of fixed pixel values. Also fixes stale role names in the storybook page (key→id, image→logoUri, updatedAt→timestamp). --- storybook/pages/TokenListPopupPage.qml | 13 +- .../qmlTests/tests/tst_TokenListPopup.qml | 117 ++++++++++++++++++ .../Profile/popups/TokenListPopup.qml | 53 ++++---- 3 files changed, 147 insertions(+), 36 deletions(-) create mode 100644 storybook/qmlTests/tests/tst_TokenListPopup.qml diff --git a/storybook/pages/TokenListPopupPage.qml b/storybook/pages/TokenListPopupPage.qml index 219ad86d26..71c667e4a0 100644 --- a/storybook/pages/TokenListPopupPage.qml +++ b/storybook/pages/TokenListPopupPage.qml @@ -54,7 +54,7 @@ SplitView { filters: ValueFilter { id: keyFilter - roleName: "key" + roleName: "id" value : uniswapBtn.checked ? "uniswap" : "status" } } @@ -63,23 +63,22 @@ SplitView { id: delegate required property string name - required property string image + required property string logoUri required property string source required property string version - required property double updatedAt + required property double timestamp readonly property TokenListPopup popup: TokenListPopup { - parent: root - + parent: root.Overlay.overlay visible: true modal: false closePolicy: Popup.NoAutoClose title: qsTr("%1 Token List").arg(delegate.name) - sourceImage: delegate.image + sourceImage: delegate.logoUri sourceUrl: delegate.source sourceVersion: delegate.version - updatedAt: delegate.updatedAt + updatedAt: delegate.timestamp tokensListModel: SortFilterProxyModel { sourceModel: root.tokensProxyModel diff --git a/storybook/qmlTests/tests/tst_TokenListPopup.qml b/storybook/qmlTests/tests/tst_TokenListPopup.qml new file mode 100644 index 0000000000..c4833ab8c5 --- /dev/null +++ b/storybook/qmlTests/tests/tst_TokenListPopup.qml @@ -0,0 +1,117 @@ +import QtQuick +import QtTest + +import AppLayouts.Profile.popups + +Item { + id: root + + width: 1024 + height: 768 + + ListModel { + id: tokensModel + + ListElement { + name: "Ether" + symbol: "ETH" + image: "" + chainId: 1 + chainName: "Ethereum" + address: "0x0000000000000000000000000000000000000000" + blockExplorerURL: "https://etherscan.io" + isTest: false + } + ListElement { + name: "USDC" + symbol: "USDC" + image: "" + chainId: 1 + chainName: "Ethereum" + address: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" + blockExplorerURL: "https://etherscan.io" + isTest: false + } + } + + Component { + id: componentUnderTest + + TokenListPopup { + destroyOnClose: false + title: "Uniswap Token List" + sourceImage: "" + sourceUrl: "https://tokens.uniswap.org" + sourceVersion: "11.6.0" + updatedAt: 1710538948 + tokensListModel: tokensModel + } + } + + property TokenListPopup controlUnderTest: null + + SignalSpy { + id: linkClickedSpy + target: controlUnderTest + signalName: "linkClicked" + } + + TestCase { + name: "TokenListPopup" + when: windowShown + + function init() { + controlUnderTest = createTemporaryObject(componentUnderTest, root); + linkClickedSpy.clear(); + } + + function cleanup() { + if (controlUnderTest) { + controlUnderTest.close(); + controlUnderTest.destroy(); + controlUnderTest = null; + } + linkClickedSpy.clear(); + } + + function openDialog() { + verify(!!controlUnderTest); + controlUnderTest.open(); + tryCompare(controlUnderTest, "opened", true); + } + + function test_subtitle_shows_token_count() { + openDialog(); + verify(controlUnderTest.subtitle.includes("2")); + } + + function test_done_button_closes_dialog() { + openDialog(); + + const doneButton = findChild(controlUnderTest, "tokenListPopupDoneButton"); + verify(!!doneButton); + + mouseClick(doneButton); + + tryCompare(controlUnderTest, "opened", false); + } + + function test_content_host_is_present() { + openDialog(); + + const contentHost = findChild(controlUnderTest, "statusAdaptiveDialogContentHost"); + verify(!!contentHost); + verify(contentHost.visible); + } + + function test_link_clicked_signal_emitted() { + openDialog(); + + controlUnderTest.linkClicked("https://etherscan.io/token/0x0000000000000000000000000000000000000000"); + + compare(linkClickedSpy.count, 1); + compare(linkClickedSpy.signalArguments[0][0], + "https://etherscan.io/token/0x0000000000000000000000000000000000000000"); + } + } +} diff --git a/ui/app/AppLayouts/Profile/popups/TokenListPopup.qml b/ui/app/AppLayouts/Profile/popups/TokenListPopup.qml index 153250717b..99f42c7037 100644 --- a/ui/app/AppLayouts/Profile/popups/TokenListPopup.qml +++ b/ui/app/AppLayouts/Profile/popups/TokenListPopup.qml @@ -1,6 +1,7 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts +import QtQml.Models import StatusQ.Core import StatusQ.Controls @@ -15,7 +16,7 @@ import shared.panels import QtModelsToolkit -StatusDialog { +StatusAdaptiveDialog { id: root required property string sourceImage @@ -29,24 +30,21 @@ StatusDialog { QtObject { id: d - readonly property int symbolColumnWidth: 90 - readonly property int addressColumnWidth: 106 + readonly property real symbolColumnFraction: 1 / 6 + readonly property real addressColumnFraction: 1 / 5 readonly property int externalLinkBtnWidth: 32 - readonly property int maxTokenListBodyHeight: 420 } - width: 521 // by design - padding: 0 - horizontalPadding: Theme.padding - fillHeightOnBottomSheet: true + subtitle: qsTr("%n token(s)", "", root.tokensListModel.ModelCount.count) + leftHeaderComponent: StatusSmartIdenticon { + asset.name: root.sourceImage + asset.isImage: !!asset.name + } - contentItem: StatusListView { + contentComponent: StatusListView { id: list - topMargin: Theme.padding - bottomMargin: Theme.padding - // Cap ListView implicit height so delegates recycle instead of laying out the full list - implicitHeight: Math.min(d.maxTokenListBodyHeight, contentHeight + topMargin + bottomMargin) + ScrollBar.vertical: null model: root.tokensListModel @@ -64,7 +62,7 @@ StatusDialog { CustomHeaderDelegate {} } delegate: CustomDelegate { - width: contentItem.width + width: ListView.view.width height: 64 name: model.name @@ -77,19 +75,14 @@ StatusDialog { } } - header: StatusDialogHeader { - headline.title: root.title - headline.subtitle: qsTr("%n token(s)", "", root.tokensListModel.ModelCount.count) - actions.closeButton.onClicked: root.close() - leftComponent: StatusSmartIdenticon { - asset.name: root.sourceImage - asset.isImage: !!asset.name + footerRightButtons: ObjectModel { + StatusButton { + objectName: "tokenListPopupDoneButton" + text: qsTr("Done") + onClicked: root.close() } } - standardButtons: Dialog.Ok - okButtonText: qsTr("Done") - component CustomTextBlock: ColumnLayout { id: textBlock @@ -153,8 +146,10 @@ StatusDialog { } component CustomHeaderDelegate: RowLayout { + id: headerDelegate + height: 34 - width: contentItem.width + width: ListView.view ? ListView.view.width : 0 spacing: 0 StatusBaseText { @@ -167,7 +162,7 @@ StatusDialog { StatusBaseText { Layout.leftMargin: Theme.padding - Layout.preferredWidth: d.symbolColumnWidth - Layout.leftMargin + Layout.preferredWidth: headerDelegate.width * d.symbolColumnFraction - Layout.leftMargin Layout.alignment: Qt.AlignLeft text: qsTr("Symbol") @@ -176,7 +171,7 @@ StatusDialog { StatusBaseText { Layout.leftMargin: Theme.padding - Layout.preferredWidth: d.addressColumnWidth - Layout.leftMargin + Layout.preferredWidth: headerDelegate.width * d.addressColumnFraction - Layout.leftMargin Layout.alignment: Qt.AlignLeft text: qsTr("Address") @@ -249,7 +244,7 @@ StatusDialog { StatusBaseText { Layout.leftMargin: Theme.padding - Layout.preferredWidth: d.symbolColumnWidth - Layout.leftMargin + Layout.preferredWidth: customDelegate.width * d.symbolColumnFraction - Layout.leftMargin Layout.alignment: Qt.AlignLeft text: customDelegate.symbol @@ -258,7 +253,7 @@ StatusDialog { StatusBaseText { Layout.leftMargin: Theme.padding - Layout.preferredWidth: d.addressColumnWidth - Layout.leftMargin + Layout.preferredWidth: customDelegate.width * d.addressColumnFraction - Layout.leftMargin Layout.alignment: Qt.AlignLeft text: customDelegate.address