From ca23852da67b402d23fc15625f73b0d15f28cb3c Mon Sep 17 00:00:00 2001 From: Khushboo Mehta Date: Tue, 3 Dec 2024 10:50:49 +0100 Subject: [PATCH] feat(@desktop/wallet): This commit is focussed on testing and adding dialog resizing for simple send fixes #16836 --- storybook/pages/SimpleSendModalPage.qml | 382 +++++++++++++++++- storybook/pages/StatusDialogPage.qml | 5 + storybook/src/Models/NetworksModel.qml | 20 +- storybook/src/Models/WalletAccountsModel.qml | 17 +- .../StatusQ/Popups/Dialog/StatusDialog.qml | 4 +- .../Popups/Dialog/StatusDialogHeader.qml | 10 + .../Wallet/panels/SendModalHeader.qml | 127 ++++++ .../Wallet/panels/StickySendModalHeader.qml | 133 ++++++ ui/app/AppLayouts/Wallet/panels/qmldir | 2 + .../popups/simpleSend/SimpleSendModal.qml | 185 ++++++++- 10 files changed, 858 insertions(+), 27 deletions(-) create mode 100644 ui/app/AppLayouts/Wallet/panels/SendModalHeader.qml create mode 100644 ui/app/AppLayouts/Wallet/panels/StickySendModalHeader.qml diff --git a/storybook/pages/SimpleSendModalPage.qml b/storybook/pages/SimpleSendModalPage.qml index 04e0ae49810..a306f97ac48 100644 --- a/storybook/pages/SimpleSendModalPage.qml +++ b/storybook/pages/SimpleSendModalPage.qml @@ -1,18 +1,35 @@ import QtQuick 2.15 import QtQuick.Controls 2.15 -import QtQuick.Layouts 1.15 +import SortFilterProxyModel 0.2 + +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 - function launchPopup() { - simpleSend.createObject(root) + 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 + } + + readonly property var walletAccountsModel: WalletAccountsModel{} } PopupBackground { @@ -27,25 +44,368 @@ SplitView { text: "Reopen" enabled: !simpleSend.visible - onClicked: launchPopup() + onClicked: simpleSend.open() } - Component.onCompleted: launchPopup() + Component.onCompleted: simpleSend.open() } - Component { + SimpleSendModal { id: simpleSend - SimpleSendModal { - visible: true - modal: false - closePolicy: Popup.NoAutoClose + visible: true + modal: false + closePolicy: Popup.CloseOnEscape + + accountsModel: d.walletAccountsModel + assetsModel: assetsSelectorViewAdaptor.outputAssetsModel + collectiblesModel: collectiblesSelectionAdaptor.model + networksModel: d.filteredNetworksModel + Component.onCompleted: simpleSend.open() + } + + TokenSelectorViewAdaptor { + id: assetsSelectorViewAdaptor + + assetsModel: d.walletAssetStore.groupedAccountAssetsModel + + flatNetworksModel: NetworksModel.flatNetworks + + currentCurrency: "USD" + accountAddress: simpleSend.selectedAccountAddress + showCommunityAssets: true + enabledChainIds: [simpleSend.selectedChainId] + } + + CollectiblesSelectionAdaptor { + id: collectiblesSelectionAdaptor + + accountKey: simpleSend.selectedAccountAddress + + networksModel: d.filteredNetworksModel + collectiblesModel: collectiblesBySymbolModel + } + + ListModel { + id: collectiblesBySymbolModel + + readonly property var data: [ + // collection 2 + { + tokenId: "id_3", + symbol: "abc", + chainId: NetworksModel.mainnetChainId, + name: "Multi-sequencer Test 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("") + }, + { + tokenId: "id_4", + symbol: "def", + chainId: NetworksModel.mainnetChainId, + name: "Multi-sequencer Test 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("") + }, + { + tokenId: "id_5", + symbol: "ghi", + chainId: NetworksModel.mainnetChainId, + name: "Multi-sequencer Test 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("") + }, + // 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("") + }, + { + 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("") + }, + // 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) + }, + { + 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) + }, + { + 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) + }, + { + 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) + }, + { + 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) + }, + { + 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) + }, + { + 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) + }, + { + 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) + }, + { + 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) + } + ] + + Component.onCompleted: { + append(data) } } LogsAndControlsPanel { SplitView.minimumHeight: 100 - SplitView.preferredHeight: 100 + SplitView.minimumWidth: 300 + CheckBox { + id: testNetworksCheckbox + text: "are test networks enabled" + } } } diff --git a/storybook/pages/StatusDialogPage.qml b/storybook/pages/StatusDialogPage.qml index fafe9dc9292..245cd41312f 100644 --- a/storybook/pages/StatusDialogPage.qml +++ b/storybook/pages/StatusDialogPage.qml @@ -75,6 +75,7 @@ SplitView { header: StatusDialogHeader { //color: Theme.palette.baseColor3 color: !!ctrlHeaderBgColor.text ? ctrlHeaderBgColor.text : Theme.palette.statusModal.backgroundColor + dropShadowEnabled: ctrlHeaderDropShadow.checked visible: dialog.title || dialog.subtitle headline.title: dialog.title @@ -233,6 +234,10 @@ SplitView { id: ctrlDropShadow text: "Footer drop shadow" } + CheckBox { + id: ctrlHeaderDropShadow + text: "Header drop shadow" + } } } } diff --git a/storybook/src/Models/NetworksModel.qml b/storybook/src/Models/NetworksModel.qml index e2214532c25..e3cb64a5ff9 100644 --- a/storybook/src/Models/NetworksModel.qml +++ b/storybook/src/Models/NetworksModel.qml @@ -12,6 +12,14 @@ QtObject { readonly property int testnetNet: 5 readonly property int customNet: 6 + readonly property int mainnetChainId: 1 + readonly property int sepMainnetChainId: 11155111 + readonly property int optChainId: 10 + readonly property int sepOptChainId: 11155420 + readonly property int arbChainId: 42161 + readonly property int sepArbChainId: 421614 + + function getShortChainName(chainId) { if(chainId === root.ethNet) return "eth" @@ -55,7 +63,7 @@ QtObject { readonly property var flatNetworks: ListModel { Component.onCompleted: append([ { - chainId: 1, + chainId: mainnetChainId, chainName: "Mainnet", blockExplorerURL: "https://etherscan.io/", iconUrl: "network/Network=Ethereum", @@ -69,7 +77,7 @@ QtObject { isRouteEnabled: true, }, { - chainId: 11155111, + chainId: sepMainnetChainId, chainName: "Sepolia Mainnet", blockExplorerURL: "https://sepolia.etherscan.io/", iconUrl: "network/Network=Ethereum", @@ -83,7 +91,7 @@ QtObject { isRouteEnabled: true, }, { - chainId: 10, + chainId: optChainId, chainName: "Optimism", blockExplorerURL: "https://optimistic.etherscan.io", iconUrl: "network/Network=Optimism", @@ -97,7 +105,7 @@ QtObject { isRouteEnabled: true, }, { - chainId: 11155420, + chainId: sepOptChainId, chainName: "Optimism Sepolia", blockExplorerURL: "https://sepolia-optimism.etherscan.io/", iconUrl: "network/Network=Optimism", @@ -111,7 +119,7 @@ QtObject { isRouteEnabled: true, }, { - chainId: 42161, + chainId: arbChainId, chainName: "Arbitrum", blockExplorerURL: "https://arbiscan.io/", iconUrl: "network/Network=Arbitrum", @@ -125,7 +133,7 @@ QtObject { isRouteEnabled: true, }, { - chainId: 421614, + chainId: sepArbChainId, chainName: "Arbitrum Sepolia", blockExplorerURL: "https://sepolia-explorer.arbitrum.io/", iconUrl: "network/Network=Arbitrum", diff --git a/storybook/src/Models/WalletAccountsModel.qml b/storybook/src/Models/WalletAccountsModel.qml index 3cb142c77c3..418e96e5924 100644 --- a/storybook/src/Models/WalletAccountsModel.qml +++ b/storybook/src/Models/WalletAccountsModel.qml @@ -3,13 +3,20 @@ import QtQuick 2.15 import utils 1.0 ListModel { + + readonly property string accountAddress1: "0x7F47C2e18a4BBf5487E6fb082eC2D9Ab0E6d7240" + readonly property string accountAddress2: "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8881" + readonly property string accountAddress3: "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8882" + readonly property string accountAddress4: "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8883" + readonly property string accountAddress5: "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8884" + readonly property var data: [ { name: "helloworld", emoji: "😋", colorId: Constants.walletAccountColors.primary, color: "#2A4AF5", - address: "0x7F47C2e18a4BBf5487E6fb082eC2D9Ab0E6d7240", + address: accountAddress1, walletType: "", canSend: true, position: 0, @@ -53,7 +60,7 @@ ListModel { emoji: "🚗", colorId: Constants.walletAccountColors.army, color: "#216266", - address: "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8881", + address: accountAddress2, walletType: Constants.generatedWalletType, canSend: true, position: 3, @@ -79,7 +86,7 @@ ListModel { emoji: "🎨", colorId: Constants.walletAccountColors.magenta, color: "#EC266C", - address: "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8882", + address: accountAddress3, walletType: Constants.seedWalletType, canSend: true, position: 1, @@ -114,7 +121,7 @@ ListModel { emoji: "⌚", colorId: Constants.walletAccountColors.copper, color: "#CB6256", - address: "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8883", + address: accountAddress4, walletType: Constants.watchWalletType, canSend: false, position: 2, @@ -131,7 +138,7 @@ ListModel { emoji: "🔑", colorId: Constants.walletAccountColors.camel, color: "#C78F67", - address: "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8884", + address: accountAddress5, walletType: Constants.keyWalletType, canSend: true, position: 4, diff --git a/ui/StatusQ/src/StatusQ/Popups/Dialog/StatusDialog.qml b/ui/StatusQ/src/StatusQ/Popups/Dialog/StatusDialog.qml index dfd9660230e..bd8a75ca3a6 100644 --- a/ui/StatusQ/src/StatusQ/Popups/Dialog/StatusDialog.qml +++ b/ui/StatusQ/src/StatusQ/Popups/Dialog/StatusDialog.qml @@ -1,5 +1,6 @@ import QtQuick 2.15 import QtQuick.Controls 2.15 +import QtQuick.Window 2.15 import QtQuick.Layouts 1.15 import QtQml.Models 2.15 import QtQml 2.15 @@ -32,7 +33,8 @@ Dialog { anchors.centerIn: Overlay.overlay padding: 16 - margins: 64 + // by design + margins: root.contentItem.Window.window.height <= 780 ? 28: 64 modal: true // workaround for https://bugreports.qt.io/browse/QTBUG-87804 diff --git a/ui/StatusQ/src/StatusQ/Popups/Dialog/StatusDialogHeader.qml b/ui/StatusQ/src/StatusQ/Popups/Dialog/StatusDialogHeader.qml index 1d2c216f7de..ce11c13ece4 100644 --- a/ui/StatusQ/src/StatusQ/Popups/Dialog/StatusDialogHeader.qml +++ b/ui/StatusQ/src/StatusQ/Popups/Dialog/StatusDialogHeader.qml @@ -1,5 +1,6 @@ import QtQuick 2.14 import QtQuick.Layouts 1.14 +import QtGraphicalEffects 1.15 import StatusQ.Core 0.1 import StatusQ.Core.Theme 0.1 @@ -9,6 +10,7 @@ Rectangle { readonly property alias headline: headline readonly property alias actions: actions + property bool dropShadowEnabled property alias leftComponent: leftComponentLoader.sourceComponent @@ -63,4 +65,12 @@ Rectangle { anchors.bottom: parent.bottom width: parent.width } + + layer.enabled: root.dropShadowEnabled + layer.effect: DropShadow { + horizontalOffset: 0 + verticalOffset: 2 + samples: 37 + color: Theme.palette.dropShadow + } } diff --git a/ui/app/AppLayouts/Wallet/panels/SendModalHeader.qml b/ui/app/AppLayouts/Wallet/panels/SendModalHeader.qml new file mode 100644 index 00000000000..9daa0e1efb0 --- /dev/null +++ b/ui/app/AppLayouts/Wallet/panels/SendModalHeader.qml @@ -0,0 +1,127 @@ +import QtQuick 2.15 +import QtQuick.Layouts 1.15 + +import StatusQ 0.1 +import StatusQ.Core 0.1 +import StatusQ.Core.Theme 0.1 + +import AppLayouts.Wallet.controls 1.0 + +RowLayout { + id: root + + /** + Expected model structure: + - tokensKey: unique string ID of the token (asset); e.g. "ETH" or contract address + - name: user visible token name (e.g. "Ethereum") + - symbol: user visible token symbol (e.g. "ETH") + - decimals: number of decimal places + - communityId:optional; ID of the community this token belongs to, if any + - marketDetails: object containing props like `currencyPrice` for the computed values below + - balances: submodel[ chainId:int, account:string, balance:BigIntString, iconUrl:string ] + - currentBalance: amount of tokens + - currencyBalance: e.g. `1000.42` in user's fiat currency + - currencyBalanceAsString: e.g. "1 000,42 CZK" formatted as a string according to the user's locale + - balanceAsString: `1.42` formatted as e.g. "1,42" in user's locale + - iconSource: string + **/ + required property var assetsModel + /** + Expected model structure: + - groupName: group name (from collection or community name) + - icon: from imageUrl or mediaUrl + - type: can be "community" or "other" + - subitems: submodel of collectibles/collections of the group + - key: key of collection (community type) or collectible (other type) + - name: name of the subitem (of collectible or collection) + - balance: balance of collection (in case of community collectibles) + or collectible (in case of ERC-1155) + - icon: icon of the subitem + **/ + required property var collectiblesModel + /** + Expected model structure: + - chainId: network chain id + - chainName: name of network + - iconUrl: network icon url + **/ + required property var networksModel + + /** property holds if the header is the sticky header **/ + property bool isStickyHeader + + /** property that exposes the selected network **/ + readonly property int selectedNetworkChainId: selectedNetworkEntry.item.chainId + + /** signal to propagate that an asset was selected **/ + signal assetSelected(string key) + /** signal to propagate that a collection was selected **/ + signal collectionSelected(string key) + /** signal to propagate that a collectible was selected **/ + signal collectibleSelected(string key) + + implicitHeight: sendModalTitleText.height + + spacing: 8 + + StatusBaseText { + id: sendModalTitleText + + Layout.preferredWidth: contentWidth + + lineHeightMode: Text.FixedHeight + lineHeight: root.isStickyHeader ? 30 : 38 + font.pixelSize: root.isStickyHeader ? 22 : 28 + elide: Text.ElideRight + + text: qsTr("Send") + } + + TokenSelector { + id: tokenSelector + + Layout.preferredWidth: implicitWidth + + assetsModel: root.assetsModel + collectiblesModel: root.collectiblesModel + + onCollectibleSelected: root.collectibleSelected(key) + onCollectionSelected: root.collectionSelected(key) + onAssetSelected: root.assetSelected(key) + } + + // Horizontal spacer + RowLayout {} + + StatusBaseText { + Layout.alignment: Qt.AlignRight + text: qsTr("On:") + color: Theme.palette.baseColor1 + font.pixelSize: 13 + lineHeight: 38 + lineHeightMode: Text.FixedHeight + verticalAlignment: Text.AlignVCenter + visible: networkFilter.visible + } + + NetworkFilter { + id: networkFilter + + Layout.alignment: Qt.AlignTop + + flatNetworks: root.networksModel + + multiSelection: false + showSelectionIndicator: false + showTitle: false + + control.bottomPadding: 0 + } + + ModelEntry { + id: selectedNetworkEntry + sourceModel: root.networksModel + key: "chainId" + value: networkFilter.selection[0] + } +} diff --git a/ui/app/AppLayouts/Wallet/panels/StickySendModalHeader.qml b/ui/app/AppLayouts/Wallet/panels/StickySendModalHeader.qml new file mode 100644 index 00000000000..c2bbdb1cfee --- /dev/null +++ b/ui/app/AppLayouts/Wallet/panels/StickySendModalHeader.qml @@ -0,0 +1,133 @@ +import QtQuick 2.14 +import QtGraphicalEffects 1.15 + +import StatusQ.Core.Theme 0.1 +import StatusQ.Popups.Dialog 0.1 + +Rectangle { + id: root + + /** + Expected model structure: + - tokensKey: unique string ID of the token (asset); e.g. "ETH" or contract address + - name: user visible token name (e.g. "Ethereum") + - symbol: user visible token symbol (e.g. "ETH") + - decimals: number of decimal places + - communityId:optional; ID of the community this token belongs to, if any + - marketDetails: object containing props like `currencyPrice` for the computed values below + - balances: submodel[ chainId:int, account:string, balance:BigIntString, iconUrl:string ] + - currentBalance: amount of tokens + - currencyBalance: e.g. `1000.42` in user's fiat currency + - currencyBalanceAsString: e.g. "1 000,42 CZK" formatted as a string according to the user's locale + - balanceAsString: `1.42` formatted as e.g. "1,42" in user's locale + - iconSource: string + **/ + required property var assetsModel + /** + Expected model structure: + - groupName: group name (from collection or community name) + - icon: from imageUrl or mediaUrl + - type: can be "community" or "other" + - subitems: submodel of collectibles/collections of the group + - key: key of collection (community type) or collectible (other type) + - name: name of the subitem (of collectible or collection) + - balance: balance of collection (in case of community collectibles) + or collectible (in case of ERC-1155) + - icon: icon of the subitem + **/ + required property var collectiblesModel + /** + Expected model structure: + - chainId: network chain id + - chainName: name of network + - iconUrl: network icon url + **/ + required property var networksModel + + /** this property decided if the sticky header is visible or not. + Not using visible property directly here as the animation on + height doesnt work + **/ + property bool isVisible + + /** property that exposes the selected network **/ + readonly property int selectedNetworkChainId: sendModalHeader.selectedNetworkChainId + + /** signal to propagate that an asset was selected **/ + signal assetSelected(string key) + /** signal to propagate that a collection was selected **/ + signal collectionSelected(string key) + /** signal to propagate that a collectible was selected **/ + signal collectibleSelected(string key) + + color: Theme.palette.baseColor3 + radius: 8 + + implicitWidth: sendModalHeader.implicitWidth + sendModalHeader.anchors.leftMargin + sendModalHeader.anchors.rightMargin + + // cover for the bottom rounded corners + Rectangle { + width: parent.width + height: parent.radius + anchors.bottom: parent.bottom + color: parent.color + } + + SendModalHeader { + id: sendModalHeader + + width: parent.width + + anchors { + fill: parent + leftMargin: Theme.xlPadding + rightMargin: Theme.xlPadding + topMargin: 16 + bottomMargin: 12 + } + + networksModel: root.networksModel + assetsModel: root.assetsModel + collectiblesModel: root.collectiblesModel + + onCollectibleSelected: root.collectibleSelected(key) + onCollectionSelected: root.collectionSelected(key) + onAssetSelected: root.assetSelected(key) + isStickyHeader: true + } + + StatusDialogDivider { + anchors.bottom: parent.bottom + width: parent.width + } + + layer.enabled: true + layer.effect: DropShadow { + horizontalOffset: 0 + verticalOffset: 2 + samples: 37 + color: Theme.palette.dropShadow + } + + // Needed for drawer animation when send modal is scrolling + states: [ + State { + name: "visible"; when: root.isVisible + PropertyChanges { + target: root + height: sendModalHeader.implicitHeight + + sendModalHeader.anchors.topMargin + + sendModalHeader.anchors.bottomMargin + } + }, + State { + name: "notVisible"; when: !root.isVisible + PropertyChanges { target: root; height: 0 } + } + ] + + transitions: Transition { + NumberAnimation { property: "height"; duration: 350 } + } +} + diff --git a/ui/app/AppLayouts/Wallet/panels/qmldir b/ui/app/AppLayouts/Wallet/panels/qmldir index 8d837dfea89..01221de437c 100644 --- a/ui/app/AppLayouts/Wallet/panels/qmldir +++ b/ui/app/AppLayouts/Wallet/panels/qmldir @@ -12,3 +12,5 @@ SignInfoBox 1.0 SignInfoBox.qml SwapInputPanel 1.0 SwapInputPanel.qml TokenSelectorPanel 1.0 TokenSelectorPanel.qml WalletHeader 1.0 WalletHeader.qml +SendModalHeader 1.0 SendModalHeader.qml +StickySendModalHeader 1.0 StickySendModalHeader.qml diff --git a/ui/app/AppLayouts/Wallet/popups/simpleSend/SimpleSendModal.qml b/ui/app/AppLayouts/Wallet/popups/simpleSend/SimpleSendModal.qml index 40fe860ca2c..172c65f0221 100644 --- a/ui/app/AppLayouts/Wallet/popups/simpleSend/SimpleSendModal.qml +++ b/ui/app/AppLayouts/Wallet/popups/simpleSend/SimpleSendModal.qml @@ -1,17 +1,194 @@ import QtQuick 2.15 +import QtQuick.Layouts 1.14 +import StatusQ.Core 0.1 +import StatusQ.Controls 0.1 import StatusQ.Core.Theme 0.1 import StatusQ.Popups.Dialog 0.1 +import shared.popups.send.views 1.0 +import shared.controls 1.0 + +import AppLayouts.Wallet.panels 1.0 + StatusDialog { - id: popup + id: root + + /** + TODO: use the newly defined WalletAccountsSelectorAdaptor + in https://github.com/status-im/status-desktop/pull/16834 + Expected model structure: + - name: name of account + - address: wallet address + - color: color of the account + - emoji: emoji selected for the account + - currencyBalance: total currency balance in CurrencyAmount + - accountBalance: balance of selected token + selected chain + **/ + required property var accountsModel + /** + Expected model structure: + - tokensKey: unique string ID of the token (asset); e.g. "ETH" or contract address + - name: user visible token name (e.g. "Ethereum") + - symbol: user visible token symbol (e.g. "ETH") + - decimals: number of decimal places + - communityId:optional; ID of the community this token belongs to, if any + - marketDetails: object containing props like `currencyPrice` for the computed values below + - balances: submodel[ chainId:int, account:string, balance:BigIntString, iconUrl:string ] + - currentBalance: amount of tokens + - currencyBalance: e.g. `1000.42` in user's fiat currency + - currencyBalanceAsString: e.g. "1 000,42 CZK" formatted as a string according to the user's locale + - balanceAsString: `1.42` formatted as e.g. "1,42" in user's locale + - iconSource: string + **/ + required property var assetsModel + /** + Expected model structure: + - groupName: group name (from collection or community name) + - icon: from imageUrl or mediaUrl + - type: can be "community" or "other" + - subitems: submodel of collectibles/collections of the group + - key: key of collection (community type) or collectible (other type) + - name: name of the subitem (of collectible or collection) + - balance: balance of collection (in case of community collectibles) + or collectible (in case of ERC-1155) + - icon: icon of the subitem + **/ + required property var collectiblesModel + /** + Expected model structure: + - chainId: network chain id + - chainName: name of network + - iconUrl: network icon url + **/ + required property var networksModel + + /** TODO: rethink property definitions needed to pre set values + + expose values to outside world **/ + property int selectedChainId: sendModalHeader.selectedNetworkChainId + property string selectedAccountAddress: accountSelector.currentAccountAddress - title: qsTr("Send") + QtObject { + id: d + readonly property bool isScrolling: scrollView.flickable.contentY > sendModalHeader.height + Theme.xlPadding + } + + width: 556 padding: 0 + leftPadding: Theme.xlPadding + rightPadding: Theme.xlPadding + background: StatusDialogBackground { - implicitHeight: 846 - implicitWidth: 556 color: Theme.palette.baseColor3 } + + header: ColumnLayout { + width: parent.width + anchors.top: parent.top + anchors.topMargin: -accountSelector.height - Theme.padding + spacing: Theme.padding + + AccountSelectorHeader { + id: accountSelector + model: root.accountsModel + } + + // Sticky header only visible when scrolling + StickySendModalHeader { + Layout.fillWidth: true + networksModel: root.networksModel + assetsModel: root.assetsModel + collectiblesModel: root.collectiblesModel + isVisible: d.isScrolling + + onCollectibleSelected: console.log("collectible selected:", key) + onCollectionSelected: console.log("collection selected:", key) + onAssetSelected: console.log("asset selected:", key) + } + } + + // Main scrollable Layout + Item { + id: scrollViewWrapper + + anchors.fill: parent + anchors.top: parent.top + // offset the floating header height and add top margin as per design + anchors.topMargin: -accountSelector.height - Theme.padding + 28 + + implicitWidth: parent.width + implicitHeight: scrollView.implicitHeight + + StatusScrollView { + id: scrollView + + anchors.fill: parent + contentWidth: availableWidth + + padding: 0 + + StatusScrollBar.vertical { + id: verticalScrollbar + + parent: scrollViewWrapper + x: scrollViewWrapper.width + root.rightPadding - verticalScrollbar.width + } + + ColumnLayout { + id: scrollViewLayout + + width: scrollView.availableWidth + spacing: 20 + + // Header that scrolls + SendModalHeader { + id: sendModalHeader + + networksModel: root.networksModel + assetsModel: root.assetsModel + collectiblesModel: root.collectiblesModel + + onCollectibleSelected: console.log("collectible selected:", key) + onCollectionSelected: console.log("collection selected:", key) + onAssetSelected: console.log("asset selected:", key) + } + + // TODO: Remove these Dummy items added only to test dialog resizing + readonly property string longLoremIpsum: "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum." + Text { + Layout.fillWidth: true + text: scrollViewLayout.longLoremIpsum.repeat(3) + wrapMode: Text.WordWrap + } + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 200 + opacity: 0.2 + color: "red" + } + Text { + Layout.fillWidth: true + text: scrollViewLayout.longLoremIpsum.repeat(3) + wrapMode: Text.WordWrap + } + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 200 + opacity: 0.2 + color: "red" + } + // End Dummy items + } + } + } + + // TODO:: move to new location and rework if needed + footer: TransactionModalFooter { + width: parent.width + pending: false + nextButtonText: qsTr("Review Send") + maxFiatFees: "..." + totalTimeEstimate: "..." + } }