feat(@desktop/wallet): Adapt Token Selector

fixes #16702
This commit is contained in:
Khushboo Mehta 2024-11-08 12:22:13 +01:00
parent 9eaf4cc4ea
commit 9e7dad7344
15 changed files with 203 additions and 18 deletions

View File

@ -75,6 +75,17 @@ Pane {
iconSource: Constants.tokenIcon("ZRX"), iconSource: Constants.tokenIcon("ZRX"),
balances: [], balances: [],
sectionName: "Popular assets"
},
{
tokensKey: "abc_key",
communityId: "",
name: "0x",
currencyBalanceAsString: "41,22 USD",
symbol: "ABC",
iconSource: Constants.tokenIcon("ABC"),
balances: [],
sectionName: "Popular assets" sectionName: "Popular assets"
} }
] ]

View File

@ -46,6 +46,7 @@ SplitView {
symbol: "ETH" symbol: "ETH"
currencyBalanceAsString: "14,456.42 USD" currencyBalanceAsString: "14,456.42 USD"
iconSource: Constants.tokenIcon(symbol) iconSource: Constants.tokenIcon(symbol)
isFirstSearchEntry: ctrlIsFirstSearchEntry.checked
balancesModel: ListModel { balancesModel: ListModel {
readonly property var data: [ readonly property var data: [
@ -85,6 +86,11 @@ SplitView {
text: "Highlighted" text: "Highlighted"
checked: false checked: false
} }
Switch {
id: ctrlIsFirstSearchEntry
text: "isFirstSearchEntry"
checked: false
}
Item { Layout.fillHeight: true } Item { Layout.fillHeight: true }
} }

View File

@ -34,11 +34,13 @@ SplitView {
name: nameTextField.text name: nameTextField.text
balance: balanceSpinBox.value ? balanceSpinBox.value : "" balance: balanceSpinBox.value ? balanceSpinBox.value : ""
image: Constants.tokenIcon("ETH") image: Constants.tokenIcon("ETH")
networkIcon: "network/Network=Ethereum"
goDeeperIconVisible: goDeeperSwitch.checked goDeeperIconVisible: goDeeperSwitch.checked
interactive: interactiveSwitch.checked interactive: interactiveSwitch.checked
highlighted: highlightedSwitch.checked highlighted: highlightedSwitch.checked
isFirstSearchEntry: ctrlIsFirstSearchEntry.checked
} }
} }
@ -95,6 +97,12 @@ SplitView {
checked: false checked: false
} }
Switch {
id: ctrlIsFirstSearchEntry
text: "isFirstSearchEntry"
checked: false
}
Item { Layout.fillHeight: true } Item { Layout.fillHeight: true }
} }
} }

View File

