From 1618e6ce0a9f729c5072b320fcc47ed4788bc245 Mon Sep 17 00:00:00 2001 From: Stefan Date: Mon, 27 May 2024 22:50:16 +0300 Subject: [PATCH] feat(dapps) implement basic DAppRequestModal I added it to storybook for testing. There is not integration with with the app yet. Updates: #14762 --- storybook/pages/ConnectDAppModalPage.qml | 3 +- storybook/pages/DAppRequestModalPage.qml | 164 ++++++++ .../src/StatusQ/Core/Theme/StatusColors.qml | 3 + .../StatusQ/Core/Theme/StatusDarkTheme.qml | 2 + .../StatusQ/Core/Theme/StatusLightTheme.qml | 2 + .../src/StatusQ/Core/Theme/ThemePalette.qml | 2 + ui/app/AppLayouts/Profile/controls/qmldir | 1 + .../popups/walletconnect/DAppRequestModal.qml | 376 ++++++++++++++++++ .../popups/walletconnect/assets/sign.svg | 5 + ui/imports/shared/popups/walletconnect/qmldir | 3 +- 10 files changed, 558 insertions(+), 3 deletions(-) create mode 100644 storybook/pages/DAppRequestModalPage.qml create mode 100644 ui/imports/shared/popups/walletconnect/DAppRequestModal.qml create mode 100644 ui/imports/shared/popups/walletconnect/assets/sign.svg diff --git a/storybook/pages/ConnectDAppModalPage.qml b/storybook/pages/ConnectDAppModalPage.qml index a8d8be700..3dfffc2af 100644 --- a/storybook/pages/ConnectDAppModalPage.qml +++ b/storybook/pages/ConnectDAppModalPage.qml @@ -72,8 +72,7 @@ Item { spacing: 8 - accounts: WalletAccountsModel{ - } + accounts: d.selectedAccount flatNetworks: SortFilterProxyModel { sourceModel: NetworksModel.flatNetworks diff --git a/storybook/pages/DAppRequestModalPage.qml b/storybook/pages/DAppRequestModalPage.qml new file mode 100644 index 000000000..acbdc5e4b --- /dev/null +++ b/storybook/pages/DAppRequestModalPage.qml @@ -0,0 +1,164 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 +import QtQml 2.15 +import Qt.labs.settings 1.0 +import QtTest 1.15 + +import StatusQ.Core 0.1 +import StatusQ.Core.Utils 0.1 +import StatusQ.Controls 0.1 +import StatusQ.Components 0.1 +import StatusQ.Core.Theme 0.1 +import StatusQ.Popups.Dialog 0.1 + +import Models 1.0 +import Storybook 1.0 + +import shared.popups.walletconnect 1.0 + +import SortFilterProxyModel 0.2 + +import AppLayouts.Wallet.panels 1.0 + +import utils 1.0 +import shared.stores 1.0 + +Item { + id: root + + function openModal() { + modal.openWith() + } + + // qml Splitter + SplitView { + anchors.fill: parent + + ColumnLayout { + SplitView.fillWidth: true + + Component.onCompleted: root.openModal() + + DAppRequestModal { + id: modal + + anchors.centerIn: parent + + spacing: 8 + + dappName: settings.dappName + dappUrl: settings.dappUrl + dappIcon: settings.dappIcon + signContent: JSON.stringify(d.signTestContent, null, 2) + maxFeesText: "1.82 EUR" + estimatedTimeText: "3-5 mins" + + account: d.selectedAccount + network: d.selectedNetwork + + onSign: { + console.log("Sign button clicked") + } + onReject: { + console.log("Reject button clicked") + } + } + + StatusButton { + id: openButton + + Layout.alignment: Qt.AlignHCenter + Layout.margins: 20 + + text: "Open DAppRequestModal" + + onClicked: root.openModal() + } + + ColumnLayout {} + } + + ColumnLayout { + id: optionsSpace + + TextField { + id: dappNameTextField + + text: settings.dappName + onTextChanged: settings.dappName = text + } + TextField { + id: dappUrlTextField + + text: settings.dappUrl + onTextChanged: settings.dappUrl = text + } + TextField { + id: dappIconTextField + + text: settings.dappIcon + onTextChanged: settings.dappIcon = text + } + TextField { + id: accountDisplayTextField + + text: settings.accountDisplay + onTextChanged: settings.accountDisplay = text + } + + Item { Layout.fillHeight: true } + } + } + + Settings { + id: settings + + property string dappName: "OpenSea" + property string dappUrl: "opensea.io" + property string dappIcon: "https://opensea.io/static/images/logos/opensea-logo.svg" + property string accountDisplay: "helloworld" + } + + QtObject { + id: d + + readonly property var accountsModel: WalletAccountsModel{} + readonly property var selectedAccount: accountsModel.data[0] + + readonly property var selectedNetwork: NetworksModel.flatNetworks.get(0) + + readonly property var signTestContent: { + "id": 1714038548266495, + "params": { + "chainld": "eip155:11155111", + "request": { + "expiryTimestamp": 1714038848, + "method": "eth_signTransaction", + "params": [ + { + "data": "0x", + "from": "0xE2d622C817878dA5143bBE06866ca8E35273Ba8", + "gasLimit": "0x5208", + "gasPrice": "0xa677ef31", + "nonce": "0x27", + "to": "0xE2d622C817878dA5143bBE06866ca8E35273Ba8a", + "value": "0x00" + } + ] + } + }, + "topic": "a0f85b23a1f3a540d85760a523963165fb92169d57320c", + "verifyContext": { + "verified": { + "isScam": false, + "origin": "https://react-app.walletconnect.com/", + "validation": "VALID", + "verifyUrl": "https://verify.walletconnect.com/" + } + } + } + } +} + +// category: Wallet diff --git a/ui/StatusQ/src/StatusQ/Core/Theme/StatusColors.qml b/ui/StatusQ/src/StatusQ/Core/Theme/StatusColors.qml index 079c3a0b3..468b822ef 100644 --- a/ui/StatusQ/src/StatusQ/Core/Theme/StatusColors.qml +++ b/ui/StatusQ/src/StatusQ/Core/Theme/StatusColors.qml @@ -83,5 +83,8 @@ QtObject { 'mossHovered': '#1E857B', 'brownHovered': '#6F2727', 'brown2Hovered': '#7C6926', + + 'lightDesktopBlue10': '#ECEFFB', + 'darkDesktopBlue10': '#273251', } } diff --git a/ui/StatusQ/src/StatusQ/Core/Theme/StatusDarkTheme.qml b/ui/StatusQ/src/StatusQ/Core/Theme/StatusDarkTheme.qml index 35b4b7662..fdb119515 100644 --- a/ui/StatusQ/src/StatusQ/Core/Theme/StatusDarkTheme.qml +++ b/ui/StatusQ/src/StatusQ/Core/Theme/StatusDarkTheme.qml @@ -74,6 +74,8 @@ ThemePalette { messageHighlightColor: getColor('blue4', 0.2) + desktopBlue10: getColor('darkDesktopBlue10') + userCustomizationColors: [ "#AAC6FF", "#887AF9", diff --git a/ui/StatusQ/src/StatusQ/Core/Theme/StatusLightTheme.qml b/ui/StatusQ/src/StatusQ/Core/Theme/StatusLightTheme.qml index 059782202..caa2c6c21 100644 --- a/ui/StatusQ/src/StatusQ/Core/Theme/StatusLightTheme.qml +++ b/ui/StatusQ/src/StatusQ/Core/Theme/StatusLightTheme.qml @@ -72,6 +72,8 @@ ThemePalette { messageHighlightColor: getColor('blue', 0.2) + desktopBlue10: getColor('lightDesktopBlue10') + userCustomizationColors: [ "#2946C4", "#887AF9", diff --git a/ui/StatusQ/src/StatusQ/Core/Theme/ThemePalette.qml b/ui/StatusQ/src/StatusQ/Core/Theme/ThemePalette.qml index aef203549..dbabbc493 100644 --- a/ui/StatusQ/src/StatusQ/Core/Theme/ThemePalette.qml +++ b/ui/StatusQ/src/StatusQ/Core/Theme/ThemePalette.qml @@ -183,6 +183,8 @@ QtObject { property color messageHighlightColor + property color desktopBlue10 + property var userCustomizationColors: [] property var identiconRingColors: [] diff --git a/ui/app/AppLayouts/Profile/controls/qmldir b/ui/app/AppLayouts/Profile/controls/qmldir index 29509efca..8f8f7b2bd 100644 --- a/ui/app/AppLayouts/Profile/controls/qmldir +++ b/ui/app/AppLayouts/Profile/controls/qmldir @@ -4,3 +4,4 @@ ShowcaseDelegate 1.0 ShowcaseDelegate.qml StaticSocialLinkInput 1.0 StaticSocialLinkInput.qml WalletAccountDelegate 1.0 WalletAccountDelegate.qml WalletKeyPairDelegate 1.0 WalletKeyPairDelegate.qml +WalletAccountDetailsKeypairItem 1.0 WalletAccountDetailsKeypairItem.qml \ No newline at end of file diff --git a/ui/imports/shared/popups/walletconnect/DAppRequestModal.qml b/ui/imports/shared/popups/walletconnect/DAppRequestModal.qml new file mode 100644 index 000000000..20d13841e --- /dev/null +++ b/ui/imports/shared/popups/walletconnect/DAppRequestModal.qml @@ -0,0 +1,376 @@ +import QtQuick 2.15 +import QtQuick.Layouts 1.15 +import QtQml.Models 2.15 + +import StatusQ.Core 0.1 +import StatusQ.Popups.Dialog 0.1 +import StatusQ.Controls 0.1 +import StatusQ.Components 0.1 +import StatusQ.Core.Theme 0.1 +import StatusQ.Core.Utils 0.1 as StatusQ + +import utils 1.0 + +StatusDialog { + id: root + + implicitWidth: 480 + + required property string dappName + required property string dappUrl + required property url dappIcon + required property string signContent + required property string maxFeesText + required property string estimatedTimeText + + required property var account + property var network: null + + signal sign() + signal reject() + + function openWith() { + root.open() + } + + title: qsTr("Sign request") + + padding: 20 + + contentItem: ColumnLayout { + spacing: 20 + clip: true + + IntentionPanel { + Layout.fillWidth: true + + dappName: root.dappName + dappIcon: root.dappIcon + account: root.account + signContent: root.signContent + } + + ContentPanel { + Layout.fillWidth: true + Layout.preferredHeight: 340 + } + + // TODO: externalize as a TargetPanel + ColumnLayout { + spacing: 8 + + StatusBaseText { + text: qsTr("Sign with") + font.pixelSize: 13 + color: Theme.palette.directColor1 + } + + // TODO #14762: implement proper control to display the accounts details + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 76 + + radius: 8 + border.width: 1 + border.color: Theme.palette.baseColor2 + + RowLayout { + spacing: 12 + anchors.fill: parent + anchors.margins: 16 + + StatusSmartIdenticon { + width: 40 + height: 40 + + asset: StatusAssetSettings { + color: Theme.palette.primaryColor1 + isImage: false + isLetterIdenticon: true + useAcronymForLetterIdenticon: false + emoji: root.account.emoji + } + } + + ColumnLayout { + Layout.alignment: Qt.AlignLeft + + StatusBaseText { + text: root.account.name + + Layout.alignment: Qt.AlignLeft + + font.pixelSize: 13 + } + StatusBaseText { + text: StatusQ.Utils.elideAndFormatWalletAddress(root.account.address, 6, 4) + + Layout.alignment: Qt.AlignLeft + + font.pixelSize: 13 + + color: Theme.palette.baseColor1 + } + } + + Item {Layout.fillWidth: true } + } + } + // TODO #14762: implement proper control to display the chain + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 76 + + visible: root.network !== null + + radius: 8 + border.width: 1 + border.color: Theme.palette.baseColor2 + + RowLayout { + spacing: 12 + anchors.fill: parent + anchors.margins: 16 + + StatusSmartIdenticon { + width: 40 + height: 40 + + asset: StatusAssetSettings { + isImage: true + name: !!root.network ? Style.svg("tiny/" + root.network.iconUrl) : "" + } + } + + StatusBaseText { + text: !!root.network ? root.network.chainName : "" + + Layout.alignment: Qt.AlignLeft + + font.pixelSize: 13 + } + Item {Layout.fillWidth: true } + } + } + } + } + + header: StatusDialogHeader { + leftComponent: Item { + width: 46 + height: 46 + + StatusSmartIdenticon { + anchors.fill: parent + anchors.margins: 3 + + asset: StatusAssetSettings { + width: 40 + height: 40 + bgRadius: bgWidth / 2 + imgIsIdenticon: false + isImage: true + useAcronymForLetterIdenticon: false + name: root.dappIcon + } + bridgeBadge.visible: true + bridgeBadge.width: 16 + bridgeBadge.height: 16 + bridgeBadge.image.source: "assets/sign.svg" + bridgeBadge.border.width: 3 + bridgeBadge.border.color: "transparent" + bridgeBadge.color: Theme.palette.miscColor1 + } + } + headline.title: qsTr("Sign request") + headline.subtitle: root.dappUrl + } + + footer: StatusDialogFooter { + id: footer + + leftButtons: ObjectModel { + MaxFeesDisplay {} + Item { + width: 20 + } + EstimatedTimeDisplay {} + } + + rightButtons: ObjectModel { + StatusButton { + height: 44 + text: qsTr("Reject") + + onClicked: { + root.reject() + } + } + StatusButton { + height: 44 + text: qsTr("Sign") + + onClicked: { + root.sign() + } + } + } + } + + component MaxFeesDisplay: ColumnLayout { + StatusBaseText { + text: qsTr("Max fees:") + font.pixelSize: 12 + color: Theme.palette.directColor1 + } + StatusBaseText { + text: root.maxFeesText + font.pixelSize: 16 + font.weight: Font.DemiBold + } + } + + component EstimatedTimeDisplay: ColumnLayout { + StatusBaseText { + text: qsTr("Est. time:") + font.pixelSize: 12 + color: Theme.palette.directColor1 + } + StatusBaseText { + text: root.estimatedTimeText + font.pixelSize: 16 + font.weight: Font.DemiBold + } + } + + component IntentionPanel: ColumnLayout { + spacing: 8 + + required property string dappName + required property url dappIcon + required property var account + required property string signContent + + // Icons + Item { + Layout.fillWidth: true + Layout.preferredHeight: 40 + Layout.alignment: Qt.AlignHCenter + Layout.bottomMargin: 8 + + StatusRoundedImage { + id: dappIconComponent + + width: height + height: parent.height + + anchors.horizontalCenter: parent.horizontalCenter + anchors.horizontalCenterOffset: -16 + anchors.verticalCenter: parent.verticalCenter + + image.source: root.dappIcon + } + StatusRoundIcon { + anchors.horizontalCenter: parent.horizontalCenter + anchors.horizontalCenterOffset: 16 + anchors.verticalCenter: parent.verticalCenter + + asset: StatusAssetSettings { + width: 24 + height: 24 + color: Theme.palette.primaryColor1 + bgWidth: 40 + bgHeight: 40 + bgColor: Theme.palette.desktopBlue10 + bgRadius: bgWidth / 2 + bgBorderWidth: 2 + bgBorderColor: Theme.palette.statusAppLayout.backgroundColor + source: "assets/sign.svg" + } + } + } + + // Names and intentions + StatusBaseText { + text: qsTr("%1 wants you to sign this transaction with %2").arg(dappName).arg(account.name) + + Layout.preferredWidth: 400 + Layout.alignment: Qt.AlignHCenter + + font.pixelSize: 15 + font.weight: Font.DemiBold + + wrapMode: Text.WordWrap + horizontalAlignment: Text.AlignHCenter + } + + // TODO #14762: externalize as a InfoPill and merge base implementation with + // the existing IssuePill reusable component + Rectangle { + Layout.preferredWidth: operationStatusLayout.implicitWidth + 24 + Layout.preferredHeight: operationStatusLayout.implicitHeight + 14 + + Layout.alignment: Qt.AlignHCenter + + visible: true + + border.color: Theme.palette.successColor2 + border.width: 1 + color: "transparent" + radius: height / 2 + + RowLayout { + id: operationStatusLayout + + spacing: 8 + + anchors.centerIn: parent + + StatusIcon { + Layout.preferredWidth: 16 + Layout.preferredHeight: 16 + + visible: true + + color: Theme.palette.directColor1 + icon: "info" + } + + StatusBaseText { + text: qsTr("Only sign if you trust the dApp") + + font.pixelSize: 12 + color: Theme.palette.directColor1 + } + } + } + } + + component ContentPanel: Rectangle { + id: contentPanelRect + border.width: 1 + border.color: Theme.palette.baseColor2 + color: "transparent" + radius: 8 + + StatusScrollView { + id: contentScrollView + anchors.fill: parent + + contentWidth: availableWidth + contentHeight: contentText.implicitHeight + + StatusBaseText { + id: contentText + anchors.fill: parent + anchors.margins: 20 + + width: contentScrollView.availableWidth + + text: signContent + + wrapMode: Text.WrapAnywhere + } + } + } +} diff --git a/ui/imports/shared/popups/walletconnect/assets/sign.svg b/ui/imports/shared/popups/walletconnect/assets/sign.svg new file mode 100644 index 000000000..98927a935 --- /dev/null +++ b/ui/imports/shared/popups/walletconnect/assets/sign.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/ui/imports/shared/popups/walletconnect/qmldir b/ui/imports/shared/popups/walletconnect/qmldir index 44ca44dcb..57b65d497 100644 --- a/ui/imports/shared/popups/walletconnect/qmldir +++ b/ui/imports/shared/popups/walletconnect/qmldir @@ -1,3 +1,4 @@ PairWCModal 1.0 PairWCModal.qml DAppsListPopup 1.0 DAppsListPopup.qml -ConnectDAppModal 1.0 ConnectDAppModal.qml \ No newline at end of file +ConnectDAppModal 1.0 ConnectDAppModal.qml +DAppRequestModal 1.0 DAppRequestModal.qml \ No newline at end of file