feat(WalletFirst): Creating the wallet banner UI component

Banner items are created as per design.
Banner items support customizable close button.
Banner items have hove state as per design.
Banner items are added to storybook with all possible configurations.
Banner items have a close animation including fade-out and the remaining item occupy the empty space.
QML tests are added.
This commit is contained in:
Alex Jbanca 2024-11-21 13:45:08 +02:00 committed by Alex Jbanca
parent b555d19a1a
commit 5e6db4d2ff
10 changed files with 543 additions and 0 deletions

View File

@ -0,0 +1,59 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import AppLayouts.Wallet.controls 1.0
import StatusQ.Core.Theme 0.1
SplitView {
id: root
Pane {
id: leftPane
SplitView.fillWidth: true
SplitView.fillHeight: true
BannerCard {
anchors.centerIn: parent
title: titleField.text
subTitle: subTitleField.text
image: Theme.png("wallet/wallet-green")
closeEnabled: closeEnabled.checked
onClicked: {
print("BannerCard clicked")
}
onClose: {
print("BannerCard closed")
}
}
}
Pane {
id: rightPane
SplitView.fillHeight: true
SplitView.preferredWidth: 300
ColumnLayout {
anchors.fill: parent
CheckBox {
id: closeEnabled
text: "Show close button"
checked: true
}
Label {
text: "Title"
}
TextField {
id: titleField
text: "Ways to buy crypto"
}
Label {
text: "Sub title"
}
TextField {
id: subTitleField
text: "Via card or bank transfer"
}
Item {
Layout.fillHeight: true
}
}
}
}

View File

@ -0,0 +1,74 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import AppLayouts.Wallet.panels 1.0
import StatusQ.Core.Theme 0.1
SplitView {
id: root
Pane {
id: leftPane
SplitView.fillWidth: true
SplitView.fillHeight: true
BuyReceiveBanner {
id: banner
anchors.centerIn: parent
width: leftPane.width
closeEnabled: closeEnabled.checked
buyEnabled: buyEnabled.checked
receiveEnabled: receiveEnabled.checked
onBuyClicked: {
print("Buy clicked")
}
onReceiveClicked: {
print("Receive clicked")
}
onCloseBuy: {
buyEnabled.checked = false
}
onCloseReceive: {
receiveEnabled.checked = false
}
onAnyVisibleItemsChanged: {
print("Any visible: ", anyVisibleItems)
}
}
}
Pane {
id: rightPane
SplitView.fillHeight: true
SplitView.preferredWidth: 300
ColumnLayout {
anchors.fill: parent
Label {
text: "Any visible items"
}
Rectangle {
width: 30
height: 30
radius: 15
color: banner.anyVisibleItems ? "green" : "red"
}
CheckBox {
id: closeEnabled
text: "Show close button"
checked: true
}
CheckBox {
id: buyEnabled
text: "Show buy button"
checked: false
}
CheckBox {
id: receiveEnabled
text: "Show receive button"
checked: false
}
Item {
Layout.fillHeight: true
}
}
}
}

View File

@ -0,0 +1,76 @@
import QtQuick 2.15
import QtTest 1.15
import AppLayouts.Wallet.controls 1.0
Item {
id: root
width: 600
height: 400
Component {
id: bannerCardComponent
BannerCard {}
}
TestCase {
id: bannerCardTest
when: windowShown
property BannerCard componentUnderTest
function init() {
componentUnderTest = createTemporaryObject(bannerCardComponent, root)
}
function test_empty() {
compare(componentUnderTest.image, "")
compare(componentUnderTest.title, "")
compare(componentUnderTest.subTitle, "")
compare(componentUnderTest.closeEnabled, true)
}
function test_geometry() {
verify(componentUnderTest.height > 0)
verify(componentUnderTest.width > 0)
}
function test_hoverState() {
compare(componentUnderTest.hovered, false)
mouseMove(componentUnderTest)
compare(componentUnderTest.hovered, true)
const closeButton = findChild(componentUnderTest, "bannerCard_closeButton")
verify(!!closeButton)
verify(closeButton.visible)
verify(closeButton.width > 0)
verify(closeButton.height > 0)
mouseMove(closeButton, closeButton.width / 2, closeButton.height / 2)
compare(componentUnderTest.hovered, true)
}
function test_click() {
let clicked = false
componentUnderTest.clicked.connect(() => {
clicked = true
})
mouseClick(componentUnderTest)
compare(clicked, true)
clicked = false
const closeButton = findChild(componentUnderTest, "bannerCard_closeButton")
let closed = false
componentUnderTest.close.connect(() => {
closed = true
})
mouseClick(closeButton)
compare(closed, true)
compare(clicked, false)
}
}
}

View File