@ -93,6 +93,8 @@ Pane {
name: "My token", name: "My token",
balance: 1, balance: 1,
icon: Constants.tokenIcon("CFI"), icon: Constants.tokenIcon("CFI"),
chainId: 11155111,
iconUrl: "network/Network=Ethereum"
} }
] ]
}, },
@ -106,18 +108,24 @@ Pane {
name: "Furbeard", name: "Furbeard",
balance: 1, balance: 1,
icon: Constants.tokenIcon("FUEL"), icon: Constants.tokenIcon("FUEL"),
chainId: 11155111,
iconUrl: "network/Network=Ethereum"
}, },
{ {
key: "collection_1_key_2", key: "collection_1_key_2",
name: "Magicat", name: "Magicat",
balance: 1, balance: 1,
icon: Constants.tokenIcon("ENJ"), icon: Constants.tokenIcon("ENJ"),
chainId: 11155111,
iconUrl: "network/Network=Ethereum"
}, },
{ {
key: "collection_1_key_3", key: "collection_1_key_3",
name: "Happy Meow", name: "Happy Meow",
balance: 1, balance: 1,
icon: Constants.tokenIcon("FUN"), icon: Constants.tokenIcon("FUN"),
chainId: 11155111,
iconUrl: "network/Network=Ethereum"
} }
] ]
}, },
@ -130,19 +138,25 @@ Pane {
key: "collection_2_key_1", key: "collection_2_key_1",
name: "Unicorn 1", name: "Unicorn 1",
balance: 12, balance: 12,
icon: Constants.tokenIcon("CVC") icon: Constants.tokenIcon("CVC"),
chainId: 11155111,
iconUrl: "network/Network=Ethereum"
}, },
{ {
key: "collection_2_key_2", key: "collection_2_key_2",
name: "Unicorn 2", name: "Unicorn 2",
balance: 1, balance: 1,
icon: Constants.tokenIcon("CVC") icon: Constants.tokenIcon("CVC"),
chainId: 11155111,
iconUrl: "network/Network=Ethereum"
} }
] ]
}, },
{ {
groupName: "Unicorn", groupName: "Unicorn",
icon: Constants.tokenIcon("ELF"), icon: Constants.tokenIcon("ELF"),
chainId: 11155111,
iconUrl: "network/Network=Ethereum",
type: "other", type: "other",
subitems: [ subitems: [
{ {

View File

@ -92,6 +92,8 @@ Pane {
name: "My token", name: "My token",
balance: 1, balance: 1,
icon: Constants.tokenIcon("CFI"), icon: Constants.tokenIcon("CFI"),
chainId: 11155111,
iconUrl: "network/Network=Ethereum"
} }
] ]
}, },
@ -105,18 +107,24 @@ Pane {
name: "Furbeard", name: "Furbeard",
balance: 1, balance: 1,
icon: Constants.tokenIcon("FUEL"), icon: Constants.tokenIcon("FUEL"),
chainId: 11155111,
iconUrl: "network/Network=Ethereum"
}, },
{ {
key: "collection_1_key_2", key: "collection_1_key_2",
name: "Magicat", name: "Magicat",
balance: 1, balance: 1,
icon: Constants.tokenIcon("ENJ"), icon: Constants.tokenIcon("ENJ"),
chainId: 11155111,
iconUrl: "network/Network=Ethereum"
}, },
{ {
key: "collection_1_key_3", key: "collection_1_key_3",
name: "Happy Meow", name: "Happy Meow",
balance: 1, balance: 1,
icon: Constants.tokenIcon("FUN"), icon: Constants.tokenIcon("FUN"),
chainId: 11155111,
iconUrl: "network/Network=Ethereum"
} }
] ]
}, },
@ -129,13 +137,17 @@ Pane {
key: "collection_2_key_1", key: "collection_2_key_1",
name: "Unicorn 1", name: "Unicorn 1",
balance: 12, balance: 12,
icon: Constants.tokenIcon("CVC") icon: Constants.tokenIcon("CVC"),
chainId: 11155111,
iconUrl: "network/Network=Ethereum"
}, },
{ {
key: "collection_2_key_2", key: "collection_2_key_2",
name: "Unicorn 2", name: "Unicorn 2",
balance: 1, balance: 1,
icon: Constants.tokenIcon("CVC") icon: Constants.tokenIcon("CVC"),
chainId: 11155111,
iconUrl: "network/Network=Ethereum"
} }
] ]
}, },
@ -143,6 +155,8 @@ Pane {
groupName: "Unicorn", groupName: "Unicorn",
icon: Constants.tokenIcon("ELF"), icon: Constants.tokenIcon("ELF"),
type: "other", type: "other",
chainId: 11155111,
iconUrl: "network/Network=Ethereum",
subitems: [ subitems: [
{ {
key: "collection_3_key_1", key: "collection_3_key_1",

View File

@ -3,6 +3,8 @@ import QtTest 1.15
import AppLayouts.Wallet.panels 1.0 import AppLayouts.Wallet.panels 1.0
import StatusQ.Core.Theme 0.1
import Storybook 1.0 import Storybook 1.0
import utils 1.0 import utils 1.0
@ -157,6 +159,8 @@ Item {
const delegate1 = listView.itemAtIndex(0) const delegate1 = listView.itemAtIndex(0)
verify(delegate1) verify(delegate1)
compare(delegate1.name, "Status Test Token") compare(delegate1.name, "Status Test Token")
verify(delegate1.isFirstSearchEntry)
compare(delegate1.background.color, Theme.palette.baseColor2)
} }
{ {
searchBox.text = "zrx" searchBox.text = "zrx"
@ -166,6 +170,8 @@ Item {
const delegate1 = listView.itemAtIndex(0) const delegate1 = listView.itemAtIndex(0)
verify(delegate1) verify(delegate1)
compare(delegate1.name, "0x") compare(delegate1.name, "0x")
verify(delegate1.isFirstSearchEntry)
compare(delegate1.background.color, Theme.palette.baseColor2)
} }
{ {
control.clearSearch() control.clearSearch()

View File

@ -5,6 +5,8 @@ import AppLayouts.Wallet.views 1.0
import Storybook 1.0 import Storybook 1.0
import StatusQ.Core.Theme 0.1
Item { Item {
id: root id: root
@ -21,6 +23,7 @@ Item {
symbol: "ETH" symbol: "ETH"
currencyBalanceAsString: "42.02 USD" currencyBalanceAsString: "42.02 USD"
iconSource: "" iconSource: ""
isFirstSearchEntry: false
width: 250 width: 250
readonly property SignalSpy clickSpy: SignalSpy { readonly property SignalSpy clickSpy: SignalSpy {
@ -129,5 +132,27 @@ Item {
verify(subBalanceText1.visible) verify(subBalanceText1.visible)
verify(subBalanceText2.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)
}
} }
} }

View File

@ -5,6 +5,8 @@ import AppLayouts.Wallet.controls 1.0
import Storybook 1.0 import Storybook 1.0
import StatusQ.Components 0.1
Item { Item {
id: root id: root
@ -39,6 +41,11 @@ Item {
verify(!TestUtils.findTextItem(button, button.text)) verify(!TestUtils.findTextItem(button, button.text))
verify(TestUtils.findTextItem(button, "ETH")) verify(TestUtils.findTextItem(button, "ETH"))
verify(findChild(button, "selectedContent")) verify(findChild(button, "selectedContent"))
const icon = TestUtils.findByType(button, StatusRoundedImage)
verify(icon)
compare(icon.width, 24)
compare(icon.height, 24)
} }
} }
} }

View File

@ -74,8 +74,8 @@ Control {
id: tokenSelectorIcon id: tokenSelectorIcon
objectName: "tokenSelectorIcon" objectName: "tokenSelectorIcon"
Layout.preferredWidth: 21 Layout.preferredWidth: 24
Layout.preferredHeight: 21 Layout.preferredHeight: 24
image.source: root.icon image.source: root.icon
} }

View File

@ -39,6 +39,14 @@ Control {
searchBox.text = "" 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 { SortFilterProxyModel {
id: sfpm id: sfpm
@ -64,6 +72,8 @@ Control {
Layout.fillWidth: true Layout.fillWidth: true
placeholderText: qsTr("Search assets") placeholderText: qsTr("Search assets")
Keys.forwardTo: [listView]
} }
StatusDialogDivider { StatusDialogDivider {
@ -80,7 +90,11 @@ Control {
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
Layout.preferredHeight: contentHeight Layout.preferredHeight: d.maxListViewHeight
Layout.leftMargin: 4
Layout.rightMargin: 4
spacing: 4
model: sfpm model: sfpm
section.property: "sectionName" section.property: "sectionName"
@ -95,10 +109,12 @@ Control {
required property int index required property int index
width: ListView.view.width width: ListView.view.width
height: d.delegateHeight
highlighted: model.tokensKey === root.highlightedKey highlighted: model.tokensKey === root.highlightedKey
enabled: model.tokensKey !== root.nonInteractiveKey enabled: model.tokensKey !== root.nonInteractiveKey
balancesListInteractive: !ListView.view.moving balancesListInteractive: !ListView.view.moving
isFirstSearchEntry: d.validSearchResultExists && index === 0
name: model.name name: model.name
symbol: model.symbol symbol: model.symbol
@ -108,6 +124,11 @@ Control {
onClicked: root.selected(model.tokensKey) onClicked: root.selected(model.tokensKey)
} }
Keys.onReturnPressed: {
if(d.validSearchResultExists)
listView.itemAtIndex(0).clicked()
}
} }
} }
} }

View File

@ -42,6 +42,13 @@ Control {
readonly property alias currentItem: collectiblesStackView.currentItem 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 { SortFilterProxyModel {
id: sfpm id: sfpm
@ -59,6 +66,13 @@ Control {
initialItem: ColumnLayout { initialItem: ColumnLayout {
spacing: 0 spacing: 0
StatusBaseText {
Layout.alignment: Qt.AlignHCenter
text: qsTr("Your collectibles will appear here")
color: Theme.palette.baseColor1
visible: !collectiblesListView.count
}
TokenSearchBox { TokenSearchBox {
id: collectiblesSearchBox id: collectiblesSearchBox
@ -66,6 +80,10 @@ Control {
Layout.fillWidth: true Layout.fillWidth: true
placeholderText: qsTr("Search collectibles") placeholderText: qsTr("Search collectibles")
visible: collectiblesListView.count
Keys.forwardTo: [collectiblesListView]
} }
StatusDialogDivider { StatusDialogDivider {
@ -76,9 +94,15 @@ Control {
StatusListView { StatusListView {
id: collectiblesListView id: collectiblesListView
readonly property bool validSearchResultExists: !!collectiblesSearchBox.text && sfpm.rowCount() > 0
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
Layout.preferredHeight: contentHeight Layout.preferredHeight: d.maxListViewHeight
Layout.leftMargin: 4
Layout.rightMargin: 4
spacing: 4
clip: true clip: true
model: sfpm model: sfpm
@ -95,15 +119,19 @@ Control {
readonly property bool showCount: readonly property bool showCount:
subitemsCount > 1 || isCommunity subitemsCount > 1 || isCommunity
height: d.delegateHeight
name: model.groupName name: model.groupName
balance: showCount ? subitemsCount : "" balance: showCount ? subitemsCount : ""
image: model.icon image: model.icon
goDeeperIconVisible: subitemsCount > 1 goDeeperIconVisible: subitemsCount > 1
|| isCommunity || isCommunity
networkIcon: model.iconUrl
highlighted: subitemsCount === 1 && !isCommunity highlighted: subitemsCount === 1 && !isCommunity
? ModelUtils.get(model.subitems, 0, "key") ? ModelUtils.get(model.subitems, 0, "key")
=== root.highlightedKey === root.highlightedKey
: false : false
isFirstSearchEntry: collectiblesListView.validSearchResultExists && model.index === 0
onClicked: { onClicked: {
if (subitemsCount === 1 && !isCommunity) { if (subitemsCount === 1 && !isCommunity) {
@ -133,6 +161,11 @@ Control {
? qsTr("Community minted") ? qsTr("Community minted")
: qsTr("Other") : qsTr("Other")
} }
Keys.onReturnPressed: {
if(validSearchResultExists)
collectiblesListView.itemAtIndex(0).clicked()
}
} }
} }
} }
@ -180,6 +213,8 @@ Control {
Layout.fillWidth: true Layout.fillWidth: true
placeholderText: qsTr("Search collectibles") placeholderText: qsTr("Search collectibles")
Keys.forwardTo: [sublist]
} }
StatusDialogDivider { StatusDialogDivider {
@ -190,9 +225,11 @@ Control {
StatusListView { StatusListView {
id: sublist id: sublist
readonly property bool validSearchResultExists: !!collectiblesSublistSearchBox.text && sublistSfpm.rowCount() > 0
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
Layout.preferredHeight: contentHeight Layout.preferredHeight: d.maxListViewHeight
model: sublistSfpm model: sublistSfpm
@ -201,11 +238,15 @@ Control {
delegate: TokenSelectorCollectibleDelegate { delegate: TokenSelectorCollectibleDelegate {
required property var model required property var model
height: d.delegateHeight
name: model.name name: model.name
balance: model.balance > 1 ? model.balance : "" balance: model.balance > 1 ? model.balance : ""
image: model.icon image: model.icon
goDeeperIconVisible: false goDeeperIconVisible: false
networkIcon: model.iconUrl
highlighted: model.key === root.highlightedKey highlighted: model.key === root.highlightedKey
isFirstSearchEntry: sublist.validSearchResultExists && model.index === 0
onClicked: { onClicked: {
if (isCommunity) if (isCommunity)
@ -214,6 +255,11 @@ Control {
root.collectibleSelected(model.key) root.collectibleSelected(model.key)
} }
} }
Keys.onReturnPressed: {
if(validSearchResultExists)
sublist.itemAtIndex(0).clicked()
}
} }
// Detection if the related model entry has been removed. // Detection if the related model entry has been removed.

View File

@ -11,6 +11,8 @@ import StatusQ.Core.Utils 0.1 as SQUtils
QtObject { QtObject {
id: root id: root
property var filteredFlatModel
/* PRIVATE: Modules used to get data from backend */ /* PRIVATE: Modules used to get data from backend */
readonly property var _allCollectiblesModule: !!walletSectionAllCollectibles ? walletSectionAllCollectibles : null 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 */ /* TODO: move all transformations to a dedicated adaptors */
readonly property LeftJoinModel jointCollectiblesBySymbolModel: LeftJoinModel { readonly property LeftJoinModel jointCollectiblesBySymbolModel: LeftJoinModel {
objectName: "jointCollectiblesBySymbolModel" objectName: "jointCollectiblesBySymbolModel"
leftModel: allCollectiblesModel leftModel: jointCollectiblesByNwChainId
rightModel: _renamedCommunitiesModel rightModel: _renamedCommunitiesModel
joinRole: "communityId" joinRole: "communityId"
} }

View File

@ -51,7 +51,9 @@ QtObject {
property var accountSensitiveSettings: localAccountSensitiveSettings property var accountSensitiveSettings: localAccountSensitiveSettings
property bool hideSignPhraseModal: accountSensitiveSettings.hideSignPhraseModal property bool hideSignPhraseModal: accountSensitiveSettings.hideSignPhraseModal
property CollectiblesStore collectiblesStore: CollectiblesStore {} property CollectiblesStore collectiblesStore: CollectiblesStore {
filteredFlatModel: root.filteredFlatModel
}
readonly property bool areTestNetworksEnabled: networksModule.areTestNetworksEnabled readonly property bool areTestNetworksEnabled: networksModule.areTestNetworksEnabled

View File

@ -17,6 +17,7 @@ ItemDelegate {
required property string symbol required property string symbol
required property string currencyBalanceAsString required property string currencyBalanceAsString
required property string iconSource required property string iconSource
required property bool isFirstSearchEntry
// expected structure: [iconUrl: string, balanceAsString: string] // expected structure: [iconUrl: string, balanceAsString: string]
property alias balancesModel: balancesListView.model property alias balancesModel: balancesListView.model
@ -36,9 +37,11 @@ ItemDelegate {
background: Rectangle { background: Rectangle {
radius: Theme.radius radius: Theme.radius
color: root.hovered || root.highlighted color: root.hovered || root.isFirstSearchEntry
? Theme.palette.statusListItem.highlightColor ? Theme.palette.baseColor2
: "transparent" : root.highlighted
? Theme.palette.statusListItem.highlightColor
: "transparent"
HoverHandler { HoverHandler {
cursorShape: root.enabled ? Qt.PointingHandCursor : undefined cursorShape: root.enabled ? Qt.PointingHandCursor : undefined

View File

@ -15,6 +15,8 @@ ItemDelegate {
required property string name required property string name
required property string balance required property string balance
required property url image required property url image
required property string networkIcon
required property bool isFirstSearchEntry
property bool goDeeperIconVisible: true property bool goDeeperIconVisible: true
property bool interactive: true property bool interactive: true
@ -36,9 +38,11 @@ ItemDelegate {
background: Rectangle { background: Rectangle {
radius: Theme.radius radius: Theme.radius
color: (root.interactive && root.hovered) || root.highlighted color: (root.interactive && (root.hovered || isFirstSearchEntry ))
? Theme.palette.statusListItem.highlightColor ? Theme.palette.baseColor2
: "transparent" : root.highlighted
? Theme.palette.statusListItem.highlightColor
: "transparent"
HoverHandler { HoverHandler {
cursorShape: root.interactive ? Qt.PointingHandCursor : undefined cursorShape: root.interactive ? Qt.PointingHandCursor : undefined
@ -59,7 +63,7 @@ ItemDelegate {
Layout.fillWidth: true Layout.fillWidth: true
spacing: 0 spacing: 0
// name, symbol, total balance // name, symbol, total balance, network icon
RowLayout { RowLayout {
Layout.fillWidth: true Layout.fillWidth: true
spacing: root.spacing spacing: root.spacing
@ -86,6 +90,15 @@ ItemDelegate {
elide: Text.ElideRight 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 { StatusIcon {
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter