status-desktop/storybook/pages/SimpleSendModalPage.qml
Cuteivist 89564d2970
feat: Adjust send model height depending on contents (#17336)
* feat: Adjust send model height depending on contents

* fix: Revert recipient_type from adapter

* fix: Updated the button size

* fix: Unit test
2025-02-21 22:13:36 +01:00

785 lines
29 KiB
QML

import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import SortFilterProxyModel 0.2
import StatusQ 0.1
import StatusQ.Core 0.1
import StatusQ.Core.Utils 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Core.Backpressure 0.1
import Models 1.0
import Storybook 1.0
import AppLayouts.Wallet.popups.simpleSend 1.0
import AppLayouts.Wallet.stores 1.0
import AppLayouts.Wallet.adaptors 1.0
import utils 1.0
SplitView {
id: root
orientation: Qt.Horizontal
QtObject {
id: d
readonly property SortFilterProxyModel filteredNetworksModel: SortFilterProxyModel {
sourceModel: NetworksModel.flatNetworks
filters: ValueFilter { roleName: "isTest"; value: testNetworksCheckbox.checked }
}
readonly property WalletAssetsStore walletAssetStore: WalletAssetsStore {
assetsWithFilteredBalances: groupedAccountsAssetsModel
walletTokensStore: TokensStore {
plainTokensBySymbolModel: TokensBySymbolModel{}
getDisplayAssetsBelowBalanceThresholdDisplayAmount: () => 0
}
}
readonly property var walletAccountsModel: WalletAccountsModel{}
function getCurrencyAmount(amount, symbol) {
return ({
amount: amount,
symbol: symbol ? symbol.toUpperCase() : root.currentCurrency,
displayDecimals: 2,
stripTrailingZeroes: false
})
}
readonly property var savedAddressesModel: ListModel {
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",
})
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 () {
simpleSend.routesLoading = false
simpleSend.estimatedTime = "~60s"
simpleSend.estimatedFiatFees = "1.45 EUR"
simpleSend.estimatedCryptoFees = "0.0007 ETH"
simpleSend.routerErrorCode = Constants.routerErrorCodes.router.errNotEnoughNativeBalance
simpleSend.routerError = qsTr("Not enough ETH to pay gas fees")
simpleSend.routerErrorDetails = ""
})
function formatCurrencyAmount(amount, symbol, options = null, locale = null) {
if (isNaN(amount)) {
return "N/A"
}
var currencyAmount = d.getCurrencyAmount(amount, symbol)
return LocaleUtils.currencyAmountToLocaleString(currencyAmount, options, locale)
}
function resetRouterValues() {
simpleSend.estimatedCryptoFees = ""
simpleSend.estimatedFiatFees = ""
simpleSend.estimatedTime = ""
simpleSend.routerErrorCode = ""
simpleSend.routerError = ""
simpleSend.routerErrorDetails = ""
}
}
PopupBackground {
id: popupBg
SplitView.fillWidth: true
SplitView.fillHeight: true
Button {
id: reopenButton
anchors.centerIn: parent
text: "Reopen"
enabled: !simpleSend.visible
onClicked: simpleSend.open()
}
Component.onCompleted: simpleSend.open()
}
SimpleSendModal {
id: simpleSend
visible: true
modal: false
closePolicy: Popup.CloseOnEscape
interactive: interactiveCheckbox.checked
displayOnlyAssets: displayOnlyAssetsCheckbox.checked
transferOwnership: transferOwnershipCheckbox.checked
accountsModel: accountsSelectorAdaptor.processedWalletAccounts
assetsModel: assetsSelectorViewAdaptor.outputAssetsModel
flatCollectiblesModel: collectiblesSelectionAdaptor.filteredFlatModel
collectiblesModel: collectiblesSelectionAdaptor.model
networksModel: d.filteredNetworksModel
recipientsModel: recipientViewAdaptor.recipientsModel
recipientsFilterModel: recipientViewAdaptor.recipientsFilterModel
highestTabElementCount: recipientViewAdaptor.highestTabElementCount
selectedRecipientAddress: "0x2B748A02e06B159C7C3E98F5064577B96E55A7b4"
selectedTokenKey: "SNT"
// selectedRawAmount: "10000000"
currentCurrency: "USD"
fnFormatCurrencyAmount: d.formatCurrencyAmount
fnResolveENS: Backpressure.debounce(root, 500, function (ensName, uuid) {
if (!!ensName && ensName.endsWith(".eth")) {
// return some valid address
simpleSend.ensNameResolved(ensName, "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc4", uuid)
} else {
simpleSend.ensNameResolved(ensName, "", uuid) // invalid
}
})
onFormChanged: {
d.resetRouterValues()
if(allValuesFilledCorrectly) {
console.log("Fetch fees...")
routesLoading = true
d.setFees()
}
}
onReviewSendClicked: console.log("Review send clicked")
onLaunchBuyFlow: console.log("launch buy flow clicked")
Binding on selectedAccountAddress {
value: accountsCombobox.currentValue ?? ""
}
Binding on selectedChainId {
value: networksCombobox.currentValue ?? 0
}
Binding on selectedTokenKey {
value: tokensCombobox.currentValue ?? ""
}
}
RecipientViewAdaptor {
id: recipientViewAdaptor
savedAddressesModel: d.savedAddressesModel
accountsModel: d.walletAccountsModel
recentRecipientsModel: WalletTransactionsModel{}
selectedSenderAddress: simpleSend.selectedAccountAddress
selectedRecipientType: simpleSend.selectedRecipientType
searchPattern: simpleSend.recipientSearchPattern
}
WalletAccountsSelectorAdaptor {
id: accountsSelectorAdaptor
accounts: d.walletAccountsModel
assetsModel: GroupedAccountsAssetsModel {}
tokensBySymbolModel: d.walletAssetStore.walletTokensStore.plainTokensBySymbolModel
filteredFlatNetworksModel: d.filteredNetworksModel
selectedTokenKey: simpleSend.selectedTokenKey
selectedNetworkChainId: simpleSend.selectedChainId
fnFormatCurrencyAmountFromBigInt: function(balance, symbol, decimals, options = null) {
let bigIntBalance = AmountsArithmetic.fromString(balance)
let decimalBalance = AmountsArithmetic.toNumber(bigIntBalance, decimals)
return d.formatCurrencyAmount(decimalBalance, symbol, options)
}
}
TokenSelectorViewAdaptor {
id: assetsSelectorViewAdaptor
assetsModel: d.walletAssetStore.groupedAccountAssetsModel
flatNetworksModel: NetworksModel.flatNetworks
currentCurrency: "USD"
showCommunityAssets: true
accountAddress: simpleSend.selectedAccountAddress
enabledChainIds: [simpleSend.selectedChainId]
}
CollectiblesSelectionAdaptor {
id: collectiblesSelectionAdaptor
accountKey: simpleSend.selectedAccountAddress
enabledChainIds: [simpleSend.selectedChainId]
networksModel: d.filteredNetworksModel
collectiblesModel: collectiblesBySymbolModel
filterCommunityOwnerAndMasterTokens: true
}
ListModel {
id: collectiblesBySymbolModel
readonly property var data: [
// collection 2
{
tokenId: "id_3",
symbol: "abc",
chainId: NetworksModel.mainnetChainId,
name: "Multi-seq NFT 1",
contractAddress: "contract_2",
collectionName: "Multi-sequencer Test NFT",
collectionUid: "collection_2",
ownership: [
{
accountAddress: d.walletAccountsModel.accountAddress1,
balance: 1,
txTimestamp: 1714059810
}
],
imageUrl: Constants.tokenIcon("ETH", false),
mediaUrl: Qt.resolvedUrl(""),
communityId: "",
communityName: "",
communityImage: Qt.resolvedUrl(""),
tokenType: Constants.TokenType.ERC721
},
{
tokenId: "id_4",
symbol: "def",
chainId: NetworksModel.mainnetChainId,
name: "Multi-seq NFT 2",
contractAddress: "contract_2",
collectionName: "Multi-sequencer Test NFT",
collectionUid: "collection_2",
ownership: [
{
accountAddress: d.walletAccountsModel.accountAddress1,
balance: 1,
txTimestamp: 1714059811
}
],
imageUrl: Constants.tokenIcon("ETH", false),
mediaUrl: Qt.resolvedUrl(""),
communityId: "",
communityName: "",
communityImage: Qt.resolvedUrl(""),
tokenType: Constants.TokenType.ERC721
},
{
tokenId: "id_5",
symbol: "ghi",
chainId: NetworksModel.mainnetChainId,
name: "Multi-seq NFT 3",
contractAddress: "contract_2",
collectionName: "Multi-sequencer Test NFT",
collectionUid: "collection_2",
ownership: [
{
accountAddress: d.walletAccountsModel.accountAddress1,
balance: 1,
txTimestamp: 1714059899
}
],
imageUrl: Constants.tokenIcon("ETH", false),
mediaUrl: Qt.resolvedUrl(""),
communityId: "",
communityName: "",
communityImage: Qt.resolvedUrl(""),
tokenType: Constants.TokenType.ERC721
},
// collection 1
{
tokenId: "id_1",
symbol: "jkl",
chainId: NetworksModel.mainnetChainId,
name: "Genesis",
contractAddress: "contract_1",
collectionName: "ERC-1155 Faucet",
collectionUid: "collection_1",
ownership: [
{
accountAddress: d.walletAccountsModel.accountAddress1,
balance: 23,
txTimestamp: 1714059862
},
{
accountAddress: d.walletAccountsModel.accountAddress2,
balance: 29,
txTimestamp: 1714054862
}
],
imageUrl: Constants.tokenIcon("DAI", false),
mediaUrl: Qt.resolvedUrl(""),
communityId: "",
communityName: "",
communityImage: Qt.resolvedUrl(""),
tokenType: Constants.TokenType.ERC1155
},
{
tokenId: "id_2",
symbol: "mno",
chainId: NetworksModel.mainnetChainId,
name: "QAERC1155",
contractAddress: "contract_1",
collectionName: "ERC-1155 Faucet",
collectionUid: "collection_1",
ownership: [
{
accountAddress: d.walletAccountsModel.accountAddress1,
balance: 500,
txTimestamp: 1714059864
}
],
imageUrl: Constants.tokenIcon("ZRX", false),
mediaUrl: Qt.resolvedUrl(""),
communityId: "",
communityName: "",
communityImage: Qt.resolvedUrl(""),
tokenType: Constants.TokenType.ERC1155
},
// collection 3, community token
{
tokenId: "id_6",
symbol: "pqr",
chainId: NetworksModel.optChainId,
name: "My Token",
contractAddress: "contract_3",
collectionName: "My Token",
collectionUid: "collection_3",
ownership: [
{
accountAddress: d.walletAccountsModel.accountAddress1,
balance: 1,
txTimestamp: 1714059899
}
],
imageUrl: Constants.tokenIcon("ZRX", false),
mediaUrl: Qt.resolvedUrl(""),
communityId: "community_1",
communityName: "My community",
communityImage: Constants.tokenIcon("KIN", false),
tokenType: Constants.TokenType.ERC721
},
{
tokenId: "id_7",
symbol: "stu",
chainId: NetworksModel.optChainId,
name: "My Token",
contractAddress: "contract_3",
collectionName: "My Token",
collectionUid: "collection_3",
ownership: [
{
accountAddress: d.walletAccountsModel.accountAddress1,
balance: 1,
txTimestamp: 1714059899
}
],
imageUrl: Constants.tokenIcon("ZRX", false),
mediaUrl: Qt.resolvedUrl(""),
communityId: "community_1",
communityName: "My community",
communityImage: Constants.tokenIcon("KIN", false),
tokenType: Constants.TokenType.ERC721
},
{
tokenId: "id_8",
symbol: "vwx",
chainId: NetworksModel.optChainId,
name: "My Token",
contractAddress: "contract_3",
collectionName: "My Token",
collectionUid: "collection_3",
ownership: [
{
accountAddress: d.walletAccountsModel.accountAddress2,
balance: 1,
txTimestamp: 1714059999
}
],
imageUrl: Constants.tokenIcon("ZRX", false),
mediaUrl: Qt.resolvedUrl(""),
communityId: "community_1",
communityName: "My community",
communityImage: Constants.tokenIcon("KIN", false),
tokenType: Constants.TokenType.ERC721
},
{
tokenId: "id_9",
symbol: "yz1",
chainId: NetworksModel.optChainId,
name: "My Other Token",
contractAddress: "contract_4",
collectionName: "My Other Token",
collectionUid: "collection_4",
ownership: [
{
accountAddress: d.walletAccountsModel.accountAddress1,
balance: 1,
txTimestamp: 1714059991
}
],
imageUrl: Constants.tokenIcon("ZRX", false),
mediaUrl: Qt.resolvedUrl(""),
communityId: "community_1",
communityName: "My community",
communityImage: Constants.tokenIcon("KIN", false),
tokenType: Constants.TokenType.ERC721
},
{
tokenId: "id_10",
symbol: "234",
chainId: NetworksModel.arbChainId,
name: "My Community 2 Token",
contractAddress: "contract_5",
collectionName: "My Community 2 Token",
collectionUid: "collection_5",
ownership: [
{
accountAddress: d.walletAccountsModel.accountAddress1,
balance: 1,
txTimestamp: 1714059777
}
],
imageUrl: Constants.tokenIcon("ZRX", false),
mediaUrl: Qt.resolvedUrl(""),
communityId: "community_2",
communityName: "My community 2",
communityImage: Constants.tokenIcon("ICOS", false),
tokenType: Constants.TokenType.ERC721
},
{
tokenId: "id_11",
symbol: "567",
chainId: NetworksModel.arbChainId,
name: "My Community 2 Token",
contractAddress: "contract_5",
collectionName: "My Community 2 Token",
collectionUid: "collection_5",
ownership: [
{
accountAddress: d.walletAccountsModel.accountAddress1,
balance: 1,
txTimestamp: 1714059778
}
],
imageUrl: Constants.tokenIcon("ZRX", false),
mediaUrl: Qt.resolvedUrl(""),
communityId: "community_2",
communityName: "My community 2",
communityImage: Constants.tokenIcon("ICOS", false),
tokenType: Constants.TokenType.ERC721
},
{
tokenId: "id_11",
symbol: "8910",
chainId: NetworksModel.arbChainId,
name: "My Community 2 Token",
contractAddress: "contract_5",
collectionName: "My Community 2 Token",
collectionUid: "collection_5",
ownership: [
{
accountAddress: d.walletAccountsModel.accountAddress2,
balance: 1,
txTimestamp: 1714059779
}
],
imageUrl: Constants.tokenIcon("ZRX", false),
mediaUrl: Qt.resolvedUrl(""),
communityId: "community_2",
communityName: "My community 2",
communityImage: Constants.tokenIcon("ICOS", false),
tokenType: Constants.TokenType.ERC721
},
{
tokenId: "id_12",
symbol: "111213",
chainId: NetworksModel.arbChainId,
name: "My Community 2 Token",
contractAddress: "contract_5",
collectionName: "My Community 2 Token",
collectionUid: "collection_5",
ownership: [
{
accountAddress: d.walletAccountsModel.accountAddress3,
balance: 1,
txTimestamp: 1714059779
}
],
imageUrl: Constants.tokenIcon("ZRX", false),
mediaUrl: Qt.resolvedUrl(""),
communityId: "community_2",
communityName: "My community 2",
communityImage: Constants.tokenIcon("ICOS", false),
tokenType: Constants.TokenType.ERC721
},
{
tokenId: "id_13",
symbol: "141516",
chainId: NetworksModel.arbChainId,
name: "My Community 2 Token",
contractAddress: "contract_5",
collectionName: "My Community 2 Token",
collectionUid: "collection_5",
ownership: [
{
accountAddress: d.walletAccountsModel.accountAddress3,
balance: 1,
txTimestamp: 1714059788
}
],
imageUrl: Constants.tokenIcon("ZRX", false),
mediaUrl: Qt.resolvedUrl(""),
communityId: "community_2",
communityName: "My community 2",
communityImage: Constants.tokenIcon("ICOS", false),
tokenType: Constants.TokenType.ERC721
}
]
Component.onCompleted: {
append(data)
}
}
Pane {
SplitView.minimumHeight: 100
SplitView.minimumWidth: 300
SplitView.maximumWidth: 380
ColumnLayout {
spacing: 20
CheckBox {
id: interactiveCheckbox
text: "Is interactive"
checked: true
}
CheckBox {
id: transferOwnershipCheckbox
text: "transfer ownership"
checked: false
}
CheckBox {
id: displayOnlyAssetsCheckbox
text: "displayOnlyAssets"
}
Text {
text: "Select an accounts"
}
ComboBox {
id: accountsCombobox
model: SortFilterProxyModel {
sourceModel: d.walletAccountsModel
filters: ValueFilter {
roleName: "walletType"
value: Constants.watchWalletType
inverted: true
}
}
textRole: "name"
valueRole: "address"
currentIndex: 0
}
CheckBox {
id: testNetworksCheckbox
text: "are test networks enabled"
}
Text {
text: "Select a network"
}
ComboBox {
id: networksCombobox
model: d.filteredNetworksModel
textRole: "chainName"
valueRole: "chainId"
currentIndex: 0
}
Text {
text: "Select a token"
}
ComboBox {
id: tokensCombobox
Layout.preferredWidth: 200
model: ConcatModel {
sources: [
SourceModel {
model: ObjectProxyModel {
sourceModel: d.walletAssetStore.walletTokensStore.plainTokensBySymbolModel
delegate: SortFilterProxyModel {
readonly property var addressPerChain: this
sourceModel: LeftJoinModel {
leftModel: model.addressPerChain
rightModel: d.filteredNetworksModel
joinRole: "chainId"
}
filters: ValueFilter {
roleName: "isTest"
value: testNetworksCheckbox.checked
}
}
expectedRoles: "addressPerChain"
exposedRoles: "addressPerChain"
}
markerRoleValue: "first_model"
},
SourceModel {
model: RolesRenamingModel {
sourceModel: collectiblesBySymbolModel
mapping: [
RoleRename {
from: "symbol"
to: "key"
}
]
}
markerRoleValue: "second_model"
}
]
markerRoleName: "which_model"
expectedRoles: ["key", "name", "addressPerChain", "chainId", "ownership", "type", "tokenType", "addressPerChain"]
}
delegate: ItemDelegate {
contentItem: RowLayout {
Text {
text: model.name
}
StatusIcon {
icon: {
const iconUrl = ModelUtils.getByKey(NetworksModel.flatNetworks, "chainId", model.chainId, "iconUrl")
if(!!iconUrl)
return Theme.svg(iconUrl)
else return ""
}
}
}
onClicked: {
let tokenType = model.type ?? model.tokenType
if (tokenType === Constants.TokenType.ERC721) {
simpleSend.sendType = Constants.SendType.ERC721Transfer
simpleSend.selectedChainId = model.chainId
const firstAccountOwningSelectedCollectible = ModelUtils.get(model.ownership, 0, "accountAddress")
if(!!firstAccountOwningSelectedCollectible)
simpleSend.selectedAccountAddress = firstAccountOwningSelectedCollectible
}
else if (tokenType === Constants.TokenType.ERC1155) {
simpleSend.sendType = Constants.SendType.ERC1155Transfer
simpleSend.selectedChainId = model.chainId
const firstAccountOwningSelectedCollectible = ModelUtils.get(model.ownership, 0, "accountAddress")
if(!!firstAccountOwningSelectedCollectible)
simpleSend.selectedAccountAddress = firstAccountOwningSelectedCollectible
}
else {
let selectedChainId = ModelUtils.getByKey(model.addressPerChain, "layer", "1", "chainId")
if (!selectedChainId) {
selectedChainId = ModelUtils.get(model.addressPerChain, 0, "chainId")
}
simpleSend.selectedChainId = selectedChainId
simpleSend.sendType = Constants.SendType.Transfer
}
}
highlighted: tokensCombobox.highlightedIndex === index
}
textRole: "name"
valueRole: "key"
}
RowLayout {
Layout.fillWidth: true
TextField {
id: amountInput
Layout.preferredWidth: 200
Layout.preferredHeight: 50
validator: RegularExpressionValidator {
regularExpression: /^\d*\.?\d*$/
}
}
Button {
text: "update raw value in modal"
onClicked: simpleSend.selectedRawAmount = amountInput.text
}
}
Text {
text: "Select a recipient"
}
RowLayout {
Layout.fillWidth: true
TextField {
id: recipientInput
Layout.preferredWidth: 200
Layout.preferredHeight: 50
}
Button {
text: "update in modal"
onClicked: simpleSend.selectedRecipientAddress = recipientInput.text
}
}
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
}
Text {
text: "network selected is: " + simpleSend.selectedChainId
}
Text {
text: "token selected is: " + simpleSend.selectedTokenKey
}
Text {
text: "raw amount entered is: " + simpleSend.selectedRawAmount
}
Text {
text: "selected recipient is: \n" + simpleSend.selectedRecipientAddress
}
}
}
}
// category: Popups