@ -0,0 +1,161 @@
import QtQuick 2.15
import QtTest 1.15
import AppLayouts.Wallet.panels 1.0
Item {
id: root
width: 600
height: 400
Component {
id: buyReceiveComponent
BuyReceiveBanner {
id: banner
anchors.fill: parent
readonly property SignalSpy buyClickedSpy: SignalSpy { target: banner; signalName: "buyClicked" }
readonly property SignalSpy receiveClickedSpy: SignalSpy { target: banner; signalName: "receiveClicked" }
readonly property SignalSpy closeBuySpy: SignalSpy { target: banner; signalName: "closeBuy" }
readonly property SignalSpy closeReceiveSpy: SignalSpy { target: banner; signalName: "closeReceive" }
}
}
TestCase {
id: buyReceiveBannerTest
when: windowShown
property BuyReceiveBanner componentUnderTest
function init() {
componentUnderTest = createTemporaryObject(buyReceiveComponent, root)
}
function test_empty() {
verify(componentUnderTest.closeEnabled)
verify(componentUnderTest.buyEnabled)
verify(componentUnderTest.receiveEnabled)
verify(componentUnderTest.anyVisibleItems)
}
function test_geometry() {
compare(componentUnderTest.width, root.width)
compare(componentUnderTest.height, root.height)
}
function test_buyGeometry() {
const buyCard = findChild(componentUnderTest, "buyCard")
verify(!!buyCard)
verify(buyCard.visible)
verify(buyCard.opacity > 0)
verify(buyCard.width > 0)
verify(buyCard.height > 0)
verify(buyCard.x >= 0)
verify(buyCard.x <= buyCard.parent.width - buyCard.width)
verify(buyCard.y >= 0)
verify(buyCard.y <= buyCard.parent.height - buyCard.height)
}
function test_receiveGeometry() {
const receiveCard = findChild(componentUnderTest, "receiveCard")
verify(!!receiveCard)
verify(receiveCard.visible)
verify(receiveCard.opacity > 0)
verify(receiveCard.width > 0)
verify(receiveCard.height > 0)
verify(receiveCard.x >= 0)
verify(receiveCard.x <= receiveCard.parent.width - receiveCard.width)
verify(receiveCard.y >= 0)
verify(receiveCard.y <= receiveCard.parent.height - receiveCard.height)
}
function test_clickBuy() {
const buyCard = findChild(componentUnderTest, "buyCard")
verify(!!buyCard)
compare(componentUnderTest.buyClickedSpy.count, 0)
mouseClick(buyCard)
compare(componentUnderTest.buyClickedSpy.count, 1)
}
function test_receiveClicked() {
const receiveCard = findChild(componentUnderTest, "receiveCard")
verify(!!receiveCard)
compare(componentUnderTest.receiveClickedSpy.count, 0)
mouseClick(receiveCard)
compare(componentUnderTest.receiveClickedSpy.count, 1)
}
function test_closeBuy() {
const buyCard = findChild(componentUnderTest, "buyCard")
verify(!!buyCard)
const closeButton = findChild(buyCard, "bannerCard_closeButton")
verify(!!closeButton)
verify(closeButton.visible)
compare(componentUnderTest.closeBuySpy.count, 0)
mouseClick(closeButton)
compare(componentUnderTest.closeBuySpy.count, 1)
}
function test_closeReceive() {
const receiveCard = findChild(componentUnderTest, "receiveCard")
verify(!!receiveCard)
const closeButton = findChild(receiveCard, "bannerCard_closeButton")
verify(!!closeButton)
verify(closeButton.visible)
compare(componentUnderTest.closeReceiveSpy.count, 0)
mouseClick(closeButton)
compare(componentUnderTest.closeReceiveSpy.count, 1)
}
function test_anyVisibleItemsChanged() {
compare(componentUnderTest.anyVisibleItems, true)
compare(componentUnderTest.buyEnabled, true)
compare(componentUnderTest.receiveEnabled, true)
componentUnderTest.buyEnabled = false
compare(componentUnderTest.anyVisibleItems, true)
componentUnderTest.receiveEnabled = false
tryVerify(() => componentUnderTest.anyVisibleItems, false)
}
function test_closeDisabled() {
const buyCard = findChild(componentUnderTest, "buyCard")
verify(!!buyCard)
const closeButton = findChild(buyCard, "bannerCard_closeButton")
verify(!!closeButton)
verify(closeButton.visible)
componentUnderTest.closeEnabled = false
verify(!closeButton.visible)
const receiveCard = findChild(componentUnderTest, "receiveCard")
verify(!!receiveCard)
const receiveCloseButton = findChild(receiveCard, "bannerCard_closeButton")
verify(!!receiveCloseButton)
verify(!receiveCloseButton.visible)
}
function test_hideBuy() {
const buyCard = findChild(componentUnderTest, "buyCard")
verify(!!buyCard)
verify(buyCard.visible)
componentUnderTest.buyEnabled = false
tryVerify(() => !buyCard.visible)
}
function test_hideReceive() {
const receiveCard = findChild(componentUnderTest, "receiveCard")
verify(!!receiveCard)
verify(receiveCard.visible)
componentUnderTest.receiveEnabled = false
tryVerify(() => !receiveCard.visible)
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

View File

@ -0,0 +1,99 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import QtGraphicalEffects 1.15
import StatusQ.Components 0.1
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import utils 1.0
Control {
id: root
property alias image: image.source
property alias title: title.text
property alias subTitle: subTitle.text
property alias closeEnabled: closeButton.visible
signal clicked()
signal close()
implicitHeight: 70
implicitWidth: 400
padding: Theme.halfPadding
leftPadding: 20
TapHandler {
acceptedButtons: Qt.LeftButton
enabled: !closeHandler.pressed
onTapped: {
root.clicked()
}
}
HoverHandler {
cursorShape: Qt.PointingHandCursor
}
background: Rectangle {
id: background
color: Theme.palette.background
radius: 12
border.width: 1
border.color: Theme.palette.baseColor2
layer.enabled: true
layer.effect: DropShadow {
horizontalOffset: 0
verticalOffset: 7
radius: 8
spread: root.hovered ? 0.3 : 0
color: Theme.palette.baseColor2
}
}
contentItem: RowLayout {
id: layout
spacing: Theme.padding
StatusImage {
id: image
Layout.preferredWidth: 36
Layout.preferredHeight: 36
}
ColumnLayout {
Layout.fillWidth: true
Layout.fillHeight: true
StatusBaseText {
id: title
Layout.fillWidth: true
color: Theme.palette.directColor1
font.pixelSize: Theme.additionalTextSize
font.weight: Font.Medium
elide: Text.ElideRight
}
StatusBaseText {
id: subTitle
Layout.fillWidth: true
color: Theme.palette.baseColor1
font.pixelSize: Theme.additionalTextSize
elide: Text.ElideRight
}
}
StatusIcon {
id: closeButton
objectName: "bannerCard_closeButton"
Layout.topMargin: 4
Layout.rightMargin: 4
Layout.alignment: Qt.AlignTop
Layout.preferredWidth: 16
Layout.preferredHeight: 16
icon: "close"
color: Theme.palette.baseColor1
TapHandler {
id: closeHandler
acceptedButtons: Qt.LeftButton
onTapped: {
root.close()
}
}
}
}
}

View File

@ -2,6 +2,7 @@ AccountHeaderGradient 1.0 AccountHeaderGradient.qml
ActivityFilterTagItem 1.0 ActivityFilterTagItem.qml
AssetSelector 1.0 AssetSelector.qml
AssetSelectorCompact 1.0 AssetSelectorCompact.qml
BannerCard 1.0 BannerCard.qml
BuyCryptoProvidersDelegate 1.0 BuyCryptoProvidersDelegate.qml
BuyCryptoProvidersLoadingDelegate 1.0 BuyCryptoProvidersLoadingDelegate.qml
CollectibleBalanceTag 1.0 CollectibleBalanceTag.qml

View File

@ -0,0 +1,72 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import AppLayouts.Wallet.controls 1.0
import StatusQ.Core.Theme 0.1
Control {
id: root
property bool closeEnabled: true
property bool buyEnabled: true
property bool receiveEnabled: true
readonly property bool anyVisibleItems: buyCard.visible || receiveCard.visible
signal buyClicked()
signal receiveClicked()
signal closeBuy()
signal closeReceive()
contentItem: RowLayout {
id: layout
spacing: Theme.padding
BannerCard {
id: buyCard
objectName: "buyCard"
Layout.fillWidth: true
Layout.preferredWidth: root.buyEnabled ? layout.width / layout.children.length : 0
title: qsTr("Ways to buy crypto")
subTitle: qsTr("Via card or bank transfer")
image: Theme.png("wallet/wallet-green")
closeEnabled: root.closeEnabled
visible: Layout.preferredWidth > 0
opacity: root.buyEnabled ? 1 : 0
onClose: root.closeBuy()
onClicked: root.buyClicked()
Behavior on opacity {
NumberAnimation { duration: 200; easing.type: Easing.InOutQuad }
}
Behavior on Layout.preferredWidth {
NumberAnimation { duration: 200; easing.type: Easing.InOutQuad }
}
}
BannerCard {
id: receiveCard
objectName: "receiveCard"
Layout.fillWidth: true
Layout.preferredWidth: root.receiveEnabled ? layout.width / layout.children.length : 0
title: qsTr("Receive crypto")
subTitle: qsTr("Deposit to your Wallet address")
image: Theme.png("wallet/flying-coin")
closeEnabled: root.closeEnabled
visible: Layout.preferredWidth > 0
opacity: root.receiveEnabled ? 1 : 0
onClose: root.closeReceive()
onClicked: root.receiveClicked()
Behavior on opacity {
NumberAnimation { duration: 200; easing.type: Easing.InOutQuad }
}
Behavior on Layout.preferredWidth {
NumberAnimation { duration: 200; easing.type: Easing.InOutQuad }
}
}
}
}

View File

@ -1,5 +1,6 @@
ActivityFilterPanel 1.0 ActivityFilterPanel.qml
BuyCryptoProvidersListPanel 1.0 BuyCryptoProvidersListPanel.qml
BuyReceiveBanner 1.0 BuyReceiveBanner.qml
ContractInfoButtonWithMenu 1.0 ContractInfoButtonWithMenu.qml
DAppsWorkflow 1.0 DAppsWorkflow.qml
ManageAssetsPanel 1.0 ManageAssetsPanel.qml