From f4473f304227ccc7074e3c2dc1172220302182e7 Mon Sep 17 00:00:00 2001 From: Emil Sawicki Date: Mon, 17 Feb 2025 07:31:26 +0100 Subject: [PATCH] feat: Adjust send model height depending on contents --- storybook/pages/RecipientViewAdaptorPage.qml | 4 ++ storybook/pages/RecipientViewDelegatePage.qml | 2 + storybook/pages/RecipientViewPage.qml | 2 + storybook/pages/SimpleSendModalPage.qml | 32 ++++++++-- .../pages/SimpleSendRecipientInputPage.qml | 2 + .../tests/tst_RecipientViewAdaptor.qml | 5 +- .../Wallet/adaptors/RecipientViewAdaptor.qml | 33 ++++------- .../Wallet/panels/RecipientSelectorPanel.qml | 43 ++++++++++---- .../Wallet/panels/StickySendModalHeader.qml | 54 +++++++++-------- .../popups/simpleSend/SimpleSendModal.qml | 59 ++++++++++++------- .../Wallet/views/SendModalFooter.qml | 48 +++++++++------ ui/app/mainui/SendModalHandler.qml | 1 + 12 files changed, 188 insertions(+), 97 deletions(-) diff --git a/storybook/pages/RecipientViewAdaptorPage.qml b/storybook/pages/RecipientViewAdaptorPage.qml index 620343a5304..c581a6f1d58 100644 --- a/storybook/pages/RecipientViewAdaptorPage.qml +++ b/storybook/pages/RecipientViewAdaptorPage.qml @@ -317,6 +317,10 @@ Item { color: "lightgray" } + Label { + text: "Highest tab element count: " + adaptor.highestTabElementCount + } + RowLayout { ColumnLayout { Layout.fillWidth: true diff --git a/storybook/pages/RecipientViewDelegatePage.qml b/storybook/pages/RecipientViewDelegatePage.qml index 7c9c1155b7e..f9f3d45d0ed 100644 --- a/storybook/pages/RecipientViewDelegatePage.qml +++ b/storybook/pages/RecipientViewDelegatePage.qml @@ -107,3 +107,5 @@ SplitView { } } } + +// category: Components diff --git a/storybook/pages/RecipientViewPage.qml b/storybook/pages/RecipientViewPage.qml index d74d772120e..af03f9909bc 100644 --- a/storybook/pages/RecipientViewPage.qml +++ b/storybook/pages/RecipientViewPage.qml @@ -242,3 +242,5 @@ SplitView { SplitView.preferredWidth: 300 } } + +// category: Panels diff --git a/storybook/pages/SimpleSendModalPage.qml b/storybook/pages/SimpleSendModalPage.qml index 260ecc50610..3269905c8c8 100644 --- a/storybook/pages/SimpleSendModalPage.qml +++ b/storybook/pages/SimpleSendModalPage.qml @@ -52,19 +52,26 @@ SplitView { } readonly property var savedAddressesModel: ListModel { - Component.onCompleted: { - for (let i = 0; i < 10; i++) - append({ + function populateModel(count) { + if (count <= 0) + return + + let data = [] + for (let i = 0; i < count - 1; i++) + data.push({ name: "some saved addr name " + i, ens: "", address: "0x2B748A02e06B159C7C3E98F5064577B96E55A7b4", }) - append({ + data.push({ name: "some saved ENS name ", ens: "me@status.eth", address: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc4", }) + append(data) } + + Component.onCompleted: populateModel(savedAddressesCount.value) } property var setFees: Backpressure.debounce(root, 1500, function () { @@ -133,6 +140,8 @@ SplitView { recipientsModel: recipientViewAdaptor.recipientsModel recipientsFilterModel: recipientViewAdaptor.recipientsFilterModel + highestTabElementCount: recipientViewAdaptor.highestTabElementCount + currentCurrency: "USD" fnFormatCurrencyAmount: d.formatCurrencyAmount @@ -733,6 +742,21 @@ SplitView { } } + Text { + text: "Number of saved wallet accounts" + } + SpinBox { + id: savedAddressesCount + editable: true + from: 0 + to: 100 + value: 10 + onValueModified: { + d.savedAddressesModel.clear() + d.savedAddressesModel.populateModel(value) + } + } + Text { text: "account selected is: \n" + simpleSend.selectedAccountAddress diff --git a/storybook/pages/SimpleSendRecipientInputPage.qml b/storybook/pages/SimpleSendRecipientInputPage.qml index e4f0afdd026..ae3400ca888 100644 --- a/storybook/pages/SimpleSendRecipientInputPage.qml +++ b/storybook/pages/SimpleSendRecipientInputPage.qml @@ -79,3 +79,5 @@ SplitView { } } } + +// category: Controls diff --git a/storybook/qmlTests/tests/tst_RecipientViewAdaptor.qml b/storybook/qmlTests/tests/tst_RecipientViewAdaptor.qml index 0ced523740d..8a5fbf1d7bc 100644 --- a/storybook/qmlTests/tests/tst_RecipientViewAdaptor.qml +++ b/storybook/qmlTests/tests/tst_RecipientViewAdaptor.qml @@ -315,6 +315,7 @@ Item { compare(adaptor.selectedRecipientType, 0) compare(model.ModelCount.count, 11) compare(adaptor.recipientsFilterModel.ModelCount.count, 11) + compare(adaptor.highestTabElementCount, 4) adaptor.selectedRecipientType = Constants.RecipientAddressObjectType.RecentsAddress compare(model.ModelCount.count, 3) @@ -322,6 +323,7 @@ Item { compare(ModelUtils.get(model, 0, "address"), "0x7F47C2e18a4BBf5487E6fb082eC2D9Ab0E6d7240") compare(ModelUtils.get(model, 1, "address"), "0xebfbfe4072ebb77e53aa9117c7300531d1511111") compare(ModelUtils.get(model, 2, "address"), "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8882") + compare(adaptor.highestTabElementCount, 4) adaptor.selectedRecipientType = Constants.RecipientAddressObjectType.SavedAddress compare(model.ModelCount.count, 4) @@ -330,6 +332,7 @@ Item { compare(ModelUtils.get(model, 1, "address"), "0x28F00D9d64bc7B41003F8217A74c66f76199E21D") compare(ModelUtils.get(model, 2, "address"), "0xE6bf08d897C8f4140647b51eB20D4c764b2Fb168") compare(ModelUtils.get(model, 3, "address"), "0xc5250feE40ABb4f5E2A5DDE62065ca6A9A6010A9") + compare(adaptor.highestTabElementCount, 4) adaptor.selectedRecipientType = Constants.RecipientAddressObjectType.Account compare(model.ModelCount.count, 4) @@ -338,6 +341,7 @@ Item { compare(ModelUtils.get(model, 1, "address"), "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8881") compare(ModelUtils.get(model, 2, "address"), "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8882") compare(ModelUtils.get(model, 3, "address"), "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8884") + compare(adaptor.highestTabElementCount, 4) } function test_patternFiltering() { @@ -449,7 +453,6 @@ Item { recipientItem = ModelUtils.getByKey(adaptor.recipientsModel, "address", address) verify(!!recipientItem) verify(recipientItem.cherrypicked, "Entry from recents model. Data is cherrypicked from account input model") - } } } diff --git a/ui/app/AppLayouts/Wallet/adaptors/RecipientViewAdaptor.qml b/ui/app/AppLayouts/Wallet/adaptors/RecipientViewAdaptor.qml index 4209d606981..c294b2adab8 100644 --- a/ui/app/AppLayouts/Wallet/adaptors/RecipientViewAdaptor.qml +++ b/ui/app/AppLayouts/Wallet/adaptors/RecipientViewAdaptor.qml @@ -60,28 +60,24 @@ QObject { /** Currently selected sender address **/ property string selectedSenderAddress + /** Maximum number of tab elements from all tabs in tab bar **/ + readonly property int highestTabElementCount: Math.max(accountsModelProxyModel.ModelCount.count, root.savedAddressesModel.ModelCount.count, recentsModel.ModelCount.count) + readonly property SortFilterProxyModel recipientsModel: SortFilterProxyModel { objectName: "RecipientViewAdaptor_recipientsModel" sourceModel: concatModel - function isSameModelType(recipientType, whichModel) { - switch(recipientType) { - case Constants.RecipientAddressObjectType.RecentsAddress: - return whichModel === concatModel.recentRecipientsModelKey - case Constants.RecipientAddressObjectType.SavedAddress: - return whichModel === concatModel.savedAddressesModelKey - case Constants.RecipientAddressObjectType.Account: - return whichModel === concatModel.accountsModelKey - default: - return true - } + function isSameModelType(selectedType, recipientType) { + if (!selectedType || selectedType === Constants.RecipientAddressObjectType.Address) + return true + return selectedType === Number(recipientType) } filters: [ FastExpressionFilter { - expectedRoles: ["which_model"] - expression: root.recipientsModel.isSameModelType(root.selectedRecipientType, model.which_model) + expectedRoles: ["recipient_type"] + expression: root.recipientsModel.isSameModelType(root.selectedRecipientType, model.recipient_type) } ] } @@ -123,26 +119,23 @@ QObject { ConcatModel { id: concatModel objectName: "RecipientViewAdaptor_concatModel" - readonly property string accountsModelKey: "accounts_model" - readonly property string savedAddressesModelKey: "saved_addresses_model" - readonly property string recentRecipientsModelKey: "recents_model" sources: [ SourceModel { model: accountsModelProxyModel - markerRoleValue: concatModel.accountsModelKey + markerRoleValue: Constants.RecipientAddressObjectType.Account }, SourceModel { model: root.savedAddressesModel - markerRoleValue: concatModel.savedAddressesModelKey + markerRoleValue: Constants.RecipientAddressObjectType.SavedAddress }, SourceModel { model: recentsModel - markerRoleValue: concatModel.recentRecipientsModelKey + markerRoleValue: Constants.RecipientAddressObjectType.RecentsAddress } ] expectedRoles: ["name", "address", "color", "colorId", "emoji", "ens", "cherrypicked", "duplicate"] - markerRoleName: "which_model" + markerRoleName: "recipient_type" } SortFilterProxyModel { diff --git a/ui/app/AppLayouts/Wallet/panels/RecipientSelectorPanel.qml b/ui/app/AppLayouts/Wallet/panels/RecipientSelectorPanel.qml index 5a6ea3f1287..fba09d50bc1 100644 --- a/ui/app/AppLayouts/Wallet/panels/RecipientSelectorPanel.qml +++ b/ui/app/AppLayouts/Wallet/panels/RecipientSelectorPanel.qml @@ -33,17 +33,29 @@ Rectangle { /** Search pattern in recipient view input **/ readonly property string searchPattern: recipientInputLoader.searchPattern - /** Currently viewed tab is empty **/ - readonly property bool emptyListVisible: emptyListText.visible && !selectedRecipientAddress - /** Currently selected recipient tab **/ readonly property alias selectedRecipientType: d.selectedRecipientType /** Selected recipient address. It is input and output property **/ property alias selectedRecipientAddress: recipientInputLoader.selectedRecipientAddress + /** Maximum number of tab elements from all tabs in tab bar **/ + property int highestTabElementCount: recipientsModel.ModelCount.count + /** Can selector be interacted **/ property bool interactive: true + /** Visual height of the component. It might differ from actual height. + The purpose is to be able to show non-interactive list with constant height for all tabs. + This will not affect Flickable parents. **/ + readonly property int visualHeight: { + // Adjust to filtered results height + if (d.searchInProgress || !!selectedRecipientAddress) + return implicitHeight + const walletViewHeight = Math.max(walletView.contentHeight, emptyListText.height) + const count = Math.max(3, highestTabElementCount) + return implicitHeight + (count * walletView.delegateHeight - walletViewHeight) + } + /** Request ens address to be resolved **/ signal resolveENS(string ensName, string uuid) @@ -51,7 +63,7 @@ Rectangle { recipientInputLoader.ensNameResolved(resolvedPubKey, resolvedAddress, uuid) } - implicitHeight: childrenRect.height + (emptyListText.visible ? Theme.bigPadding : Theme.halfPadding / 2) + implicitHeight: childrenRect.height + (!!selectedRecipientAddress ? 0 : Theme.halfPadding/2) color: Theme.palette.indirectColor1 radius: 8 @@ -63,6 +75,7 @@ Rectangle { QtObject { id: d + readonly property bool allTabsAreEmpty: root.highestTabElementCount === 0 readonly property bool searchInProgress: !!root.searchPattern && root.recipientsFilterModel.ModelCount.count > 0 property int highlightedIndex: 0 property int selectedRecipientType: Constants.RecipientAddressObjectType.RecentsAddress @@ -178,8 +191,9 @@ Rectangle { id: emptyListText Layout.alignment: Qt.AlignTop | Qt.AlignHCenter Layout.fillWidth: true - Layout.topMargin: Theme.bigPadding + Layout.preferredHeight: walletView.delegateHeight horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter color: Theme.palette.baseColor1 text: { switch(root.selectedRecipientType) { @@ -197,21 +211,26 @@ Rectangle { visible: !root.selectedRecipientAddress && !d.searchInProgress && root.recipientsModel.ModelCount.count === 0 && !!text } - Repeater { - id: walletList + ListView { + id: walletView Layout.alignment: Qt.AlignTop Layout.topMargin: Theme.halfPadding / 2 Layout.fillWidth: true - + Layout.leftMargin: Theme.halfPadding / 2 + Layout.rightMargin: Theme.halfPadding / 2 + Layout.preferredHeight: childrenRect.height + spacing: 0 + bottomMargin: Theme.smallPadding + interactive: false model: root.selectedRecipientAddress ? null : (d.searchInProgress ? root.recipientsFilterModel : root.recipientsModel) + readonly property int delegateHeight: 64 + delegate: RecipientViewDelegate { required property var model - Layout.fillWidth: true - Layout.preferredHeight: 64 - Layout.leftMargin: Theme.halfPadding / 2 - Layout.rightMargin: Theme.halfPadding / 2 + width: walletView.width + height: walletView.delegateHeight name: model.name ?? "" address: model.address diff --git a/ui/app/AppLayouts/Wallet/panels/StickySendModalHeader.qml b/ui/app/AppLayouts/Wallet/panels/StickySendModalHeader.qml index 75379fd7dd6..fc119884281 100644 --- a/ui/app/AppLayouts/Wallet/panels/StickySendModalHeader.qml +++ b/ui/app/AppLayouts/Wallet/panels/StickySendModalHeader.qml @@ -104,6 +104,8 @@ Control { Rectangle { anchors.fill: parent + anchors.leftMargin: radius + anchors.rightMargin: radius color: foregroundRect.color visible: !!root.blurSource radius: 8 @@ -116,39 +118,45 @@ Control { ShaderEffectSource { sourceItem: root.blurSource anchors.fill: parent - anchors.leftMargin: Theme.xlPadding - anchors.rightMargin: -Theme.xlPadding + anchors.leftMargin: Theme.xlPadding - parent.radius + anchors.rightMargin: -Theme.xlPadding - parent.radius sourceRect: Qt.rect(0, 0, width, height) live: true } } - Rectangle { - id: foregroundRect + Item { anchors.fill: parent - color: root.implicitHeight > d.bottomMargin ? Theme.palette.baseColor3 : Theme.palette.transparent - radius: 8 - opacity: 0.85 + Rectangle { + id: foregroundRect + anchors.fill: parent + color: root.implicitHeight > d.bottomMargin ? Theme.palette.alphaColor(Theme.palette.baseColor3, 0.85) : Theme.palette.transparent + radius: 8 + + // cover for the bottom rounded corners + Rectangle { + width: parent.radius + height: parent.radius + anchors.bottom: parent.bottom + anchors.left: parent.left + color: parent.color + } + // cover for the bottom rounded corners + Rectangle { + width: parent.radius + height: parent.radius + anchors.bottom: parent.bottom + anchors.right: parent.right + color: parent.color + } + } layer.enabled: true layer.effect: DropShadow { horizontalOffset: 0 - verticalOffset: 2 - samples: 37 - color: Theme.palette.dropShadow - } - - // cover for the bottom rounded corners - Rectangle { - width: parent.width - height: parent.radius - anchors.bottom: parent.bottom - color: parent.color - } - - StatusDialogDivider { - anchors.bottom: parent.bottom - width: parent.width + verticalOffset: 5 + samples: 24 + color: Theme.palette.alphaColor(Theme.palette.dropShadow, 0.06) } } } diff --git a/ui/app/AppLayouts/Wallet/popups/simpleSend/SimpleSendModal.qml b/ui/app/AppLayouts/Wallet/popups/simpleSend/SimpleSendModal.qml index 305fadf030f..1ac70f7aba1 100644 --- a/ui/app/AppLayouts/Wallet/popups/simpleSend/SimpleSendModal.qml +++ b/ui/app/AppLayouts/Wallet/popups/simpleSend/SimpleSendModal.qml @@ -108,6 +108,9 @@ StatusDialog { required property var recipientsModel required property var recipientsFilterModel + /** Maximum number of tab elements from all tabs in tab bar **/ + property alias highestTabElementCount: recipientsPanel.highestTabElementCount + /** Input property holds currently selected Fiat currency **/ required property string currentCurrency /** Input function to format currency amount to locale string **/ @@ -346,36 +349,47 @@ StatusDialog { readonly property var lastTabSettings: Settings { property alias lastSelectedTab: sendModalHeader.tokenSelectorTab } + + readonly property Timer enableAnimationTimer: Timer { + interval: 100 + onTriggered: modalHeightBehavior.enabled = true + } } width: 556 height: { - if (!selectedRecipientAddress) - return root.contentItem.Window.height - topMargin - margins - const amountToSendHeight = amountToSend.visible ? amountToSend.height : 0 - let contentHeight = Math.max(sendModalHeader.height + + const maxHeight = root.contentItem.Window.height - topMargin - margins + const minHeight = 430 + + const feesHeight = (amountToSend.visible && !!selectedRecipientAddress) || feesLayout.visible ? feesLayout.height + Theme.xlPadding : 0 + const recipientPanelHeight = recipientLabel.height + + recipientsPanelLayout.spacing + + recipientsPanel.visualHeight + + scrollViewLayout.spacing + + (!selectedRecipientAddress ? Theme.bigPadding : 0) + const amountToSendHeight = (amountToSend.visible ? amountToSend.height + scrollViewLayout.spacing : 0) + + const calculateContentHeight = sendModalHeader.height + amountToSendHeight + - recipientsPanelLayout.height + - feesLayout.height + - scrollViewLayout.spacing*3 + - 28, - scrollView.implicitHeight) + footer.height - - if (!!footer.errorTags && !feesLayout.visible) { - // Utilize empty space when fees are not visible and error is shown - contentHeight -= feesLayout.height - } - return contentHeight + recipientPanelHeight + + feesHeight + + bottomSpacer.height + + scrollViewLayout.spacing * 2 + const contentHeight = Math.max(calculateContentHeight, minHeight) + footer.height + return Math.min(maxHeight, contentHeight) } padding: 0 horizontalPadding: Theme.xlPadding topMargin: margins + accountSelector.height + Theme.padding Behavior on height { - enabled: !!root.selectedRecipientAddress - NumberAnimation { duration: 100; easing: Easing.OutCurve } + id: modalHeightBehavior + enabled: false + NumberAnimation { duration: 1000; easing.type: Easing.OutExpo } } + onOpened: d.enableAnimationTimer.start() + background: StatusDialogBackground { color: Theme.palette.baseColor3 } @@ -578,6 +592,7 @@ StatusDialog { spacing: Theme.halfPadding StatusBaseText { + id: recipientLabel elide: Text.ElideRight text: qsTr("To") Layout.alignment: Qt.AlignTop @@ -585,7 +600,6 @@ StatusDialog { Item { Layout.alignment: Qt.AlignTop Layout.fillWidth: true - Layout.bottomMargin: feesLayout.visible ? 0 : Theme.xlPadding implicitHeight: recipientsPanel.height Rectangle { @@ -595,7 +609,7 @@ StatusDialog { right: recipientsPanel.right } // Imitate recipient background and overflow the rectangle under footer - height: recipientsPanel.emptyListVisible ? sendModalcontentItem.height : 0 + height: recipientsPanel.visualHeight color: recipientsPanel.color radius: recipientsPanel.radius } @@ -628,7 +642,6 @@ StatusDialog { objectName: "feesLayout" Layout.fillWidth: true - Layout.bottomMargin: Theme.xlPadding spacing: Theme.halfPadding @@ -648,6 +661,12 @@ StatusDialog { } visible: root.allValuesFilledCorrectly } + + Item { + id: bottomSpacer + Layout.fillWidth: true + Layout.preferredHeight: (scrollView.contentHeight < scrollView.height + Theme.padding) ? 0 : Theme.bigPadding + } } } } diff --git a/ui/app/AppLayouts/Wallet/views/SendModalFooter.qml b/ui/app/AppLayouts/Wallet/views/SendModalFooter.qml index e6db159097b..04c4ed6b4de 100644 --- a/ui/app/AppLayouts/Wallet/views/SendModalFooter.qml +++ b/ui/app/AppLayouts/Wallet/views/SendModalFooter.qml @@ -43,6 +43,8 @@ StatusDialogFooter { background: Item { Rectangle { anchors.fill: parent + anchors.leftMargin: radius + anchors.rightMargin: radius color: root.color visible: !!root.blurSource radius: 8 @@ -55,33 +57,44 @@ StatusDialogFooter { ShaderEffectSource { sourceItem: root.blurSource anchors.fill: parent - anchors.leftMargin: Theme.xlPadding - anchors.rightMargin: -Theme.xlPadding + anchors.leftMargin: Theme.xlPadding - parent.radius + anchors.rightMargin: -Theme.xlPadding - parent.radius sourceRect: root.blurSourceRect live: true } } - Rectangle { + Item { anchors.fill: parent - color: root.color - radius: 8 - opacity: !!root.blurSource ? 0.85 : 1.0 + Rectangle { + anchors.fill: parent + color: !!root.blurSource ? Theme.palette.alphaColor(root.color, 0.85) : root.color + radius: 8 + + // cover for the bottom rounded corners + Rectangle { + width: parent.radius + height: parent.radius + anchors.top: parent.top + anchors.left: parent.left + color: parent.color + } + // cover for the bottom rounded corners + Rectangle { + width: parent.radius + height: parent.radius + anchors.top: parent.top + anchors.right: parent.right + color: parent.color + } + } layer.enabled: root.dropShadowEnabled layer.effect: DropShadow { horizontalOffset: 0 - verticalOffset: -2 - samples: 37 - color: Theme.palette.dropShadow - } - - // cover for the top rounded corners - Rectangle { - width: parent.width - height: parent.radius - anchors.top: parent.top - color: parent.color + verticalOffset: -3 + samples: 24 + color: Theme.palette.alphaColor(Theme.palette.dropShadow, 0.06) } StatusDialogDivider { @@ -156,6 +169,7 @@ StatusDialogFooter { objectName: "transactionModalFooterButton" Layout.rightMargin: Theme.padding + Layout.maximumHeight: implicitHeight disabledColor: Theme.palette.directColor8 enabled: !!root.estimatedTime && diff --git a/ui/app/mainui/SendModalHandler.qml b/ui/app/mainui/SendModalHandler.qml index 2ec42b8d0eb..d9c5c4787bc 100644 --- a/ui/app/mainui/SendModalHandler.qml +++ b/ui/app/mainui/SendModalHandler.qml @@ -432,6 +432,7 @@ QtObject { recipientsModel: handler.recipientViewAdaptor.recipientsModel recipientsFilterModel: handler.recipientViewAdaptor.recipientsFilterModel + highestTabElementCount: handler.recipientViewAdaptor.highestTabElementCount currentCurrency: root.currentCurrency fnFormatCurrencyAmount: root.fnFormatCurrencyAmount fnResolveENS: root.fnResolveENS