From e3dae7e1db8c474d41c0e0e6769a7a6f63b1dc93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Tinkl?= Date: Mon, 19 Aug 2024 11:00:35 +0200 Subject: [PATCH] feat(StatusButton) Add support to show text when button is loading - add a secondary "loading" state (`loadingWithText`), that is show the loading indicator next to the text - simplify the StatusBaseButton layout (esp. handling the overall opacity/visibility) - add a QML test suite; the code was becoming too complex and adding a simple boolean prop was getting "dangerous" - port the SwapModal to use the new `loadingWithText` property Fixes #15313 --- storybook/pages/StatusButtonPage.qml | 13 +- storybook/qmlTests/tests/tst_StatusButton.qml | 259 ++++++++++++++++++ storybook/qmlTests/tests/tst_SwapModal.qml | 32 +-- .../src/StatusQ/Controls/StatusBaseButton.qml | 151 +++++----- .../AppLayouts/Wallet/panels/WalletHeader.qml | 1 - .../Wallet/popups/swap/SwapModal.qml | 12 +- 6 files changed, 377 insertions(+), 91 deletions(-) create mode 100644 storybook/qmlTests/tests/tst_StatusButton.qml diff --git a/storybook/pages/StatusButtonPage.qml b/storybook/pages/StatusButtonPage.qml index c82e7dcdc7..cebfd4ed47 100644 --- a/storybook/pages/StatusButtonPage.qml +++ b/storybook/pages/StatusButtonPage.qml @@ -55,6 +55,7 @@ SplitView { textPosition: d.effectiveTextPosition type: ctrlType.currentIndex loading: ctrlLoading.checked + loadingWithText: ctrlLoadingWithText.checked enabled: ctrlEnabled.checked interactive: ctrlInteractive.checked textFillWidth: ctrlFillWidth.checked @@ -73,6 +74,7 @@ SplitView { textPosition: d.effectiveTextPosition type: ctrlType.currentIndex loading: ctrlLoading.checked + loadingWithText: ctrlLoadingWithText.checked enabled: ctrlEnabled.checked interactive: ctrlInteractive.checked textFillWidth: ctrlFillWidth.checked @@ -92,6 +94,7 @@ SplitView { textPosition: d.effectiveTextPosition type: ctrlType.currentIndex loading: ctrlLoading.checked + loadingWithText: ctrlLoadingWithText.checked enabled: ctrlEnabled.checked interactive: ctrlInteractive.checked textFillWidth: ctrlFillWidth.checked @@ -111,10 +114,10 @@ SplitView { textPosition: d.effectiveTextPosition type: ctrlType.currentIndex loading: ctrlLoading.checked + loadingWithText: ctrlLoadingWithText.checked enabled: ctrlEnabled.checked interactive: ctrlInteractive.checked isRoundIcon: true - radius: height/2 textFillWidth: ctrlFillWidth.checked } } @@ -137,6 +140,7 @@ SplitView { textPosition: d.effectiveTextPosition type: ctrlType.currentIndex loading: ctrlLoading.checked + loadingWithText: ctrlLoadingWithText.checked enabled: ctrlEnabled.checked interactive: ctrlInteractive.checked textFillWidth: ctrlFillWidth.checked @@ -155,6 +159,7 @@ SplitView { textPosition: d.effectiveTextPosition type: ctrlType.currentIndex loading: ctrlLoading.checked + loadingWithText: ctrlLoadingWithText.checked enabled: ctrlEnabled.checked interactive: ctrlInteractive.checked textFillWidth: ctrlFillWidth.checked @@ -174,6 +179,7 @@ SplitView { textPosition: d.effectiveTextPosition type: ctrlType.currentIndex loading: ctrlLoading.checked + loadingWithText: ctrlLoadingWithText.checked enabled: ctrlEnabled.checked interactive: ctrlInteractive.checked textFillWidth: ctrlFillWidth.checked @@ -193,6 +199,7 @@ SplitView { textPosition: d.effectiveTextPosition type: ctrlType.currentIndex loading: ctrlLoading.checked + loadingWithText: ctrlLoadingWithText.checked enabled: ctrlEnabled.checked interactive: ctrlInteractive.checked isRoundIcon: true @@ -284,6 +291,10 @@ SplitView { id: ctrlLoading text: "Loading" } + Switch { + id: ctrlLoadingWithText + text: "Loading with text" + } Switch { id: ctrlInteractive text: "Interactive" diff --git a/storybook/qmlTests/tests/tst_StatusButton.qml b/storybook/qmlTests/tests/tst_StatusButton.qml new file mode 100644 index 0000000000..a89f82ebb7 --- /dev/null +++ b/storybook/qmlTests/tests/tst_StatusButton.qml @@ -0,0 +1,259 @@ +import QtQuick 2.15 +import QtTest 1.15 + +import StatusQ.Controls 0.1 + +import Models 1.0 + +Item { + id: root + width: 600 + height: 400 + + Component { + id: componentUnderTest + StatusButton { + anchors.centerIn: parent + } + } + + SignalSpy { + id: signalSpy + target: controlUnderTest + signalName: "clicked" + } + + property StatusButton controlUnderTest: null + + TestCase { + name: "StatusButton" + when: windowShown + + function cleanup() { + if (!!controlUnderTest) + controlUnderTest.destroy() + signalSpy.clear() + } + + function test_defaults() { + controlUnderTest = createTemporaryObject(componentUnderTest, root, { text: "Hello" }) + verify(!!controlUnderTest) + verify(controlUnderTest.width > 0) + verify(controlUnderTest.height > 0) + verify(controlUnderTest.interactive) + verify(controlUnderTest.enabled) + verify(!controlUnderTest.loading) + verify(!controlUnderTest.loadingWithText) + verify(!controlUnderTest.isRoundIcon) + } + + function test_text() { + const textToTest = "Hello" + controlUnderTest = createTemporaryObject(componentUnderTest, root, { text: textToTest }) + verify(!!controlUnderTest) + + const buttonText = findChild(controlUnderTest, "buttonText") + verify(!!buttonText) + verify(buttonText.visible) + compare(buttonText.text, textToTest) + + // verify the icon is not visible + const buttonIcon = findChild(controlUnderTest, "buttonIcon") + verify(!!buttonIcon) + compare(buttonIcon.item, null) + } + + function test_textLeftRight() { + const textToTest = "Hello" + controlUnderTest = createTemporaryObject(componentUnderTest, root, { text: textToTest, "icon.name": "gif" }) + verify(!!controlUnderTest) + compare(controlUnderTest.textPosition, StatusBaseButton.TextPosition.Right) + + const leftTextLoader = findChild(controlUnderTest, "leftTextLoader") + verify(!!leftTextLoader) + verify(!leftTextLoader.active) + + const rightTextLoader = findChild(controlUnderTest, "rightTextLoader") + verify(!!rightTextLoader) + verify(rightTextLoader.active) + + // set the text to appear on the right, verify the text position + controlUnderTest.textPosition = StatusBaseButton.TextPosition.Left + verify(leftTextLoader.active) + verify(!rightTextLoader.active) + } + + function test_elideText() { + const textToTest = "This is a very long text that should be elided" + controlUnderTest = createTemporaryObject(componentUnderTest, root, { text: textToTest }) + verify(!!controlUnderTest) + controlUnderTest.width = 100 + + // verify the text is visible and truncated (elided) + const buttonText = findChild(controlUnderTest, "buttonText") + verify(!!buttonText) + verify(buttonText.visible) + verify(buttonText.truncated) + } + + function test_icon_data() { + return [ + { tag: "empty", name: "", iconVisible: false }, + { tag: "gif", name: "gif", iconVisible: true }, + { tag: "invalid", name: "bflmpsvz", iconVisible: false }, + { tag: "blob", name: ModelsData.icons.status, iconVisible: true }, + { tag: "fileUrl", name: ModelsData.assets.snt, iconVisible: true }, + ] + } + + function test_icon(data) { + controlUnderTest = createTemporaryObject(componentUnderTest, root, { "icon.name": data.name }) + verify(!!controlUnderTest) + + const buttonIcon = findChild(controlUnderTest, "buttonIcon") + verify(!!buttonIcon) + + if (data.iconVisible) + verify(buttonIcon.item.visible) + + // verify the button is square with rounded edges + compare(controlUnderTest.width, controlUnderTest.height) + verify(controlUnderTest.radius != controlUnderTest.width/2) + + // verify the text is not visible + const buttonText = findChild(controlUnderTest, "buttonText") + compare(buttonText, null) + } + + function test_roundIcon() { + controlUnderTest = createTemporaryObject(componentUnderTest, root, { "icon.name": "gif", "isRoundIcon": true }) + verify(!!controlUnderTest) + + const buttonIcon = findChild(controlUnderTest, "buttonIcon") + verify(!!buttonIcon) + verify(buttonIcon.item.visible) + + // verify that the button is rounded + compare(controlUnderTest.width, controlUnderTest.height) + compare(controlUnderTest.radius, controlUnderTest.width/2) + } + + function test_emoji() { + controlUnderTest = createTemporaryObject(componentUnderTest, root, { text: "Hello" }) + verify(!!controlUnderTest) + + const buttonEmoji = findChild(controlUnderTest, "buttonEmoji") + verify(!!buttonEmoji) + verify(!buttonEmoji.visible) + + // set some emoji, verify it's visible + controlUnderTest.asset.emoji = "💩" + verify(buttonEmoji.visible) + + // unset the emoji, verify it's not visible + controlUnderTest.asset.emoji = "" + verify(!buttonEmoji.visible) + } + + function test_textAndIcon_data() { + return test_icon_data() + } + + function test_textAndIcon(data) { + const textToTest = "Hello" + controlUnderTest = createTemporaryObject(componentUnderTest, root, { text: textToTest, "icon.name": data.name }) + verify(!!controlUnderTest) + + const buttonIcon = findChild(controlUnderTest, "buttonIcon") + verify(!!buttonIcon) + + if (data.iconVisible) + verify(buttonIcon.item.visible) + + // verify the button is not square/round + verify(controlUnderTest.width > controlUnderTest.height) + + // verify the text is visible too + const buttonText = findChild(controlUnderTest, "buttonText") + verify(!!buttonText) + verify(buttonText.visible) + compare(buttonText.text, textToTest) + } + + function test_interactiveAndEnabled_data() { + return [ + { tag: "enabled", enabled: true, interactive: true, spyCount: 1, tooltip: true }, + { tag: "not enabled + interactive", enabled: false, interactive: true, spyCount: 0, tooltip: false }, + { tag: "enabled + not interactive", enabled: true, interactive: false, spyCount: 0, tooltip: true }, + { tag: "not enabled + not interactive", enabled: false, interactive: false, spyCount: 0, tooltip: false }, + ] + } + + function test_interactiveAndEnabled(data) { + controlUnderTest = createTemporaryObject(componentUnderTest, root, { text: "Hello", "tooltip.text": "This is a tooltip" }) + verify(!!controlUnderTest) + controlUnderTest.enabled = data.enabled + controlUnderTest.interactive = data.interactive + + // verify the tooltip is visible in interactive or enabled when hovered + const buttonTooltip = findChild(controlUnderTest, "buttonTooltip") + verify(!!buttonTooltip) + mouseMove(controlUnderTest, controlUnderTest.width/2, controlUnderTest.height/2) + waitForItemPolished(buttonTooltip.contentItem) + tryCompare(buttonTooltip, "opened", data.tooltip) + + // verify the click goes thru (or not) as expected + mouseClick(controlUnderTest) + compare(signalSpy.count, data.spyCount) + } + + function test_loadingIndicators() { + controlUnderTest = + createTemporaryObject(componentUnderTest, root, + { text: "Hello", "icon.name": "gif", "asset.emoji": "💩", "tooltip.text": "This is a tooltip" }) + verify(!!controlUnderTest) + + const buttonIcon = findChild(controlUnderTest, "buttonIcon") + verify(!!buttonIcon) + compare(buttonIcon.visible, true) + + const buttonText = findChild(controlUnderTest, "buttonText") + verify(!!buttonText) + compare(buttonText.visible, true) + + const buttonEmoji = findChild(controlUnderTest, "buttonEmoji") + verify(!!buttonEmoji) + compare(buttonEmoji.visible, true) + + const loadingIndicator = findChild(controlUnderTest, "loadingIndicator") + verify(!!loadingIndicator) + compare(loadingIndicator.visible, false) + compare(controlUnderTest.contentItem.opacity, 1) + + // verify when the overall indicator is running, nothing else is visible + controlUnderTest.loading = true + compare(loadingIndicator.visible, true) + compare(controlUnderTest.contentItem.opacity, 0) // the loading indicator is above the contentItem + + // stop the loadingIndicator, verify icon, emoji and text are visible again + controlUnderTest.loading = false + compare(loadingIndicator.visible, false) + compare(controlUnderTest.contentItem.opacity, 1) + compare(buttonIcon.visible, true) + compare(buttonText.visible, true) + compare(buttonEmoji.visible, true) + + // start the loadingWithText indicator, verify it and text are visible, icon and emoji not + const loadingWithTextIndicator = findChild(controlUnderTest, "loadingWithTextIndicator") + verify(!!loadingWithTextIndicator) + verify(!controlUnderTest.loadingWithText) + compare(loadingWithTextIndicator.visible, false) + + controlUnderTest.loadingWithText = true + compare(loadingWithTextIndicator.visible, true) + compare(buttonIcon.visible, false) + compare(buttonText.visible, true) + compare(buttonEmoji.visible, false) + } + } +} diff --git a/storybook/qmlTests/tests/tst_SwapModal.qml b/storybook/qmlTests/tests/tst_SwapModal.qml index 9eec289168..86e5da167e 100644 --- a/storybook/qmlTests/tests/tst_SwapModal.qml +++ b/storybook/qmlTests/tests/tst_SwapModal.qml @@ -570,7 +570,7 @@ Item { // Check max fees values and sign button state when nothing is set compare(maxFeesText.text, qsTr("Max fees:")) compare(maxFeesValue.text, "--") - verify(!signButton.enabled) + verify(!signButton.interactive) verify(!errorTag.visible) // set input values in the form correctly @@ -606,7 +606,7 @@ Item { compare(root.swapAdaptor.swapOutputData.hasError, true) verify(errorTag.visible) verify(errorTag.text, qsTr("An error has occured, please try again")) - verify(!signButton.enabled) + verify(!signButton.interactive) compare(signButton.text, qsTr("Swap")) // verfy input and output panels @@ -644,7 +644,7 @@ Item { compare(root.swapAdaptor.swapOutputData.hasError, true) verify(errorTag.visible) verify(errorTag.text, qsTr("Insufficient funds for swap")) - verify(!signButton.enabled) + verify(!signButton.interactive) compare(signButton.text, qsTr("Swap")) // verfy input and output panels @@ -682,7 +682,7 @@ Item { compare(root.swapAdaptor.swapOutputData.hasError, true) verify(errorTag.visible) verify(errorTag.text, qsTr("Insufficient funds to pay gas fees")) - verify(!signButton.enabled) + verify(!signButton.interactive) compare(signButton.text, qsTr("Swap")) // verfy input and output panels @@ -720,7 +720,7 @@ Item { compare(root.swapAdaptor.swapOutputData.hasError, true) verify(errorTag.visible) verify(errorTag.text, qsTr("Fetching the price took longer than expected. Please, try again later.")) - verify(!signButton.enabled) + verify(!signButton.interactive) compare(signButton.text, qsTr("Swap")) // verfy input and output panels @@ -758,7 +758,7 @@ Item { compare(root.swapAdaptor.swapOutputData.hasError, true) verify(errorTag.visible) verify(errorTag.text, qsTr("Not enough liquidity. Lower token amount or try again later.")) - verify(!signButton.enabled) + verify(!signButton.interactive) compare(signButton.text, qsTr("Swap")) // verfy input and output panels @@ -1487,7 +1487,7 @@ Item { // Check max fees values and sign button state when nothing is set compare(maxFeesValue.text, "--") - verify(!signButton.enabled) + verify(!signButton.interactive) verify(!errorTag.visible) // set input values in the form correctly @@ -1536,7 +1536,7 @@ Item { verify(!errorTag.visible) verify(signButton.enabled) - verify(!signButton.loading) + verify(!signButton.loadingWithText) compare(signButton.text, qsTr("Approve %1").arg(root.swapAdaptor.fromToken.symbol)) // TODO: note that there is a loss of precision as the approvalGasFees is currently passes as float from the backend and not string. compare(maxFeesValue.text, root.swapAdaptor.currencyStore.formatCurrencyAmount( @@ -1549,8 +1549,8 @@ Item { verify(root.swapAdaptor.approvalPending) verify(!root.swapAdaptor.approvalSuccessful) verify(!errorTag.visible) - verify(!signButton.enabled) - verify(signButton.loading) + verify(!signButton.interactive) + verify(signButton.loadingWithText) compare(signButton.text, qsTr("Approving %1").arg(root.swapAdaptor.fromToken.symbol)) // TODO: note that there is a loss of precision as the approvalGasFees is currently passes as float from the backend and not string. compare(maxFeesValue.text, root.swapAdaptor.currencyStore.formatCurrencyAmount( @@ -1564,7 +1564,7 @@ Item { verify(!root.swapAdaptor.approvalSuccessful) verify(!errorTag.visible) verify(signButton.enabled) - verify(!signButton.loading) + verify(!signButton.loadingWithText) compare(signButton.text, qsTr("Approve %1").arg(root.swapAdaptor.fromToken.symbol)) // TODO: note that there is a loss of precision as the approvalGasFees is currently passes as float from the backend and not string. compare(maxFeesValue.text, root.swapAdaptor.currencyStore.formatCurrencyAmount( @@ -1578,8 +1578,8 @@ Item { verify(root.swapAdaptor.approvalPending) verify(!root.swapAdaptor.approvalSuccessful) verify(!errorTag.visible) - verify(!signButton.enabled) - verify(signButton.loading) + verify(!signButton.interactive) + verify(signButton.loadingWithText) compare(signButton.text, qsTr("Approving %1").arg(root.swapAdaptor.fromToken.symbol)) // TODO: note that there is a loss of precision as the approvalGasFees is currently passes as float from the backend and not string. compare(maxFeesValue.text, root.swapAdaptor.currencyStore.formatCurrencyAmount( @@ -1599,8 +1599,8 @@ Item { verify(!root.swapAdaptor.approvalPending) verify(!root.swapAdaptor.approvalSuccessful) verify(!errorTag.visible) - verify(!signButton.enabled) - verify(!signButton.loading) + verify(!signButton.interactive) + verify(!signButton.loadingWithText) compare(signButton.text, qsTr("Swap")) compare(maxFeesValue.text, Constants.dummyText) @@ -1612,7 +1612,7 @@ Item { verify(!root.swapAdaptor.approvalSuccessful) verify(!errorTag.visible) verify(signButton.enabled) - verify(!signButton.loading) + verify(!signButton.loadingWithText) compare(signButton.text, qsTr("Swap")) compare(maxFeesValue.text, root.swapAdaptor.currencyStore.formatCurrencyAmount( root.swapAdaptor.swapOutputData.totalFees, diff --git a/ui/StatusQ/src/StatusQ/Controls/StatusBaseButton.qml b/ui/StatusQ/src/StatusQ/Controls/StatusBaseButton.qml index d011ff031d..9e195dcdd1 100644 --- a/ui/StatusQ/src/StatusQ/Controls/StatusBaseButton.qml +++ b/ui/StatusQ/src/StatusQ/Controls/StatusBaseButton.qml @@ -1,6 +1,6 @@ -import QtQuick 2.14 -import QtQuick.Layouts 1.14 -import QtQuick.Controls 2.14 +import QtQuick 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Controls 2.15 import StatusQ.Core 0.1 import StatusQ.Core.Theme 0.1 @@ -36,6 +36,7 @@ Button { property alias tooltip: tooltip property bool loading + property bool loadingWithText // loading indicator instead of icon, mutually exclusive with `loading` property bool interactive: true property color normalColor @@ -49,15 +50,12 @@ Button { property int borderWidth: 0 property bool textFillWidth: false - property int radius: size === StatusBaseButton.Size.Tiny ? 6 : 8 + property int radius: isRoundIcon ? height/2 : size === StatusBaseButton.Size.Tiny ? 6 : 8 property int size: StatusBaseButton.Size.Large property int type: StatusBaseButton.Type.Normal property int textPosition: StatusBaseButton.TextPosition.Right - // use Size.Small as default value to not change old behavior which had default size of 20x20 - property int loadingIndicatorSize: StatusBaseButton.Size.Small - property bool isRoundIcon: false QtObject { @@ -72,10 +70,8 @@ Button { } readonly property bool iconOnly: root.display === AbstractButton.IconOnly || root.text === "" - readonly property int iconSize: mapIconSize(root.size) - - function mapIconSize(buttonSize) { - switch(buttonSize) { + readonly property int iconSize: { + switch(root.size) { case StatusBaseButton.Size.Tiny: return 16 case StatusBaseButton.Size.Small: @@ -136,86 +132,64 @@ Button { color: { if (!root.enabled || !root.interactive) return disabledColor - return !root.loading && (root.hovered || root.highlighted) ? hoverColor : normalColor + return !root.loading && !root.loadingWithText && (root.hovered || root.highlighted) ? hoverColor : normalColor } } contentItem: Item { implicitWidth: layout.implicitWidth implicitHeight: layout.implicitHeight + opacity: !root.loading RowLayout { id: layout anchors.centerIn: parent - width: root.textFillWidth ? root.availableWidth : Math.min(root.availableWidth, implicitWidth) + width: root.textFillWidth && !d.iconOnly ? root.availableWidth : Math.min(root.availableWidth, implicitWidth) height: Math.min(root.availableHeight, implicitHeight) spacing: root.spacing - Component { - id: baseIcon - - StatusIcon { - icon: root.icon.name - rotation: root.asset.rotation - mirror: root.asset.mirror - opacity: !root.loading && root.icon.name !== "" && root.display !== AbstractButton.TextOnly - color: root.icon.color - } - } - - Component { - id: roundIcon - - StatusRoundIcon { - opacity: !root.loading && root.icon.name !== "" && root.display !== AbstractButton.TextOnly - asset.name: root.icon.name - asset.width: d.iconSize - asset.height: d.iconSize - asset.color: root.icon.color - asset.bgColor: root.asset.bgColor - } - } - - Component { - id: text - - StatusBaseText { - opacity: !root.loading - font: root.font - text: root.text - color: d.textColor - elide: Text.ElideRight - maximumLineCount: 1 - } - } - + // text left Loader { + objectName: "leftTextLoader" Layout.fillWidth: true active: root.textPosition === StatusBaseButton.TextPosition.Left && !d.iconOnly visible: active sourceComponent: text } + // loading with text indicator Loader { - id: iconLoader + objectName: "loadingWithTextIndicator" + active: root.loadingWithText + visible: active + sourceComponent: loadingComponent + } - Layout.preferredWidth: active ? root.icon.width : 0 - Layout.preferredHeight: active ? root.icon.height : 0 + // decoration + Loader { + objectName: "buttonIcon" + Layout.preferredWidth: root.icon.width + Layout.preferredHeight: root.icon.height Layout.alignment: Qt.AlignCenter - active: root.icon.name !== "" + active: root.icon.name !== "" && root.display !== AbstractButton.TextOnly && !root.loadingWithText + visible: active sourceComponent: root.isRoundIcon ? roundIcon : baseIcon } + // emoji StatusEmoji { - Layout.preferredWidth: visible ? root.icon.width : 0 - Layout.preferredHeight: visible ? root.icon.height : 0 + objectName: "buttonEmoji" + Layout.preferredWidth: root.icon.width + Layout.preferredHeight: root.icon.height Layout.alignment: Qt.AlignCenter - opacity: !root.loading && root.display !== AbstractButton.TextOnly - visible: root.asset.emoji + visible: root.asset.emoji && root.display !== AbstractButton.TextOnly && !root.loadingWithText emojiId: Emoji.iconId(root.asset.emoji, root.asset.emojiSize) || "" + opacity: !root.enabled || !root.interactive ? 0.4 : 1 } + // text right Loader { + objectName: "rightTextLoader" Layout.fillWidth: true active: root.textPosition === StatusBaseButton.TextPosition.Right && !d.iconOnly visible: active @@ -225,13 +199,11 @@ Button { } Loader { + objectName: "loadingIndicator" anchors.centerIn: parent active: root.loading - width: d.mapIconSize(root.loadingIndicatorSize) - height: width - sourceComponent: StatusLoadingIndicator { - color: d.textColor - } + visible: active + sourceComponent: loadingComponent } // stop the mouse clicks in the "loading" or non-interactive state w/o disabling the whole button @@ -240,15 +212,62 @@ Button { id: mouseArea anchors.fill: parent acceptedButtons: Qt.AllButtons - enabled: root.loading || !root.interactive + enabled: root.loading || root.loadingWithText || !root.interactive onPressed: mouse.accepted = true onWheel: wheel.accepted = true - cursorShape: root.interactive && !root.loading ? Qt.PointingHandCursor: undefined // always works; 'undefined' resets to default cursor + cursorShape: root.interactive && !root.loading && !root.loadingWithText ? Qt.PointingHandCursor: undefined // always works; 'undefined' resets to default cursor } StatusToolTip { id: tooltip + objectName: "buttonTooltip" visible: tooltip.text !== "" && root.hovered offset: -(tooltip.x + tooltip.width/2 - root.width/2) } + + Component { + id: baseIcon + + StatusIcon { + icon: root.icon.name + rotation: root.asset.rotation + mirror: root.asset.mirror + color: root.icon.color + } + } + + Component { + id: roundIcon + + StatusRoundIcon { + asset.name: root.icon.name + asset.width: root.icon.width + asset.height: root.icon.height + asset.color: root.icon.color + asset.bgColor: root.asset.bgColor + } + } + + Component { + id: text + + StatusBaseText { + objectName: "buttonText" + font: root.font + text: root.text + color: d.textColor + elide: Text.ElideRight + maximumLineCount: 1 + horizontalAlignment: root.textFillWidth ? Text.AlignLeft : Text.AlignHCenter + } + } + + Component { + id: loadingComponent + StatusLoadingIndicator { + width: root.icon.width + height: root.icon.height + color: d.textColor + } + } } diff --git a/ui/app/AppLayouts/Wallet/panels/WalletHeader.qml b/ui/app/AppLayouts/Wallet/panels/WalletHeader.qml index 8fa1247788..34652e0684 100644 --- a/ui/app/AppLayouts/Wallet/panels/WalletHeader.qml +++ b/ui/app/AppLayouts/Wallet/panels/WalletHeader.qml @@ -86,7 +86,6 @@ Item { StatusButton { id: reloadButton size: StatusBaseButton.Size.Tiny - loadingIndicatorSize: size height: parent.height width: height borderColor: Theme.palette.directColor7 diff --git a/ui/app/AppLayouts/Wallet/popups/swap/SwapModal.qml b/ui/app/AppLayouts/Wallet/popups/swap/SwapModal.qml index 6e9d5ccb16..87a56cd5b8 100644 --- a/ui/app/AppLayouts/Wallet/popups/swap/SwapModal.qml +++ b/ui/app/AppLayouts/Wallet/popups/swap/SwapModal.qml @@ -399,12 +399,10 @@ StatusDialog { loading: root.swapAdaptor.swapProposalLoading } } - /* TODO: https://github.com/status-im/status-desktop/issues/15313 - will introduce having loading button and showing text on the side*/ StatusButton { objectName: "signButton" readonly property string fromTokenSymbol: !!root.swapAdaptor.fromToken ? root.swapAdaptor.fromToken.symbol ?? "" : "" - loading: root.swapAdaptor.approvalPending + loadingWithText: root.swapAdaptor.approvalPending icon.name: root.swapAdaptor.selectedAccount.migratedToKeycard ? Constants.authenticationIconByType[Constants.LoginType.Keycard] : Constants.authenticationIconByType[root.loginType] text: { @@ -421,10 +419,10 @@ StatusDialog { root.swapAdaptor.swapOutputData.approvalNeeded ? qsTr("Approve %1 spending cap to Swap").arg(fromTokenSymbol) : "" disabledColor: Theme.palette.directColor8 - enabled: root.swapAdaptor.validSwapProposalReceived && - editSlippagePanel.valid && - !d.isError && - !root.swapAdaptor.approvalPending + interactive: root.swapAdaptor.validSwapProposalReceived && + editSlippagePanel.valid && + !d.isError && + !root.swapAdaptor.approvalPending onClicked: { if (root.swapAdaptor.validSwapProposalReceived) { if (root.swapAdaptor.swapOutputData.approvalNeeded)