diff --git a/storybook/pages/AssetsViewPage.qml b/storybook/pages/AssetsViewPage.qml index 703c1002a8..e2bbe75750 100644 --- a/storybook/pages/AssetsViewPage.qml +++ b/storybook/pages/AssetsViewPage.qml @@ -8,9 +8,12 @@ import utils 1.0 import Storybook 1.0 import AppLayouts.Wallet.controls 1.0 +import AppLayouts.Wallet.panels 1.0 import Qt.labs.settings 1.1 +import StatusQ.Popups.Dialog 0.1 + SplitView { id: root @@ -196,6 +199,20 @@ SplitView { onHideRequested: logs.logEvent(`hide requested: ${key}`) onHideCommunityAssetsRequested: logs.logEvent(`hide community assets requested: ${communityKey}`) onManageTokensRequested: logs.logEvent(`manage tokens requested`) + + bannerComponent: buyReceiveBannerComponent + + Component { + id: buyReceiveBannerComponent + BuyReceiveBanner { + id: banner + topPadding: anyVisibleItems ? 8 : 0 + bottomPadding: anyVisibleItems ? 20 : 0 + + onCloseBuy: buyEnabled = false + onCloseReceive: receiveEnabled = false + } + } } } @@ -226,6 +243,7 @@ SplitView { id: sorterVisibleCheckBox text: "sorter visible" + checked: false } CheckBox { id: customOrderAvailableCheckBox diff --git a/storybook/pages/CollectiblesViewPage.qml b/storybook/pages/CollectiblesViewPage.qml index 7bcf9713c4..8fce8f164e 100644 --- a/storybook/pages/CollectiblesViewPage.qml +++ b/storybook/pages/CollectiblesViewPage.qml @@ -14,6 +14,7 @@ import utils 1.0 import AppLayouts.stores 1.0 as AppLayoutStores import AppLayouts.Communities.stores 1.0 as CommunitiesStore +import AppLayouts.Wallet.panels 1.0 import AppLayouts.Wallet.views 1.0 import AppLayouts.Wallet.stores 1.0 @@ -128,6 +129,15 @@ SplitView { isUpdating: ctrlUpdatingCheckbox.checked isFetching: ctrlFetchingCheckbox.checked isError: ctrlErrorCheckbox.checked + bannerComponent: BuyReceiveBanner { + id: buyReceiveBanner + buyEnabled: buyBannerCheckbox.checked + receiveEnabled: receiveBannerCheckbox.checked + onBuyClicked: logs.logEvent("onBuyClicked") + onReceiveClicked: logs.logEvent("onReceiveClicked") + onCloseBuy: buyBannerCheckbox.checked = false + onCloseReceive: receiveBannerCheckbox.checked = false + } Settings { id: settingsStore @@ -178,6 +188,16 @@ SplitView { checked: false text: "isError" } + CheckBox { + id: buyBannerCheckbox + checked: true + text: "buy banner visible" + } + CheckBox { + id: receiveBannerCheckbox + checked: true + text: "sell banner visible" + } ColumnLayout { Layout.fillWidth: true diff --git a/storybook/qmlTests/tests/tst_BannerCard.qml b/storybook/qmlTests/tests/tst_BannerCard.qml index be43800a89..199a491413 100644 --- a/storybook/qmlTests/tests/tst_BannerCard.qml +++ b/storybook/qmlTests/tests/tst_BannerCard.qml @@ -39,7 +39,7 @@ Item { function test_hoverState() { compare(componentUnderTest.hovered, false) - mouseMove(componentUnderTest) + mouseMove(componentUnderTest, componentUnderTest.width / 2, componentUnderTest.height / 2) compare(componentUnderTest.hovered, true) const closeButton = findChild(componentUnderTest, "bannerCard_closeButton") @@ -68,6 +68,7 @@ Item { componentUnderTest.close.connect(() => { closed = true }) + mouseMove(closeButton, closeButton.width / 2, closeButton.height / 2) mouseClick(closeButton) compare(closed, true) compare(clicked, false) diff --git a/storybook/qmlTests/tests/tst_BuyReceiveBanner.qml b/storybook/qmlTests/tests/tst_BuyReceiveBanner.qml index 30302c3bef..99a1867a26 100644 --- a/storybook/qmlTests/tests/tst_BuyReceiveBanner.qml +++ b/storybook/qmlTests/tests/tst_BuyReceiveBanner.qml @@ -91,6 +91,7 @@ Item { const buyCard = findChild(componentUnderTest, "buyCard") verify(!!buyCard) const closeButton = findChild(buyCard, "bannerCard_closeButton") + mouseMove(buyCard, buyCard.width / 2, buyCard.height / 2) verify(!!closeButton) verify(closeButton.visible) @@ -103,6 +104,7 @@ Item { const receiveCard = findChild(componentUnderTest, "receiveCard") verify(!!receiveCard) const closeButton = findChild(receiveCard, "bannerCard_closeButton") + mouseMove(receiveCard, receiveCard.width / 2, receiveCard.height / 2) verify(!!closeButton) verify(closeButton.visible) @@ -127,6 +129,7 @@ Item { const buyCard = findChild(componentUnderTest, "buyCard") verify(!!buyCard) const closeButton = findChild(buyCard, "bannerCard_closeButton") + mouseMove(buyCard, buyCard.width / 2, buyCard.height / 2) verify(!!closeButton) verify(closeButton.visible) diff --git a/ui/StatusQ/src/assets.qrc b/ui/StatusQ/src/assets.qrc index bfbc6ba698..eb1dc51287 100644 --- a/ui/StatusQ/src/assets.qrc +++ b/ui/StatusQ/src/assets.qrc @@ -8965,6 +8965,8 @@ assets/png/traffic_lights/maximize_pressed.png assets/png/traffic_lights/minimise.png assets/png/traffic_lights/minimise_pressed.png + assets/png/wallet/wallet-green.png + assets/png/wallet/flying-coin.png assets/png/appearance-dark.png assets/png/appearance-light.png assets/png/appearance-system.png diff --git a/ui/app/AppLayouts/Wallet/WalletLayout.qml b/ui/app/AppLayouts/Wallet/WalletLayout.qml index a6b278ee79..2798ad97ac 100644 --- a/ui/app/AppLayouts/Wallet/WalletLayout.qml +++ b/ui/app/AppLayouts/Wallet/WalletLayout.qml @@ -179,6 +179,17 @@ Item { RootStore.selectedAddress : StatusQUtils.ModelUtils.get(RootStore.nonWatchAccounts, 0, "address") } + + function launchBuyCryptoModal() { + const walletStore = RootStore + + d.buyFormData.selectedWalletAddress = d.getSelectedOrFirstNonWatchedAddress() + d.buyFormData.selectedNetworkChainId = StatusQUtils.ModelUtils.getByKey(RootStore.filteredFlatModel, "layer", 1, "chainId") + if(!!walletStore.currentViewedHoldingTokensKey && walletStore.currentViewedHoldingType === Constants.TokenType.ERC20) { + d.buyFormData.selectedTokenKey = walletStore.currentViewedHoldingTokensKey + } + Global.openBuyCryptoModalRequested(d.buyFormData) + } } SignPhraseModal { @@ -235,6 +246,7 @@ Item { d.swapFormData.defaultToTokenKey = RootStore.areTestNetworksEnabled ? Constants.swap.testStatusTokenKey : Constants.swap.mainnetStatusTokenKey Global.openSwapModalRequested(d.swapFormData) } + onLaunchBuyCryptoModal: d.launchBuyCryptoModal() } } @@ -364,14 +376,7 @@ Item { d.swapFormData.defaultToTokenKey = RootStore.areTestNetworksEnabled ? Constants.swap.testStatusTokenKey : Constants.swap.mainnetStatusTokenKey Global.openSwapModalRequested(d.swapFormData) } - onLaunchBuyCryptoModal: { - d.buyFormData.selectedWalletAddress = d.getSelectedOrFirstNonWatchedAddress() - d.buyFormData.selectedNetworkChainId = StatusQUtils.ModelUtils.getByKey(RootStore.filteredFlatModel, "layer", 1, "chainId") - if(!!walletStore.currentViewedHoldingTokensKey && walletStore.currentViewedHoldingType === Constants.TokenType.ERC20) { - d.buyFormData.selectedTokenKey = walletStore.currentViewedHoldingTokensKey - } - Global.openBuyCryptoModalRequested(d.buyFormData) - } + onLaunchBuyCryptoModal: d.launchBuyCryptoModal() ModelEntry { id: selectedCommunityForCollectible diff --git a/ui/app/AppLayouts/Wallet/controls/BannerCard.qml b/ui/app/AppLayouts/Wallet/controls/BannerCard.qml index fcb4471811..9fd04536cb 100644 --- a/ui/app/AppLayouts/Wallet/controls/BannerCard.qml +++ b/ui/app/AppLayouts/Wallet/controls/BannerCard.qml @@ -15,7 +15,7 @@ Control { property alias image: image.source property alias title: title.text property alias subTitle: subTitle.text - property alias closeEnabled: closeButton.visible + property bool closeEnabled: true signal clicked() signal close() @@ -86,7 +86,8 @@ Control { Layout.preferredWidth: 16 Layout.preferredHeight: 16 icon: "close" - color: Theme.palette.baseColor1 + color: closeHoverHandler.hovered ? Theme.palette.directColor1 : Theme.palette.baseColor1 + visible: root.closeEnabled && root.hovered TapHandler { id: closeHandler acceptedButtons: Qt.LeftButton @@ -94,6 +95,9 @@ Control { root.close() } } + HoverHandler { + id: closeHoverHandler + } } } } diff --git a/ui/app/AppLayouts/Wallet/panels/BuyReceiveBanner.qml b/ui/app/AppLayouts/Wallet/panels/BuyReceiveBanner.qml index ff4a7b1c92..6cadf0cbe3 100644 --- a/ui/app/AppLayouts/Wallet/panels/BuyReceiveBanner.qml +++ b/ui/app/AppLayouts/Wallet/panels/BuyReceiveBanner.qml @@ -29,7 +29,7 @@ Control { objectName: "buyCard" Layout.fillWidth: true Layout.preferredWidth: root.buyEnabled ? layout.width / layout.children.length : 0 - title: qsTr("Ways to buy crypto") + title: qsTr("Ways to buy assets") subTitle: qsTr("Via card or bank transfer") image: Theme.png("wallet/wallet-green") closeEnabled: root.closeEnabled @@ -51,7 +51,7 @@ Control { objectName: "receiveCard" Layout.fillWidth: true Layout.preferredWidth: root.receiveEnabled ? layout.width / layout.children.length : 0 - title: qsTr("Receive crypto") + title: qsTr("Receive assets") subTitle: qsTr("Deposit to your Wallet address") image: Theme.png("wallet/flying-coin") closeEnabled: root.closeEnabled diff --git a/ui/app/AppLayouts/Wallet/views/CollectiblesView.qml b/ui/app/AppLayouts/Wallet/views/CollectiblesView.qml index 04d3ada243..081e8177de 100644 --- a/ui/app/AppLayouts/Wallet/views/CollectiblesView.qml +++ b/ui/app/AppLayouts/Wallet/views/CollectiblesView.qml @@ -36,6 +36,7 @@ ColumnLayout { property bool isFetching: false // Indicates if a collectibles page is being loaded from the backend property bool isUpdating: false // Indicates if the collectibles list is being updated property bool isError: false // Indicates an error occurred while updating/fetching the collectibles list + property alias bannerComponent: banner.sourceComponent // allows/disables choosing custom sort order from a sorter property bool customOrderAvailable @@ -436,6 +437,11 @@ ColumnLayout { } } + Loader { + id: banner + Layout.fillWidth: true + } + ShapeRectangle { Layout.fillWidth: true Layout.topMargin: Theme.padding diff --git a/ui/app/AppLayouts/Wallet/views/RightTabView.qml b/ui/app/AppLayouts/Wallet/views/RightTabView.qml index 2109440cb3..345cc5bba0 100644 --- a/ui/app/AppLayouts/Wallet/views/RightTabView.qml +++ b/ui/app/AppLayouts/Wallet/views/RightTabView.qml @@ -7,6 +7,7 @@ import StatusQ.Controls 0.1 import StatusQ.Core 0.1 import StatusQ.Core.Theme 0.1 import StatusQ.Core.Utils 0.1 +import StatusQ.Popups.Dialog 0.1 import AppLayouts.Wallet.controls 1.0 @@ -35,6 +36,7 @@ RightTabBaseView { property alias currentTabIndex: walletTabBar.currentIndex signal launchShareAddressModal() + signal launchBuyCryptoModal() signal launchSwapModal(string tokensKey) function resetView() { @@ -78,6 +80,30 @@ RightTabBaseView { readonly property var detailedCollectibleActivityController: RootStore.tmpActivityController0 } + Settings { + id: walletSettings + category: "walletSettings-" + root.contactsStore.myPublicKey + property real collectiblesViewCustomOrderApplyTimestamp: 0 + property bool buyBannerEnabled: true + property bool receiveBannerEnabled: true + } + + Component { + id: buyReceiveBannerComponent + BuyReceiveBanner { + id: banner + topPadding: anyVisibleItems ? 8 : 0 + bottomPadding: anyVisibleItems ? 20 : 0 + + onBuyClicked: root.launchBuyCryptoModal() + onReceiveClicked: root.launchShareAddressModal() + buyEnabled: walletSettings.buyBannerEnabled + receiveEnabled: walletSettings.receiveBannerEnabled + onCloseBuy: walletSettings.buyBannerEnabled = false + onCloseReceive: walletSettings.receiveBannerEnabled = false + } + } + Component { id: confirmHideCommunityAssetsPopup @@ -258,6 +284,7 @@ RightTabBaseView { sorterVisible: filterButton.checked customOrderAvailable: RootStore.walletAssetsStore.assetsController.hasSettings model: assetsViewAdaptor.model + bannerComponent: buyReceiveBannerComponent marketDataError: !!root.networkConnectionStore ? root.networkConnectionStore.getMarketNetworkDownText() @@ -360,12 +387,6 @@ RightTabBaseView { Component.onCompleted: refreshSortSettings() Component.onDestruction: saveSortSettings() - readonly property Settings walletSettings: Settings { - id: walletSettings - category: "walletSettings-" + root.contactsStore.myPublicKey - property real collectiblesViewCustomOrderApplyTimestamp: 0 - } - readonly property Settings settings: Settings { id: settings property int currentSortValue: SortOrderComboBox.TokenOrderDateAdded @@ -381,6 +402,7 @@ RightTabBaseView { sendEnabled: root.networkConnectionStore.sendBuyBridgeEnabled && !RootStore.overview.isWatchOnlyAccount && RootStore.overview.canSend filterVisible: filterButton.checked customOrderAvailable: controller.hasSettings + bannerComponent: buyReceiveBannerComponent onCollectibleClicked: { RootStore.collectiblesStore.getDetailedCollectible(chainId, contractAddress, tokenId) RootStore.setCurrentViewedHolding(uid, uid, tokenType, communityId) @@ -438,6 +460,7 @@ RightTabBaseView { currencyStore: root.sharedRootStore.currencyStore showAllAccounts: RootStore.showAllAccounts filterVisible: false // TODO #16761: Re-enable filter for activity when implemented + bannerComponent: buyReceiveBannerComponent } } } diff --git a/ui/imports/shared/views/AssetsView.qml b/ui/imports/shared/views/AssetsView.qml index ab652960b6..bd4f1ffb62 100644 --- a/ui/imports/shared/views/AssetsView.qml +++ b/ui/imports/shared/views/AssetsView.qml @@ -64,6 +64,8 @@ Control { property bool communitySwapVisible: false property string balanceError + // banner component to be displayed on top of the list + property alias bannerComponent: banner.sourceComponent // global market data error, presented for all tokens expecting market data property string marketDataError @@ -218,6 +220,11 @@ Control { StatusDialogDivider { Layout.fillWidth: true } } + Loader { + id: banner + Layout.fillWidth: true + } + DelegateModel { id: regularModel diff --git a/ui/imports/shared/views/HistoryView.qml b/ui/imports/shared/views/HistoryView.qml index 3d19563e84..b97377da46 100644 --- a/ui/imports/shared/views/HistoryView.qml +++ b/ui/imports/shared/views/HistoryView.qml @@ -42,6 +42,9 @@ ColumnLayout { property bool hideVerticalScrollbar: false property int firstItemOffset: 0 + // banner component to be displayed on top of the list + property alias bannerComponent: banner.sourceComponent + property real yPosition: transactionListRoot.visibleArea.yPosition * transactionListRoot.contentHeight function resetView() { @@ -127,16 +130,6 @@ ColumnLayout { wrapMode: Text.WordWrap } - ShapeRectangle { - id: noTxs - Layout.fillWidth: true - Layout.preferredHeight: 42 - Layout.topMargin: !nonArchivalNodeError.visible? root.firstItemOffset : 0 - visible: !d.isInitialLoading && !root.walletRootStore.currentActivityFiltersStore.filtersSet && transactionListRoot.count === 0 - font.pixelSize: Theme.primaryTextFontSize - text: qsTr("Activity for this account will appear here") - } - Loader { id: filterPanelLoader active: root.filterVisible && (d.isInitialLoading || transactionListRoot.count > 0 || root.walletRootStore.currentActivityFiltersStore.filtersSet) @@ -151,6 +144,21 @@ ColumnLayout { } } + Loader { + id: banner + Layout.fillWidth: true + } + + ShapeRectangle { + id: noTxs + Layout.fillWidth: true + Layout.preferredHeight: 42 + Layout.topMargin: !nonArchivalNodeError.visible? root.firstItemOffset : 0 + visible: !d.isInitialLoading && !root.walletRootStore.currentActivityFiltersStore.filtersSet && transactionListRoot.count === 0 + font.pixelSize: Theme.primaryTextFontSize + text: qsTr("Activity for this account will appear here") + } + Item { id: transactionListWrapper Layout.alignment: Qt.AlignTop