Wallet(SendModal): New tokens selector intially integrated

Closes: #15512
This commit is contained in:
Michał Cieślak 2024-07-10 00:10:13 +02:00 committed by Michał
parent baa65de1ae
commit f6320f69cb
8 changed files with 196 additions and 128 deletions

View File

@ -4,9 +4,11 @@ import QtQuick.Layouts 1.15
import StatusQ 0.1
import StatusQ.Core 0.1
import StatusQ.Core.Utils 0.1
import StatusQ.Controls 0.1
import Storybook 1.0
import Models 1.0
import utils 1.0
import shared.popups.send 1.0
@ -22,18 +24,68 @@ SplitView {
orientation: Qt.Horizontal
property WalletAssetsStore walletAssetStore: WalletAssetsStore {
assetsWithFilteredBalances: root.assetsWithFilteredBalances
// Workaround to satisfy stub which is not empty (but should be)
assetsWithFilteredBalances: ListModel {}
property var groupedAccountAssetsModel: ListModel {
Component.onCompleted: {
const data = [
{
tokensKey: "key_eth",
name: "Ethereum",
symbol: "ETH",
decimals: 18,
communityId: "",
balances: [
{
chainId: "1",
balance: "122082928968121891",
account: "0x7F47C2e18a4BBf5487E6fb082eC2D9Ab0E6d7240",
},
{
chainId: "420",
balance: "559133758939097000",
account: "0x7F47C2e18a4BBf5487E6fb082eC2D9Ab0E6d7240"
}
],
currentCurrencyBalance: 234.234,
marketDetails: {
currencyPrice: {
amount: 12234.23,
displayDecimals: true
}
}
},
{
tokensKey: "key_dai",
name: "DAI",
symbol: "DAI",
decimals: 18,
communityId: "",
balances: [
{
chainId: "420",
balance: "1142155111",
account: "0x7F47C2e18a4BBf5487E6fb082eC2D9Ab0E6d7240"
},
{
chainId: "1",
balance: "4411211243121551121",
account: "0x7F47C2e18a4BBf5487E6fb082eC2D9Ab0E6d7240"
}
],
currentCurrencyBalance: 234.234,
marketDetails: {
currencyPrice: {
amount: 234.23,
displayDecimals: true
}
}
}
]
property SubmodelProxyModel assetsWithFilteredBalances: SubmodelProxyModel {
sourceModel: root.walletAssetStore.groupedAccountsAssetsModel
submodelRoleName: "balances"
delegateModel: SortFilterProxyModel {
sourceModel: submodel
filters: FastExpressionFilter {
expression: txStore.selectedSenderAccountAddress === model.account
expectedRoles: ["account"]
append(data)
}
}
}

View File

@ -9,6 +9,16 @@ import SortFilterProxyModel 0.2
import AppLayouts.Wallet.stores 1.0
// TODO: This store, as all other stores should be empty QtObject {}.
// All mocking should be done in place in Storybook pages and unit tests.
// If it's necessary to share mocks between tests/pages, such mock can be
// created by deriving from empty stub and putting in mocks dir.
// Stores itself should be simple, thin layers over functionality exposed from
// the backend. No additional logic should there. Data transformation logic
// should be delegated to adaptors, stateles helpers to proper utility singletons.
//
// PLEASE DO NOT ADD ANY NEW CONTENT HERE
QtObject {
id: root

View File

@ -53,6 +53,9 @@ QObject {
// output model
readonly property SortFilterProxyModel outputAssetsModel: SortFilterProxyModel {
objectName: "TokenSelectorViewAdaptor_outputAssetsModel"
sourceModel: showAllTokens && !!plainTokensBySymbolModel ? concatModel : assetsObjectProxyModel
proxyRoles: [
@ -135,6 +138,7 @@ QObject {
}
expression: isPresentOnEnabledNetworks(model.addressPerChain)
expectedRoles: ["addressPerChain"]
enabled: root.enabledChainIds.length
}
]
@ -203,6 +207,8 @@ QObject {
id: assetsObjectProxyModel
sourceModel: root.assetsModel
objectName: "TokenSelectorViewAdaptor_assetsObjectProxyModel"
delegate: SortFilterProxyModel {
id: delegateRoot

View File

@ -31,7 +31,7 @@ QtObject {
}
readonly property var collectiblesController: ManageTokensController {
sourceModel: _jointCollectiblesBySymbolModel
sourceModel: root.jointCollectiblesBySymbolModel
settingsKey: "WalletCollectibles"
serializeAsCollectibles: true
@ -85,8 +85,8 @@ QtObject {
]
}
/* PRIVATE: This model joins the "Tokens By Symbol Model" and "Communities Model" by communityId */
property LeftJoinModel _jointCollectiblesBySymbolModel: LeftJoinModel {
/* TODO: move all transformations to a dedicated adaptors */
readonly property LeftJoinModel jointCollectiblesBySymbolModel: LeftJoinModel {
objectName: "jointCollectiblesBySymbolModel"
leftModel: allCollectiblesModel

View File

@ -1522,8 +1522,12 @@ Item {
sourceComponent: SendPopups.SendModal {
onlyAssets: sendModal.onlyAssets
store: appMain.transactionStore
loginType: appMain.rootStore.loginType
store: appMain.transactionStore
collectiblesStore: appMain.walletCollectiblesStore
onClosed: {
sendModal.closed()
sendModal.preSelectedSendType = Constants.SendType.Unknown

View File

@ -1,6 +1,6 @@
import QtQuick 2.13
import QtQuick.Controls 2.13
import QtQuick.Layouts 1.13
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import QtQuick.Dialogs 1.3
import QtGraphicalEffects 1.0
import SortFilterProxyModel 0.2
@ -20,8 +20,10 @@ import StatusQ.Core.Theme 0.1
import StatusQ.Core.Utils 0.1
import StatusQ.Popups.Dialog 0.1
import AppLayouts.Wallet.controls 1.0
import AppLayouts.Wallet.adaptors 1.0
import AppLayouts.Wallet.controls 1.0
import AppLayouts.Wallet.panels 1.0
import AppLayouts.Wallet.stores 1.0 as WalletStores
import shared.popups.send.panels 1.0
import "./controls"
@ -48,7 +50,8 @@ StatusDialog {
property alias modalHeader: modalHeader.text
required property TransactionStore store
property var nestedCollectiblesModel: store.nestedCollectiblesModel
property WalletStores.CollectiblesStore collectiblesStore
property var bestRoutes
property bool isLoading: false
property int loginType
@ -117,40 +120,6 @@ StatusDialog {
property var hoveredHoldingType: Constants.TokenType.Unknown
readonly property bool isHoveredHoldingValidAsset: !!hoveredHolding && hoveredHoldingType === Constants.TokenType.ERC20
function getHolding(holdingId, holdingType) {
if (holdingType === Constants.TokenType.ERC20) {
return store.getAsset(assetsAdaptor.model, holdingId)
} else if (holdingType === Constants.TokenType.ERC721 || holdingType === Constants.TokenType.ERC1155) {
return store.getCollectible(holdingId)
} else {
return {}
}
}
function setSelectedHoldingId(holdingId, holdingType) {
let holding = getHolding(holdingId, holdingType)
setSelectedHolding(holding, holdingType)
}
function setSelectedHolding(holding, holdingType) {
d.selectedHoldingType = holdingType
d.selectedHolding = holding
let selectorHolding = store.holdingToSelectorHolding(holding, holdingType)
holdingSelector.setSelectedItem(selectorHolding, holdingType)
}
function setHoveredHoldingId(holdingId, holdingType) {
let holding = getHolding(holdingId, holdingType)
setHoveredHolding(holding, holdingType)
}
function setHoveredHolding(holding, holdingType) {
d.hoveredHoldingType = holdingType
d.hoveredHolding = holding
let selectorHolding = store.holdingToSelectorHolding(holding, holdingType)
holdingSelector.setHoveredItem(selectorHolding, holdingType)
}
onSelectedHoldingChanged: {
if (d.selectedHoldingType === Constants.TokenType.ERC20) {
if(!d.ensOrStickersPurpose && store.sendType !== Constants.SendType.Bridge)
@ -173,19 +142,6 @@ StatusDialog {
}
}
SendModalAssetsAdaptor {
id: assetsAdaptor
controller: popup.store.walletAssetStore.assetsController
showCommunityAssets: popup.store.tokensStore.showCommunityAssetsInSend
tokensModel: popup.store.walletAssetStore.groupedAccountAssetsModel
account: popup.store.selectedSenderAccountAddress
marketValueThreshold:
popup.store.tokensStore.displayAssetsBelowBalance
? popup.store.tokensStore.getDisplayAssetsBelowBalanceThresholdDisplayAmount()
: 0
}
LeftJoinModel {
id: fromNetworksRouteModel
leftModel: popup.store.fromNetworksRouteModel
@ -219,11 +175,35 @@ StatusDialog {
if(popup.preSelectedSendType !== Constants.SendType.Unknown) {
store.setSendType(popup.preSelectedSendType)
}
if ((popup.preSelectedHoldingType > Constants.TokenType.Native) &&
(popup.preSelectedHoldingType < Constants.TokenType.Unknown)) {
tokenListRect.browsingHoldingType = popup.preSelectedHoldingType
if (!!popup.preSelectedHoldingID) {
d.setSelectedHoldingId(popup.preSelectedHoldingID, popup.preSelectedHoldingType)
if (!!popup.preSelectedHoldingID
&& popup.preSelectedHoldingType > Constants.TokenType.Native
&& popup.preSelectedHoldingType < Constants.TokenType.Unknown) {
if (popup.preSelectedHoldingType === Constants.TokenType.ERC20) {
const entry = ModelUtils.getByKey(
assetsAdaptor.outputAssetsModel, "tokensKey",
popup.preSelectedHoldingID)
d.selectedHoldingType = Constants.TokenType.ERC20
d.selectedHolding = entry
holdingSelector.setCustom(entry.symbol, entry.iconSource,
popup.preSelectedHoldingID)
holdingSelector.selectedItem = entry
} else {
const entry = ModelUtils.getByKey(
popup.store.collectiblesModel,
"uid", popup.preSelectedHoldingID)
d.selectedHoldingType = entry.tokenType
d.selectedHolding = entry
const id = entry.communityId ? entry.collectionUid : entry.uid
holdingSelector.setCustom(entry.name,
entry.imageUrl || entry.mediaUrl,
id)
holdingSelector.selectedItem = entry
holdingSelector.currentTab = TokenSelectorPanel.Tabs.Collectibles
}
}
@ -267,9 +247,13 @@ StatusDialog {
selectedAddress: !!popup.preSelectedAccount && !!popup.preSelectedAccount.address ? popup.preSelectedAccount.address : ""
onCurrentAccountAddressChanged: {
store.setSenderAccount(currentAccountAddress)
if (d.isSelectedHoldingValidAsset) {
d.setSelectedHoldingId(d.selectedHolding.symbol, d.selectedHoldingType)
d.selectedHolding = ModelUtils.getByKey(
holdingSelector.assetsModel, "tokensKey",
d.selectedHolding.tokensKey)
}
popup.recalculateRoutesAndFees()
}
}
@ -316,25 +300,69 @@ StatusDialog {
text: d.isBridgeTx ? qsTr("Bridge") : qsTr("Send")
}
HoldingSelector {
TokenSelectorNew {
id: holdingSelector
property var selectedItem
property bool onlyAssets: false
assetsModel: assetsAdaptor.outputAssetsModel
collectiblesModel: collectiblesAdaptorLoader.active
? collectiblesAdaptorLoader.item.model : null
TokenSelectorViewAdaptor {
id: assetsAdaptor
assetsModel: popup.store.walletAssetStore.groupedAccountAssetsModel
flatNetworksModel: popup.store.flatNetworksModel
currentCurrency: popup.store.currencyStore.currentCurrency
accountAddress: popup.preSelectedAccount ? popup.preSelectedAccount.address : ""
showCommunityAssets: popup.store.tokensStore.showCommunityAssetsInSend
}
Loader {
id: collectiblesAdaptorLoader
active: !d.isBridgeTx
sourceComponent: CollectiblesSelectionAdaptor {
accountKey: popup.preSelectedAccount ? popup.preSelectedAccount.address : ""
collectiblesModel: collectiblesStore
? collectiblesStore.jointCollectiblesBySymbolModel
: null
}
}
onAssetSelected: {
const entry = ModelUtils.getByKey(
assetsModel, "tokensKey", key)
d.selectedHoldingType = Constants.TokenType.ERC20
d.selectedHolding = entry
selectedItem = entry
}
onCollectibleSelected: {
const entry = ModelUtils.getByKey(
popup.store.collectiblesModel,
"uid", key)
d.selectedHoldingType = entry.tokenType
d.selectedHolding = entry
selectedItem = entry
}
onCollectionSelected: {
const entry = ModelUtils.getByKey(
popup.store.collectiblesModel,
"collectionUid", key)
d.selectedHoldingType = entry.tokenType
d.selectedHolding = entry
selectedItem = entry
}
}
Item {
Layout.fillWidth: true
Layout.fillHeight: true
assetsModel: assetsAdaptor.model
collectiblesModel: popup.preSelectedAccount ? popup.nestedCollectiblesModel : null
networksModel: popup.store.flatNetworksModel
visible: (!!d.selectedHolding && d.selectedHoldingType !== Constants.TokenType.Unknown) ||
(!!d.hoveredHolding && d.hoveredHoldingType !== Constants.TokenType.Unknown)
onItemSelected: {
d.setSelectedHoldingId(holdingId, holdingType)
}
onSearchTextChanged: assetsAdaptor.assetSearchString = assetSearchString
formatCurrentCurrencyAmount: function(balance){
return popup.store.currencyStore.formatCurrencyAmount(balance, popup.store.currencyStore.currentCurrency)
}
formatCurrencyAmountFromBigInt: function(balance, symbol, decimals){
return popup.store.formatCurrencyAmountFromBigInt(balance, symbol, decimals, {noSymbol: true})
}
}
MaxSendButton {
@ -404,7 +432,7 @@ StatusDialog {
ColumnLayout {
spacing: 8
Layout.fillWidth: true
visible: !d.isBridgeTx && !!d.selectedHolding
visible: !d.isBridgeTx
StatusBaseText {
id: label
elide: Text.ElideRight
@ -429,39 +457,6 @@ StatusDialog {
}
}
TokenListView {
id: tokenListRect
Layout.fillHeight: true
Layout.fillWidth: true
Layout.topMargin: Style.current.padding
Layout.leftMargin: Style.current.xlPadding
Layout.rightMargin: Style.current.xlPadding
Layout.bottomMargin: Style.current.xlPadding + Style.current.padding
visible: !d.selectedHolding
assets: assetsAdaptor.model
collectibles: popup.preSelectedAccount ? popup.nestedCollectiblesModel : null
networksModel: popup.store.flatNetworksModel
onlyAssets: holdingSelector.onlyAssets
onTokenSelected: function (symbolOrTokenKey, holdingType) {
d.setSelectedHoldingId(symbolOrTokenKey, holdingType)
}
onTokenHovered: {
if(hovered) {
d.setHoveredHoldingId(symbol, holdingType)
} else {
d.setHoveredHoldingId("", Constants.TokenType.Unknown)
}
}
onAssetSearchStringChanged: assetsAdaptor.assetSearchString = assetSearchString
formatCurrentCurrencyAmount: function(balance){
return popup.store.currencyStore.formatCurrencyAmount(balance, popup.store.currencyStore.currentCurrency)
}
formatCurrencyAmountFromBigInt: function(balance, symbol, decimals) {
return popup.store.formatCurrencyAmountFromBigInt(balance, symbol, decimals, {noSymbol: true})
}
}
RecipientSelectorPanel {
id: recipientsPanel
@ -472,9 +467,7 @@ StatusDialog {
Layout.rightMargin: Style.current.xlPadding
Layout.bottomMargin: Style.current.padding
// TODO: To be removed after all other refactors done (initial tokens selector page removed, bridge modal separated)
// This panel must be shown by default if no recipient already selected, otherwise, hidden
visible: !recipientInputLoader.ready && !d.isBridgeTx && !!d.selectedHolding
visible: !recipientInputLoader.ready && !d.isBridgeTx
savedAddressesModel: popup.store.savedAddressesModel
myAccountsModel: d.accountsAdaptor.model
@ -513,7 +506,8 @@ StatusDialog {
contentWidth: availableWidth
visible: recipientInputLoader.ready && !!d.selectedHolding && (amountToSendInput.inputNumberValid || d.isCollectiblesTransfer)
visible: recipientInputLoader.ready &&
(amountToSendInput.inputNumberValid || d.isCollectiblesTransfer)
objectName: "sendModalScroll"

View File

@ -83,6 +83,8 @@ QObject {
ObjectProxyModel {
id: proxyModel
objectName: "sendModalAssetsAdaptor_proxyModel"
sourceModel: root.tokensModel ?? null
delegate: QObject {

View File

@ -180,7 +180,7 @@ Loader {
SendRecipientInput {
width: parent.width
height: visible ? implicitHeight: 0
visible: !root.isBridgeTx && !!root.selectedAsset
visible: !root.isBridgeTx
text: root.addressText
function validateInput() {