diff --git a/storybook/pages/DAppsWorkflowPage.qml b/storybook/pages/DAppsWorkflowPage.qml index 350c9e7e23..a67c922fcd 100644 --- a/storybook/pages/DAppsWorkflowPage.qml +++ b/storybook/pages/DAppsWorkflowPage.qml @@ -341,7 +341,7 @@ Item { function startTestCase() { d.activeTestCase = settings.testCase if(root.visible) { - dappsWorkflow.clicked() + dappsWorkflow.popup.open() } } diff --git a/storybook/pages/DappsComboBoxPage.qml b/storybook/pages/DappsComboBoxPage.qml new file mode 100644 index 0000000000..8bd2ceed87 --- /dev/null +++ b/storybook/pages/DappsComboBoxPage.qml @@ -0,0 +1,78 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 + +import AppLayouts.Wallet.controls 1.0 + +SplitView { + id: root + + width: 400 + height: 400 + + Item { + SplitView.fillWidth: true + SplitView.fillHeight: true + DappsComboBox { + id: connectedDappComboBox + anchors.centerIn: parent + model: emptyModelCheckbox.checked ? emptyModel : dappsModel + } + + ListModel { + id: emptyModel + } + + ListModel { + id: dappsModel + ListElement { + name: "Test dApp 1" + url: "https://dapp.test/1" + iconUrl: "https://se-sdk-dapp.vercel.app/assets/eip155:1.png" + } + ListElement { + name: "Test dApp 2" + url: "https://dapp.test/2" + iconUrl: "https://react-app.walletconnect.com/assets/eip155-1.png" + } + ListElement { + name: "Test dApp 3" + url: "https://dapp.test/3" + iconUrl: "https://react-app.walletconnect.com/assets/eip155-1.png" + } + ListElement { + name: "Test dApp 4 - very long name !!!!!!!!!!!!!!!!" + url: "https://dapp.test/4" + iconUrl: "https://react-app.walletconnect.com/assets/eip155-1.png" + } + ListElement { + name: "Test dApp 5 - very long url" + url: "https://dapp.test/very_long/url/unusual" + iconUrl: "https://react-app.walletconnect.com/assets/eip155-1.png" + } + ListElement { + name: "Test dApp 6" + url: "https://dapp.test/6" + iconUrl: "https://react-app.walletconnect.com/assets/eip155-1.png" + } + } + } + + Pane { + id: controls + SplitView.preferredWidth: 300 + SplitView.fillHeight: true + + ColumnLayout { + CheckBox { + id: emptyModelCheckbox + text: "Empty model" + checked: false + } + } + } +} + +// category: Controls + +// https://www.figma.com/design/HrmZp1y4S77QJezRFRl6ku/dApp-Interactions---Milestone-1?node-id=130-31949&t=hnzB58fTnEnx2z84-0 \ No newline at end of file diff --git a/storybook/qmlTests/main.cpp b/storybook/qmlTests/main.cpp index 96db3ae139..eb5b4c71ac 100644 --- a/storybook/qmlTests/main.cpp +++ b/storybook/qmlTests/main.cpp @@ -14,6 +14,8 @@ public slots: QGuiApplication::setOrganizationName(QStringLiteral("Status")); QGuiApplication::setOrganizationDomain(QStringLiteral("status.im")); + qputenv("QT_QUICK_CONTROLS_HOVER_ENABLED", QByteArrayLiteral("1")); + const QStringList additionalImportPaths { STATUSQ_MODULE_IMPORT_PATH, QML_IMPORT_ROOT + QStringLiteral("/../ui/app"), diff --git a/storybook/qmlTests/tests/tst_DAppsWorkflow.qml b/storybook/qmlTests/tests/tst_DAppsWorkflow.qml index c8922453af..b98e95d1c3 100644 --- a/storybook/qmlTests/tests/tst_DAppsWorkflow.qml +++ b/storybook/qmlTests/tests/tst_DAppsWorkflow.qml @@ -537,7 +537,7 @@ Item { // waitForRendering(controlUnderTest) // compare(dappsListReadySpy.count, 1, "expected dappsListReady signal to be emitted") - // let popup = findChild(controlUnderTest, "dappsPopup") + // let popup = findChild(controlUnderTest, "dappsListPopup") // verify(!!popup) // verify(popup.opened) @@ -553,7 +553,7 @@ Item { // mouseClick(controlUnderTest) // waitForRendering(controlUnderTest) - // let popup = findChild(controlUnderTest, "dappsPopup") + // let popup = findChild(controlUnderTest, "dappsListPopup") // verify(!!popup) // verify(popup.opened) diff --git a/storybook/qmlTests/tests/tst_DappsComboBox.qml b/storybook/qmlTests/tests/tst_DappsComboBox.qml new file mode 100644 index 0000000000..13efad2243 --- /dev/null +++ b/storybook/qmlTests/tests/tst_DappsComboBox.qml @@ -0,0 +1,100 @@ +import QtQuick 2.15 +import QtQml.Models 2.15 + +import QtTest 1.15 + +import AppLayouts.Wallet.controls 1.0 + +Item { + id: root + width: 600 + height: 400 + + Component { + id: componentUnderTest + DappsComboBox { + anchors.centerIn: parent + model: ListModel {} + } + } + + SignalSpy { + id: dappsListReadySpy + target: controlUnderTest + signalName: "dappsListReady" + } + + property DappsComboBox controlUnderTest: null + + TestCase { + name: "DappsComboBox" + when: windowShown + + function init() { + controlUnderTest = createTemporaryObject(componentUnderTest, root) + dappsListReadySpy.clear() + } + + function test_basicGeometry() { + verify(controlUnderTest.width > 0) + verify(controlUnderTest.height > 0) + } + + function test_showStatusIndicator() { + const indicator = findChild(controlUnderTest, "dappBadge") + compare(indicator.visible, false) + + controlUnderTest.model.append({name: "Test dApp 1", url: "https://dapp.test/1", iconUrl: "https://se-sdk-dapp.vercel.app/assets/eip155:1.png"}) + compare(indicator.visible, true) + + controlUnderTest.model.clear() + compare(indicator.visible, false) + } + + function test_dappIcon() { + const icon = findChild(controlUnderTest, "dappIcon") + compare(icon.icon, "dapp") + compare(icon.width, 16) + compare(icon.height, 16) + compare(icon.status, Image.Ready) + } + + function test_openingPopup() { + mouseClick(controlUnderTest) + const popup = findChild(controlUnderTest, "dappsListPopup") + compare(popup.visible, true) + compare(popup.x, controlUnderTest.width - popup.width) + compare(popup.y, controlUnderTest.height + 4) + compare(popup.width, 312) + verify(popup.height > 0) + compare(dappsListReadySpy.count, 1) + + const background = findChild(controlUnderTest, "dappsBackground") + compare(background.active, true) + + mouseClick(controlUnderTest) + compare(popup.visible, false) + compare(background.active, false) + } + + function test_hoverState() { + const background = findChild(controlUnderTest, "dappsBackground") + compare(background.active, false) + + mouseMove(controlUnderTest, controlUnderTest.width/2, controlUnderTest.height/2) + compare(background.active, true) + compare(controlUnderTest.hovered, true) + const dappTooltip = findChild(controlUnderTest, "dappTooltip") + wait(dappTooltip.delay + 50) + compare(dappTooltip.visible, true) + compare(dappTooltip.text, "dApp connections") + verify(dappTooltip.width > 0) + verify(dappTooltip.height > 0) + verify(dappTooltip.y > controlUnderTest.height) + + mouseMove(root) + compare(background.active, false) + compare(dappTooltip.visible, false) + } + } +} \ No newline at end of file diff --git a/ui/StatusQ/src/StatusQ/Core/Utils/ModelUtils.qml b/ui/StatusQ/src/StatusQ/Core/Utils/ModelUtils.qml index bf6a7f7000..166d5019ec 100644 --- a/ui/StatusQ/src/StatusQ/Core/Utils/ModelUtils.qml +++ b/ui/StatusQ/src/StatusQ/Core/Utils/ModelUtils.qml @@ -2,6 +2,7 @@ pragma Singleton import QtQuick 2.14 +import StatusQ 0.1 import StatusQ.Internal 0.1 as Internal QtObject { diff --git a/ui/app/AppLayouts/Wallet/controls/ConnectedDappsButton.qml b/ui/app/AppLayouts/Wallet/controls/ConnectedDappsButton.qml deleted file mode 100644 index 2e23c7f70c..0000000000 --- a/ui/app/AppLayouts/Wallet/controls/ConnectedDappsButton.qml +++ /dev/null @@ -1,27 +0,0 @@ -import QtQuick 2.15 -import QtQuick.Controls 2.15 -import QtQuick.Layouts 1.15 - -import shared.controls 1.0 - -import StatusQ.Core.Theme 0.1 -import StatusQ.Controls 0.1 - -StatusButton { - id: root - - implicitHeight: 38 - - size: StatusBaseButton.Size.Small - - borderColor: Theme.palette.directColor7 - normalColor: Theme.palette.transparent - hoverColor: Theme.palette.baseColor2 - textPosition: StatusBaseButton.TextPosition.Left - textColor: Theme.palette.baseColor1 - - icon.name: "dapp" - icon.height: 16 - icon.width: 16 - icon.color: hovered ? Theme.palette.directColor1 : Theme.palette.baseColor1 -} diff --git a/ui/app/AppLayouts/Wallet/controls/DAppDelegate.qml b/ui/app/AppLayouts/Wallet/controls/DAppDelegate.qml new file mode 100644 index 0000000000..68e3b96f74 --- /dev/null +++ b/ui/app/AppLayouts/Wallet/controls/DAppDelegate.qml @@ -0,0 +1,106 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 +import QtGraphicalEffects 1.15 + +import StatusQ 0.1 +import StatusQ.Components 0.1 +import StatusQ.Controls 0.1 +import StatusQ.Core 0.1 +import StatusQ.Core.Theme 0.1 + + +Item { + id: root + implicitHeight: 50 + + required property string name + required property string url + required property string iconUrl + + signal disconnectDapp() + + RowLayout { + anchors.fill: parent + anchors.margins: 8 + + Item { + Layout.preferredWidth: 32 + Layout.preferredHeight: 32 + + StatusImage { + id: iconImage + + anchors.fill: parent + + source: iconUrl + visible: !fallbackImage.visible + } + + StatusIcon { + id: fallbackImage + + anchors.fill: parent + + icon: "dapp" + color: Theme.palette.baseColor1 + + visible: iconImage.isLoading || iconImage.isError || !iconUrl + } + + layer.enabled: true + layer.effect: OpacityMask { + maskSource: Rectangle { + width: iconImage.width + height: iconImage.height + radius: width / 2 + visible: false + } + } + } + + ColumnLayout { + Layout.leftMargin: 12 + Layout.rightMargin: 12 + + StatusBaseText { + text: name + + Layout.fillWidth: true + + font.pixelSize: 13 + font.bold: true + + elide: Text.ElideRight + + clip: true + } + StatusBaseText { + text: url + + Layout.fillWidth: true + + font.pixelSize: 12 + color: Theme.palette.baseColor1 + + elide: Text.ElideRight + + clip: true + } + } + + // TODO #14588 - Show tooltip on hover "Disconnect dApp" + StatusRoundButton { + implicitWidth: 32 + implicitHeight: 32 + radius: width / 2 + + icon.name: "disconnect" + + onClicked: { + console.debug(`TODO #14755 - Disconnect ${name}`) + //root.disconnectDapp() + } + } + } +} \ No newline at end of file diff --git a/ui/app/AppLayouts/Wallet/controls/DappsComboBox.qml b/ui/app/AppLayouts/Wallet/controls/DappsComboBox.qml new file mode 100644 index 0000000000..af08743c03 --- /dev/null +++ b/ui/app/AppLayouts/Wallet/controls/DappsComboBox.qml @@ -0,0 +1,86 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 +import QtGraphicalEffects 1.15 + +import shared.controls 1.0 +import shared.popups.walletconnect 1.0 + +import StatusQ.Core 0.1 +import StatusQ.Core.Theme 0.1 +import StatusQ.Controls 0.1 +import StatusQ.Components 0.1 +import StatusQ.Components.private 0.1 as SQP + + +ComboBox { + id: root + + signal dappsListReady + signal pairDapp + + implicitHeight: 38 + implicitWidth: 38 + + background: SQP.StatusComboboxBackground { + objectName: "dappsBackground" + active: root.down || root.hovered + } + + indicator: null + + contentItem: Item { + objectName: "dappsContentItem" + StatusBadge { + objectName: "dappBadge" + anchors.top: parent.top + anchors.right: parent.right + anchors.margins: 5 + width: 6 + height: 6 + visible: root.delegateModel.count > 0 + } + + StatusIcon { + objectName: "dappIcon" + anchors.centerIn: parent + width: 16 + height: 16 + icon: "dapp" + color: Theme.palette.baseColor1 + } + } + + delegate: DAppDelegate { + width: ListView.view.width + } + + popup: DAppsListPopup { + objectName: "dappsListPopup" + + x: root.width - width + y: root.height + 4 + + closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent + + delegateModel: root.delegateModel + + onPairWCDapp: { + root.pairDapp() + this.close() + } + + onOpened: { + root.dappsListReady() + } + } + + StatusToolTip { + id: tooltip + objectName: "dappTooltip" + visible: root.hovered && !root.down + text: qsTr("dApp connections") + orientation: StatusToolTip.Orientation.Bottom + y: root.height + 14 + } +} diff --git a/ui/app/AppLayouts/Wallet/controls/qmldir b/ui/app/AppLayouts/Wallet/controls/qmldir index 74910c0c30..2d50e00b3a 100644 --- a/ui/app/AppLayouts/Wallet/controls/qmldir +++ b/ui/app/AppLayouts/Wallet/controls/qmldir @@ -14,8 +14,8 @@ MaxSendButton 1.0 MaxSendButton.qml InformationTileAssetDetails 1.0 InformationTileAssetDetails.qml StatusNetworkListItemTag 1.0 StatusNetworkListItemTag.qml CollectibleBalanceTag 1.0 CollectibleBalanceTag.qml -ConnectedDappsButton 1.0 ConnectedDappsButton.qml CollectibleLinksTags 1.0 CollectibleLinksTags.qml +DappsComboBox 1.0 DappsComboBox.qml SwapExchangeButton 1.0 SwapExchangeButton.qml EditSlippagePanel 1.0 EditSlippagePanel.qml TokenSelector 1.0 TokenSelector.qml diff --git a/ui/app/AppLayouts/Wallet/panels/DAppsWorkflow.qml b/ui/app/AppLayouts/Wallet/panels/DAppsWorkflow.qml index 478c9cbac7..dc958ffcd3 100644 --- a/ui/app/AppLayouts/Wallet/panels/DAppsWorkflow.qml +++ b/ui/app/AppLayouts/Wallet/panels/DAppsWorkflow.qml @@ -11,19 +11,18 @@ import AppLayouts.Wallet.services.dapps.types 1.0 import shared.stores 1.0 import utils 1.0 -ConnectedDappsButton { +DappsComboBox { id: root required property WalletConnectService wcService - signal dappsListReady() signal pairWCReady() - onClicked: { - dappsListLoader.active = true - } + model: root.wcService.dappsModel - highlighted: dappsListLoader.active + onPairDapp: { + pairWCLoader.active = true + } Loader { id: pairWCLoader @@ -47,34 +46,6 @@ ConnectedDappsButton { } } - Loader { - id: dappsListLoader - - active: false - - onLoaded: { - item.open() - root.dappsListReady() - } - - sourceComponent: DAppsListPopup { - visible: true - - model: root.wcService.dappsModel - - onPairWCDapp: { - pairWCLoader.active = true - this.close() - } - - onOpened: { - this.x = root.width - this.contentWidth - 2 * this.padding - this.y = root.height + 4 - } - onClosed: dappsListLoader.active = false - } - } - Loader { id: connectDappLoader diff --git a/ui/imports/shared/popups/walletconnect/DAppsListPopup.qml b/ui/imports/shared/popups/walletconnect/DAppsListPopup.qml index a22d7ced49..85851aa272 100644 --- a/ui/imports/shared/popups/walletconnect/DAppsListPopup.qml +++ b/ui/imports/shared/popups/walletconnect/DAppsListPopup.qml @@ -1,5 +1,6 @@ import QtQuick 2.15 import QtQuick.Controls 2.15 +import QtQml.Models 2.15 import QtQuick.Layouts 1.15 import QtGraphicalEffects 1.15 @@ -15,10 +16,12 @@ Popup { objectName: "dappsPopup" - required property var model + required property DelegateModel delegateModel signal pairWCDapp() + width: 312 + modal: false padding: 8 closePolicy: Popup.CloseOnEscape | Popup.CloseOnOutsideClick | Popup.CloseOnPressOutside @@ -41,11 +44,9 @@ Popup { } } - ColumnLayout { + contentItem: ColumnLayout { id: mainLayout - implicitWidth: 280 - spacing: 8 ShapeRectangle { @@ -84,13 +85,9 @@ Popup { Layout.preferredHeight: contentHeight Layout.maximumHeight: 280 - model: root.model + model: root.delegateModel visible: !listPlaceholder.visible - delegate: DAppDelegate { - implicitWidth: listView.width - } - ScrollBar.vertical: null } @@ -105,96 +102,4 @@ Popup { } } } - - component DAppDelegate: Item { - implicitHeight: 50 - - required property string name - required property string url - required property string iconUrl - - RowLayout { - anchors.fill: parent - anchors.margins: 8 - - Item { - Layout.preferredWidth: 32 - Layout.preferredHeight: 32 - - StatusImage { - id: iconImage - - anchors.fill: parent - - source: iconUrl - visible: !fallbackImage.visible - } - - StatusIcon { - id: fallbackImage - - anchors.fill: parent - - icon: "dapp" - color: Theme.palette.baseColor1 - - visible: iconImage.isLoading || iconImage.isError || !iconUrl - } - - layer.enabled: true - layer.effect: OpacityMask { - maskSource: Rectangle { - width: iconImage.width - height: iconImage.height - radius: width / 2 - visible: false - } - } - } - - ColumnLayout { - Layout.leftMargin: 12 - Layout.rightMargin: 12 - - StatusBaseText { - text: name - - Layout.fillWidth: true - - font.pixelSize: 13 - font.bold: true - - elide: Text.ElideRight - - clip: true - } - StatusBaseText { - text: url - - Layout.fillWidth: true - - font.pixelSize: 12 - color: Theme.palette.baseColor1 - - elide: Text.ElideRight - - clip: true - } - } - - // TODO #14588 - Show tooltip on hover "Disconnect dApp" - StatusRoundButton { - implicitWidth: 32 - implicitHeight: 32 - radius: width / 2 - - icon.name: "disconnect" - - onClicked: { - console.debug(`TODO #14755 - Disconnect ${name}`) - //root.disconnectDapp() - } - } - } - } }