feat(@desktop/wallet): Adapt Token Selector

fixes #16702
This commit is contained in:
Khushboo Mehta 2024-11-08 12:22:13 +01:00
parent e6c0aa4a1f
commit 0b153d81c5
17 changed files with 267 additions and 49 deletions

View File

@ -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"
}
]

View File

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

View File

@ -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
isAutoHovered: ctrlIsAutoHovered.checked
}
}
@ -95,6 +97,12 @@ SplitView {
checked: false
}
Switch {
id: ctrlIsAutoHovered
text: "isAutoHovered"
checked: false
}
Item { Layout.fillHeight: true }
}
}

View File

@ -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: [
{

View File

@ -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",

View File

@ -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.isAutoHovered)
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.isAutoHovered)
compare(delegate1.background.color, Theme.palette.baseColor2)
}
{
control.clearSearch()

View File

@ -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: ""
isAutoHovered: 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 isAutoHovered behaviour
control.isAutoHovered = 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 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)
}
}
}

View File

@ -32,6 +32,16 @@ QObject {
/** Account key used for filtering **/
property string accountKey
/**
Expected model structure:
chainId [int] - network chain id
chainName [string] - name of network
iconUrl [string] - network icon url
**/
property var networksModel
/**
Expected model structure:
@ -64,13 +74,20 @@ QObject {
**/
readonly property alias model: communityGroupsGrouppedByCollection
LeftJoinModel {
id: jointCollectiblesByNwChainId
leftModel: collectiblesModel ?? null
rightModel: networksModel
joinRole: "chainId"
}
SortFilterProxyModel {
id: initiallyFilteredAndSorted
objectName: "collectiblesSelectionAdaptor_initiallyFilteredAndSorted"
sourceModel: ObjectProxyModel {
sourceModel: collectiblesModel ?? null
sourceModel: jointCollectiblesByNwChainId
delegate: QObject {
readonly property int balance: balanceAggregator.value /* 3 */

View File

@ -34,6 +34,11 @@ Control {
tokenSelectorPanel.highlightedKey = key ?? ""
}
QObject {
id: d
readonly property int maxPopupHeight: 330
}
contentItem: TokenSelectorButton {
id: tokenSelectorButton
@ -52,54 +57,63 @@ Control {
horizontalPadding: 0
bottomPadding: 0
contentItem: TokenSelectorPanel {
id: tokenSelectorPanel
onClosed: tokenSelectorPanel.clear()
objectName: "tokenSelectorPanel"
contentItem: Item {
function findSubitem(key) {
const count = collectiblesModel.rowCount()
implicitHeight: Math.min(tokenSelectorPanel.implicitHeight, d.maxPopupHeight)
for (let i = 0; i < count; i++) {
const entry = ModelUtils.get(collectiblesModel, i)
const subitem = ModelUtils.getByKey(
entry.subitems, "key", key)
if (subitem)
return subitem
TokenSelectorPanel {
id: tokenSelectorPanel
objectName: "tokenSelectorPanel"
anchors.fill: parent
function findSubitem(key) {
const count = collectiblesModel.rowCount()
for (let i = 0; i < count; i++) {
const entry = ModelUtils.get(collectiblesModel, i)
const subitem = ModelUtils.getByKey(
entry.subitems, "key", key)
if (subitem)
return subitem
}
}
}
function setCurrentAndClose(name, icon) {
tokenSelectorButton.name = name
tokenSelectorButton.icon = icon
tokenSelectorButton.selected = true
dropdown.close()
}
function setCurrentAndClose(name, icon) {
tokenSelectorButton.name = name
tokenSelectorButton.icon = icon
tokenSelectorButton.selected = true
dropdown.close()
}
onAssetSelected: {
const entry = ModelUtils.getByKey(assetsModel, "tokensKey", key)
highlightedKey = key
onAssetSelected: {
const entry = ModelUtils.getByKey(assetsModel, "tokensKey", key)
highlightedKey = key
setCurrentAndClose(entry.symbol, entry.iconSource)
root.assetSelected(key)
}
setCurrentAndClose(entry.symbol, entry.iconSource)
root.assetSelected(key)
}
onCollectibleSelected: {
highlightedKey = key
onCollectibleSelected: {
highlightedKey = key
const subitem = findSubitem(key)
setCurrentAndClose(subitem.name, subitem.icon)
const subitem = findSubitem(key)
setCurrentAndClose(subitem.name, subitem.icon)
root.collectibleSelected(key)
}
root.collectibleSelected(key)
}
onCollectionSelected: {
highlightedKey = key
onCollectionSelected: {
highlightedKey = key
const subitem = findSubitem(key)
setCurrentAndClose(subitem.name, subitem.icon)
const subitem = findSubitem(key)
setCurrentAndClose(subitem.name, subitem.icon)
root.collectionSelected(key)
root.collectionSelected(key)
}
}
}
}

View File

@ -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
}

View File

@ -39,6 +39,11 @@ Control {
searchBox.text = ""
}
QtObject {
id: d
readonly property bool validSearchResultExists: !!searchBox.text && sfpm.rowCount() > 0
}
SortFilterProxyModel {
id: sfpm
@ -64,6 +69,8 @@ Control {
Layout.fillWidth: true
placeholderText: qsTr("Search assets")
Keys.forwardTo: [listView]
}
StatusDialogDivider {
@ -81,6 +88,10 @@ Control {
Layout.fillWidth: true
Layout.fillHeight: true
Layout.preferredHeight: contentHeight
Layout.leftMargin: 4
Layout.rightMargin: 4
spacing: 4
model: sfpm
section.property: "sectionName"
@ -99,6 +110,7 @@ Control {
highlighted: model.tokensKey === root.highlightedKey
enabled: model.tokensKey !== root.nonInteractiveKey
balancesListInteractive: !ListView.view.moving
isAutoHovered: d.validSearchResultExists && index === 0 && !listViewHoverHandler.hovered
name: model.name
symbol: model.symbol
@ -108,6 +120,15 @@ Control {
onClicked: root.selected(model.tokensKey)
}
Keys.onReturnPressed: {
if(d.validSearchResultExists)
listView.itemAtIndex(0).clicked()
}
HoverHandler {
id: listViewHoverHandler
}
}
}
}

View File

@ -42,6 +42,10 @@ Control {
readonly property alias currentItem: collectiblesStackView.currentItem
function clearSearch() {
collectiblesSearchBox.text = ""
}
SortFilterProxyModel {
id: sfpm
@ -59,6 +63,14 @@ Control {
initialItem: ColumnLayout {
spacing: 0
StatusBaseText {
Layout.alignment: Qt.AlignHCenter
Layout.bottomMargin: 4
text: qsTr("Your collectibles will appear here")
color: Theme.palette.baseColor1
visible: !collectiblesListView.count && !collectiblesSearchBox.text
}
TokenSearchBox {
id: collectiblesSearchBox
@ -66,6 +78,10 @@ Control {
Layout.fillWidth: true
placeholderText: qsTr("Search collectibles")
visible: collectiblesListView.count || !!collectiblesSearchBox.text
Keys.forwardTo: [collectiblesListView]
}
StatusDialogDivider {
@ -76,9 +92,15 @@ Control {
StatusListView {
id: collectiblesListView
readonly property bool validSearchResultExists: !!collectiblesSearchBox.text && sfpm.count > 0
Layout.fillWidth: true
Layout.fillHeight: true
Layout.preferredHeight: contentHeight
Layout.leftMargin: 4
Layout.rightMargin: 4
spacing: 4
clip: true
model: sfpm
@ -100,10 +122,12 @@ Control {
image: model.icon
goDeeperIconVisible: subitemsCount > 1
|| isCommunity
networkIcon: model.iconUrl
highlighted: subitemsCount === 1 && !isCommunity
? ModelUtils.get(model.subitems, 0, "key")
=== root.highlightedKey
: false
isAutoHovered: collectiblesListView.validSearchResultExists && model.index === 0 && !collectiblesListViewHoverHandler.hovered
onClicked: {
if (subitemsCount === 1 && !isCommunity) {
@ -133,6 +157,15 @@ Control {
? qsTr("Community minted")
: qsTr("Other")
}
Keys.onReturnPressed: {
if(validSearchResultExists)
collectiblesListView.itemAtIndex(0).clicked()
}
HoverHandler {
id: collectiblesListViewHoverHandler
}
}
}
}
@ -180,6 +213,8 @@ Control {
Layout.fillWidth: true
placeholderText: qsTr("Search collectibles")
Keys.forwardTo: [sublist]
}
StatusDialogDivider {
@ -190,9 +225,13 @@ Control {
StatusListView {
id: sublist
readonly property bool validSearchResultExists: !!collectiblesSublistSearchBox.text && sublistSfpm.rowCount() > 0
Layout.fillWidth: true
Layout.fillHeight: true
Layout.preferredHeight: contentHeight
Layout.leftMargin: 4
Layout.rightMargin: 4
model: sublistSfpm
@ -205,7 +244,9 @@ Control {
balance: model.balance > 1 ? model.balance : ""
image: model.icon
goDeeperIconVisible: false
networkIcon: model.iconUrl
highlighted: model.key === root.highlightedKey
isAutoHovered: sublist.validSearchResultExists && model.index === 0 && !sublistHoverHandler.hovered
onClicked: {
if (isCommunity)
@ -214,6 +255,15 @@ Control {
root.collectibleSelected(model.key)
}
}
Keys.onReturnPressed: {
if(validSearchResultExists)
sublist.itemAtIndex(0).clicked()
}
HoverHandler {
id: sublistHoverHandler
}
}
// Detection if the related model entry has been removed.

View File

@ -40,6 +40,11 @@ Control {
property string highlightedKey: ""
function clear() {
searchableAssetsPanel.clearSearch()
searchableCollectiblesPanel.clearSearch()
}
contentItem: ColumnLayout {
StatusTabBar {
id: tabBar

View File

@ -17,6 +17,7 @@ ItemDelegate {
required property string symbol
required property string currencyBalanceAsString
required property string iconSource
required property bool isAutoHovered
// 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.isAutoHovered
? Theme.palette.baseColor2
: root.highlighted
? Theme.palette.statusListItem.highlightColor
: "transparent"
HoverHandler {
cursorShape: root.enabled ? Qt.PointingHandCursor : undefined

View File

@ -15,6 +15,8 @@ ItemDelegate {
required property string name
required property string balance
required property url image
required property string networkIcon
required property bool isAutoHovered
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 || isAutoHovered ))
? 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 || isAutoHovered) && !root.goDeeperIconVisible
}
StatusIcon {
Layout.alignment: Qt.AlignVCenter

View File

@ -409,6 +409,10 @@ StatusDialog {
sourceComponent: CollectiblesSelectionAdaptor {
accountKey: popup.selectedAccount.address
networksModel: SortFilterProxyModel {
sourceModel: popup.store.flatNetworksModel
filters: ValueFilter { roleName: "isTest"; value: popup.store.areTestNetworksEnabled }
}
collectiblesModel: SortFilterProxyModel {
sourceModel: collectiblesStore ? collectiblesStore.jointCollectiblesBySymbolModel : null
filters: ValueFilter {