From 9e7dad7344542284cff713e414ed762f155c02be Mon Sep 17 00:00:00 2001 From: Khushboo Mehta Date: Fri, 8 Nov 2024 12:22:13 +0100 Subject: [PATCH] feat(@desktop/wallet): Adapt Token Selector fixes #16702 --- storybook/pages/SearchableAssetsPanelPage.qml | 11 ++++ .../pages/TokenSelectorAssetDelegatePage.qml | 6 +++ .../TokenSelectorCollectibleDelegatePage.qml | 8 +++ storybook/pages/TokenSelectorPage.qml | 18 ++++++- storybook/pages/TokenSelectorPanelPage.qml | 18 ++++++- .../tests/tst_SearchableAssetsPanel.qml | 6 +++ .../tests/tst_TokenSelectorAssetDelegate.qml | 25 ++++++++++ .../tests/tst_TokenSelectorButton.qml | 7 +++ .../Wallet/controls/TokenSelectorButton.qml | 4 +- .../Wallet/panels/SearchableAssetsPanel.qml | 23 ++++++++- .../panels/SearchableCollectiblesPanel.qml | 50 ++++++++++++++++++- .../Wallet/stores/CollectiblesStore.qml | 11 +++- ui/app/AppLayouts/Wallet/stores/RootStore.qml | 4 +- .../views/TokenSelectorAssetDelegate.qml | 9 ++-- .../TokenSelectorCollectibleDelegate.qml | 21 ++++++-- 15 files changed, 203 insertions(+), 18 deletions(-) diff --git a/storybook/pages/SearchableAssetsPanelPage.qml b/storybook/pages/SearchableAssetsPanelPage.qml index 679a7a37f1..322a709f2d 100644 --- a/storybook/pages/SearchableAssetsPanelPage.qml +++ b/storybook/pages/SearchableAssetsPanelPage.qml @@ -75,6 +75,17 @@ Pane { iconSource: Constants.tokenIcon("ZRX"), balances: [], + sectionName: "Popular assets" + }, + { + tokensKey: "abc_key", + communityId: "", + name: "0x", + currencyBalanceAsString: "41,22 USD", + symbol: "ABC", + iconSource: Constants.tokenIcon("ABC"), + balances: [], + sectionName: "Popular assets" } ] diff --git a/storybook/pages/TokenSelectorAssetDelegatePage.qml b/storybook/pages/TokenSelectorAssetDelegatePage.qml index 375afafb48..49b62b45fd 100644 --- a/storybook/pages/TokenSelectorAssetDelegatePage.qml +++ b/storybook/pages/TokenSelectorAssetDelegatePage.qml @@ -46,6 +46,7 @@ SplitView { symbol: "ETH" currencyBalanceAsString: "14,456.42 USD" iconSource: Constants.tokenIcon(symbol) + isFirstSearchEntry: ctrlIsFirstSearchEntry.checked balancesModel: ListModel { readonly property var data: [ @@ -85,6 +86,11 @@ SplitView { text: "Highlighted" checked: false } + Switch { + id: ctrlIsFirstSearchEntry + text: "isFirstSearchEntry" + checked: false + } Item { Layout.fillHeight: true } } diff --git a/storybook/pages/TokenSelectorCollectibleDelegatePage.qml b/storybook/pages/TokenSelectorCollectibleDelegatePage.qml index dd856aac82..514740d5cc 100644 --- a/storybook/pages/TokenSelectorCollectibleDelegatePage.qml +++ b/storybook/pages/TokenSelectorCollectibleDelegatePage.qml @@ -34,11 +34,13 @@ SplitView { name: nameTextField.text balance: balanceSpinBox.value ? balanceSpinBox.value : "" image: Constants.tokenIcon("ETH") + networkIcon: "network/Network=Ethereum" goDeeperIconVisible: goDeeperSwitch.checked interactive: interactiveSwitch.checked highlighted: highlightedSwitch.checked + isFirstSearchEntry: ctrlIsFirstSearchEntry.checked } } @@ -95,6 +97,12 @@ SplitView { checked: false } + Switch { + id: ctrlIsFirstSearchEntry + text: "isFirstSearchEntry" + checked: false + } + Item { Layout.fillHeight: true } } } diff --git a/storybook/pages/TokenSelectorPage.qml b/storybook/pages/TokenSelectorPage.qml index 7a90d0c348..a73daa9fb8 100644 --- a/storybook/pages/TokenSelectorPage.qml +++ b/storybook/pages/TokenSelectorPage.qml @@ -93,6 +93,8 @@ Pane { name: "My token", balance: 1, icon: Constants.tokenIcon("CFI"), + chainId: 11155111, + iconUrl: "network/Network=Ethereum" } ] }, @@ -106,18 +108,24 @@ Pane { name: "Furbeard", balance: 1, icon: Constants.tokenIcon("FUEL"), + chainId: 11155111, + iconUrl: "network/Network=Ethereum" }, { key: "collection_1_key_2", name: "Magicat", balance: 1, icon: Constants.tokenIcon("ENJ"), + chainId: 11155111, + iconUrl: "network/Network=Ethereum" }, { key: "collection_1_key_3", name: "Happy Meow", balance: 1, icon: Constants.tokenIcon("FUN"), + chainId: 11155111, + iconUrl: "network/Network=Ethereum" } ] }, @@ -130,19 +138,25 @@ Pane { key: "collection_2_key_1", name: "Unicorn 1", balance: 12, - icon: Constants.tokenIcon("CVC") + icon: Constants.tokenIcon("CVC"), + chainId: 11155111, + iconUrl: "network/Network=Ethereum" }, { key: "collection_2_key_2", name: "Unicorn 2", balance: 1, - icon: Constants.tokenIcon("CVC") + icon: Constants.tokenIcon("CVC"), + chainId: 11155111, + iconUrl: "network/Network=Ethereum" } ] }, { groupName: "Unicorn", icon: Constants.tokenIcon("ELF"), + chainId: 11155111, + iconUrl: "network/Network=Ethereum", type: "other", subitems: [ { diff --git a/storybook/pages/TokenSelectorPanelPage.qml b/storybook/pages/TokenSelectorPanelPage.qml index d4b0f9fa8b..01f7849e1d 100644 --- a/storybook/pages/TokenSelectorPanelPage.qml +++ b/storybook/pages/TokenSelectorPanelPage.qml @@ -92,6 +92,8 @@ Pane { name: "My token", balance: 1, icon: Constants.tokenIcon("CFI"), + chainId: 11155111, + iconUrl: "network/Network=Ethereum" } ] }, @@ -105,18 +107,24 @@ Pane { name: "Furbeard", balance: 1, icon: Constants.tokenIcon("FUEL"), + chainId: 11155111, + iconUrl: "network/Network=Ethereum" }, { key: "collection_1_key_2", name: "Magicat", balance: 1, icon: Constants.tokenIcon("ENJ"), + chainId: 11155111, + iconUrl: "network/Network=Ethereum" }, { key: "collection_1_key_3", name: "Happy Meow", balance: 1, icon: Constants.tokenIcon("FUN"), + chainId: 11155111, + iconUrl: "network/Network=Ethereum" } ] }, @@ -129,13 +137,17 @@ Pane { key: "collection_2_key_1", name: "Unicorn 1", balance: 12, - icon: Constants.tokenIcon("CVC") + icon: Constants.tokenIcon("CVC"), + chainId: 11155111, + iconUrl: "network/Network=Ethereum" }, { key: "collection_2_key_2", name: "Unicorn 2", balance: 1, - icon: Constants.tokenIcon("CVC") + icon: Constants.tokenIcon("CVC"), + chainId: 11155111, + iconUrl: "network/Network=Ethereum" } ] }, @@ -143,6 +155,8 @@ Pane { groupName: "Unicorn", icon: Constants.tokenIcon("ELF"), type: "other", + chainId: 11155111, + iconUrl: "network/Network=Ethereum", subitems: [ { key: "collection_3_key_1", diff --git a/storybook/qmlTests/tests/tst_SearchableAssetsPanel.qml b/storybook/qmlTests/tests/tst_SearchableAssetsPanel.qml index 0019b9fa89..0f7c8ae487 100644 --- a/storybook/qmlTests/tests/tst_SearchableAssetsPanel.qml +++ b/storybook/qmlTests/tests/tst_SearchableAssetsPanel.qml @@ -3,6 +3,8 @@ import QtTest 1.15 import AppLayouts.Wallet.panels 1.0 +import StatusQ.Core.Theme 0.1 + import Storybook 1.0 import utils 1.0 @@ -157,6 +159,8 @@ Item { const delegate1 = listView.itemAtIndex(0) verify(delegate1) compare(delegate1.name, "Status Test Token") + verify(delegate1.isFirstSearchEntry) + compare(delegate1.background.color, Theme.palette.baseColor2) } { searchBox.text = "zrx" @@ -166,6 +170,8 @@ Item { const delegate1 = listView.itemAtIndex(0) verify(delegate1) compare(delegate1.name, "0x") + verify(delegate1.isFirstSearchEntry) + compare(delegate1.background.color, Theme.palette.baseColor2) } { control.clearSearch() diff --git a/storybook/qmlTests/tests/tst_TokenSelectorAssetDelegate.qml b/storybook/qmlTests/tests/tst_TokenSelectorAssetDelegate.qml index 9ac42c8d6b..05b0d4645a 100644 --- a/storybook/qmlTests/tests/tst_TokenSelectorAssetDelegate.qml +++ b/storybook/qmlTests/tests/tst_TokenSelectorAssetDelegate.qml @@ -5,6 +5,8 @@ import AppLayouts.Wallet.views 1.0 import Storybook 1.0 +import StatusQ.Core.Theme 0.1 + Item { id: root @@ -21,6 +23,7 @@ Item { symbol: "ETH" currencyBalanceAsString: "42.02 USD" iconSource: "" + isFirstSearchEntry: false width: 250 readonly property SignalSpy clickSpy: SignalSpy { @@ -129,5 +132,27 @@ Item { verify(subBalanceText1.visible) verify(subBalanceText2.visible) } + + + function test_hovered_highlighted_states() { + const control = createTemporaryObject(delegateCmp, root, + { balancesModel }) + + control.highlighted = true + compare(control.background.color, Theme.palette.statusListItem.highlightColor) + + mouseMove(control, control.width/2, control.height/2) + compare(control.hovered, true) + compare(control.background.color, Theme.palette.baseColor2) + + control.highlighted = false + mouseMove(control, control.width/2, control.height/2) + compare(control.hovered, true) + compare(control.background.color, Theme.palette.baseColor2) + + // test isFirstSearchEntry behaviour + control.isFirstSearchEntry = true + compare(control.background.color, Theme.palette.baseColor2) + } } } diff --git a/storybook/qmlTests/tests/tst_TokenSelectorButton.qml b/storybook/qmlTests/tests/tst_TokenSelectorButton.qml index 58d937ecdf..d142f6a04b 100644 --- a/storybook/qmlTests/tests/tst_TokenSelectorButton.qml +++ b/storybook/qmlTests/tests/tst_TokenSelectorButton.qml @@ -5,6 +5,8 @@ import AppLayouts.Wallet.controls 1.0 import Storybook 1.0 +import StatusQ.Components 0.1 + Item { id: root @@ -39,6 +41,11 @@ Item { verify(!TestUtils.findTextItem(button, button.text)) verify(TestUtils.findTextItem(button, "ETH")) verify(findChild(button, "selectedContent")) + + const icon = TestUtils.findByType(button, StatusRoundedImage) + verify(icon) + compare(icon.width, 24) + compare(icon.height, 24) } } } diff --git a/ui/app/AppLayouts/Wallet/controls/TokenSelectorButton.qml b/ui/app/AppLayouts/Wallet/controls/TokenSelectorButton.qml index f5be649407..3f26c28ffa 100644 --- a/ui/app/AppLayouts/Wallet/controls/TokenSelectorButton.qml +++ b/ui/app/AppLayouts/Wallet/controls/TokenSelectorButton.qml @@ -74,8 +74,8 @@ Control { id: tokenSelectorIcon objectName: "tokenSelectorIcon" - Layout.preferredWidth: 21 - Layout.preferredHeight: 21 + Layout.preferredWidth: 24 + Layout.preferredHeight: 24 image.source: root.icon } diff --git a/ui/app/AppLayouts/Wallet/panels/SearchableAssetsPanel.qml b/ui/app/AppLayouts/Wallet/panels/SearchableAssetsPanel.qml index d783be3483..e2fe125a7c 100644 --- a/ui/app/AppLayouts/Wallet/panels/SearchableAssetsPanel.qml +++ b/ui/app/AppLayouts/Wallet/panels/SearchableAssetsPanel.qml @@ -39,6 +39,14 @@ Control { searchBox.text = "" } + QtObject { + id: d + readonly property int delegateHeight: 60 + // should show 5.5 items as per design + readonly property int maxListViewHeight: delegateHeight*5 + delegateHeight/2 + readonly property bool validSearchResultExists: !!searchBox.text && sfpm.rowCount() > 0 + } + SortFilterProxyModel { id: sfpm @@ -64,6 +72,8 @@ Control { Layout.fillWidth: true placeholderText: qsTr("Search assets") + + Keys.forwardTo: [listView] } StatusDialogDivider { @@ -80,7 +90,11 @@ Control { Layout.fillWidth: true Layout.fillHeight: true - Layout.preferredHeight: contentHeight + Layout.preferredHeight: d.maxListViewHeight + Layout.leftMargin: 4 + Layout.rightMargin: 4 + + spacing: 4 model: sfpm section.property: "sectionName" @@ -95,10 +109,12 @@ Control { required property int index width: ListView.view.width + height: d.delegateHeight highlighted: model.tokensKey === root.highlightedKey enabled: model.tokensKey !== root.nonInteractiveKey balancesListInteractive: !ListView.view.moving + isFirstSearchEntry: d.validSearchResultExists && index === 0 name: model.name symbol: model.symbol @@ -108,6 +124,11 @@ Control { onClicked: root.selected(model.tokensKey) } + + Keys.onReturnPressed: { + if(d.validSearchResultExists) + listView.itemAtIndex(0).clicked() + } } } } diff --git a/ui/app/AppLayouts/Wallet/panels/SearchableCollectiblesPanel.qml b/ui/app/AppLayouts/Wallet/panels/SearchableCollectiblesPanel.qml index 70532e02eb..d6a09f29c8 100644 --- a/ui/app/AppLayouts/Wallet/panels/SearchableCollectiblesPanel.qml +++ b/ui/app/AppLayouts/Wallet/panels/SearchableCollectiblesPanel.qml @@ -42,6 +42,13 @@ Control { readonly property alias currentItem: collectiblesStackView.currentItem + QtObject { + id: d + readonly property int delegateHeight: 60 + // should show 5.5 items as per design + readonly property int maxListViewHeight: delegateHeight*5 + delegateHeight/2 + } + SortFilterProxyModel { id: sfpm @@ -59,6 +66,13 @@ Control { initialItem: ColumnLayout { spacing: 0 + StatusBaseText { + Layout.alignment: Qt.AlignHCenter + text: qsTr("Your collectibles will appear here") + color: Theme.palette.baseColor1 + visible: !collectiblesListView.count + } + TokenSearchBox { id: collectiblesSearchBox @@ -66,6 +80,10 @@ Control { Layout.fillWidth: true placeholderText: qsTr("Search collectibles") + + visible: collectiblesListView.count + + Keys.forwardTo: [collectiblesListView] } StatusDialogDivider { @@ -76,9 +94,15 @@ Control { StatusListView { id: collectiblesListView + readonly property bool validSearchResultExists: !!collectiblesSearchBox.text && sfpm.rowCount() > 0 + Layout.fillWidth: true Layout.fillHeight: true - Layout.preferredHeight: contentHeight + Layout.preferredHeight: d.maxListViewHeight + Layout.leftMargin: 4 + Layout.rightMargin: 4 + + spacing: 4 clip: true model: sfpm @@ -95,15 +119,19 @@ Control { readonly property bool showCount: subitemsCount > 1 || isCommunity + height: d.delegateHeight + name: model.groupName balance: showCount ? subitemsCount : "" image: model.icon goDeeperIconVisible: subitemsCount > 1 || isCommunity + networkIcon: model.iconUrl highlighted: subitemsCount === 1 && !isCommunity ? ModelUtils.get(model.subitems, 0, "key") === root.highlightedKey : false + isFirstSearchEntry: collectiblesListView.validSearchResultExists && model.index === 0 onClicked: { if (subitemsCount === 1 && !isCommunity) { @@ -133,6 +161,11 @@ Control { ? qsTr("Community minted") : qsTr("Other") } + + Keys.onReturnPressed: { + if(validSearchResultExists) + collectiblesListView.itemAtIndex(0).clicked() + } } } } @@ -180,6 +213,8 @@ Control { Layout.fillWidth: true placeholderText: qsTr("Search collectibles") + + Keys.forwardTo: [sublist] } StatusDialogDivider { @@ -190,9 +225,11 @@ Control { StatusListView { id: sublist + readonly property bool validSearchResultExists: !!collectiblesSublistSearchBox.text && sublistSfpm.rowCount() > 0 + Layout.fillWidth: true Layout.fillHeight: true - Layout.preferredHeight: contentHeight + Layout.preferredHeight: d.maxListViewHeight model: sublistSfpm @@ -201,11 +238,15 @@ Control { delegate: TokenSelectorCollectibleDelegate { required property var model + height: d.delegateHeight + name: model.name balance: model.balance > 1 ? model.balance : "" image: model.icon goDeeperIconVisible: false + networkIcon: model.iconUrl highlighted: model.key === root.highlightedKey + isFirstSearchEntry: sublist.validSearchResultExists && model.index === 0 onClicked: { if (isCommunity) @@ -214,6 +255,11 @@ Control { root.collectibleSelected(model.key) } } + + Keys.onReturnPressed: { + if(validSearchResultExists) + sublist.itemAtIndex(0).clicked() + } } // Detection if the related model entry has been removed. diff --git a/ui/app/AppLayouts/Wallet/stores/CollectiblesStore.qml b/ui/app/AppLayouts/Wallet/stores/CollectiblesStore.qml index c2cc4ad12d..71c67fc52f 100644 --- a/ui/app/AppLayouts/Wallet/stores/CollectiblesStore.qml +++ b/ui/app/AppLayouts/Wallet/stores/CollectiblesStore.qml @@ -11,6 +11,8 @@ import StatusQ.Core.Utils 0.1 as SQUtils QtObject { id: root + property var filteredFlatModel + /* PRIVATE: Modules used to get data from backend */ readonly property var _allCollectiblesModule: !!walletSectionAllCollectibles ? walletSectionAllCollectibles : null @@ -86,11 +88,18 @@ QtObject { ] } + /* TODO: move all transformations to a dedicated adaptors */ + readonly property LeftJoinModel jointCollectiblesByNwChainId: LeftJoinModel { + leftModel: allCollectiblesModel + rightModel: filteredFlatModel + joinRole: "chainId" + } + /* TODO: move all transformations to a dedicated adaptors */ readonly property LeftJoinModel jointCollectiblesBySymbolModel: LeftJoinModel { objectName: "jointCollectiblesBySymbolModel" - leftModel: allCollectiblesModel + leftModel: jointCollectiblesByNwChainId rightModel: _renamedCommunitiesModel joinRole: "communityId" } diff --git a/ui/app/AppLayouts/Wallet/stores/RootStore.qml b/ui/app/AppLayouts/Wallet/stores/RootStore.qml index c28fb9eb22..caefa0f3c0 100644 --- a/ui/app/AppLayouts/Wallet/stores/RootStore.qml +++ b/ui/app/AppLayouts/Wallet/stores/RootStore.qml @@ -51,7 +51,9 @@ QtObject { property var accountSensitiveSettings: localAccountSensitiveSettings property bool hideSignPhraseModal: accountSensitiveSettings.hideSignPhraseModal - property CollectiblesStore collectiblesStore: CollectiblesStore {} + property CollectiblesStore collectiblesStore: CollectiblesStore { + filteredFlatModel: root.filteredFlatModel + } readonly property bool areTestNetworksEnabled: networksModule.areTestNetworksEnabled diff --git a/ui/app/AppLayouts/Wallet/views/TokenSelectorAssetDelegate.qml b/ui/app/AppLayouts/Wallet/views/TokenSelectorAssetDelegate.qml index 3027584316..bb9dab56ab 100644 --- a/ui/app/AppLayouts/Wallet/views/TokenSelectorAssetDelegate.qml +++ b/ui/app/AppLayouts/Wallet/views/TokenSelectorAssetDelegate.qml @@ -17,6 +17,7 @@ ItemDelegate { required property string symbol required property string currencyBalanceAsString required property string iconSource + required property bool isFirstSearchEntry // expected structure: [iconUrl: string, balanceAsString: string] property alias balancesModel: balancesListView.model @@ -36,9 +37,11 @@ ItemDelegate { background: Rectangle { radius: Theme.radius - color: root.hovered || root.highlighted - ? Theme.palette.statusListItem.highlightColor - : "transparent" + color: root.hovered || root.isFirstSearchEntry + ? Theme.palette.baseColor2 + : root.highlighted + ? Theme.palette.statusListItem.highlightColor + : "transparent" HoverHandler { cursorShape: root.enabled ? Qt.PointingHandCursor : undefined diff --git a/ui/app/AppLayouts/Wallet/views/TokenSelectorCollectibleDelegate.qml b/ui/app/AppLayouts/Wallet/views/TokenSelectorCollectibleDelegate.qml index a7508d58a9..c10b1bd767 100644 --- a/ui/app/AppLayouts/Wallet/views/TokenSelectorCollectibleDelegate.qml +++ b/ui/app/AppLayouts/Wallet/views/TokenSelectorCollectibleDelegate.qml @@ -15,6 +15,8 @@ ItemDelegate { required property string name required property string balance required property url image + required property string networkIcon + required property bool isFirstSearchEntry property bool goDeeperIconVisible: true property bool interactive: true @@ -36,9 +38,11 @@ ItemDelegate { background: Rectangle { radius: Theme.radius - color: (root.interactive && root.hovered) || root.highlighted - ? Theme.palette.statusListItem.highlightColor - : "transparent" + color: (root.interactive && (root.hovered || isFirstSearchEntry )) + ? Theme.palette.baseColor2 + : root.highlighted + ? Theme.palette.statusListItem.highlightColor + : "transparent" HoverHandler { cursorShape: root.interactive ? Qt.PointingHandCursor : undefined @@ -59,7 +63,7 @@ ItemDelegate { Layout.fillWidth: true spacing: 0 - // name, symbol, total balance + // name, symbol, total balance, network icon RowLayout { Layout.fillWidth: true spacing: root.spacing @@ -86,6 +90,15 @@ ItemDelegate { elide: Text.ElideRight } + StatusRoundedImage { + Layout.alignment: Qt.AlignVCenter + Layout.preferredWidth: 20 + Layout.preferredHeight: 20 + + image.source: Theme.svg("tiny/%1".arg(root.networkIcon)) + visible:(root.hovered || isFirstSearchEntry) && !root.goDeeperIconVisible + } + StatusIcon { Layout.alignment: Qt.AlignVCenter