diff --git a/storybook/pages/SendSignModalPage.qml b/storybook/pages/SendSignModalPage.qml new file mode 100644 index 00000000000..ffc7470197b --- /dev/null +++ b/storybook/pages/SendSignModalPage.qml @@ -0,0 +1,228 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 + +import StatusQ 0.1 +import StatusQ.Core.Utils 0.1 +import StatusQ.Core.Theme 0.1 + +import Storybook 1.0 +import Models 1.0 + +import AppLayouts.Wallet.popups.simpleSend 1.0 + +import utils 1.0 + +SplitView { + id: root + + Logs { id: logs } + + orientation: Qt.Horizontal + + property var dialog + + function createAndOpenDialog() { + dialog = dlgComponent.createObject(popupBg) + dialog.open() + } + + Component.onCompleted: createAndOpenDialog() + + QtObject { + id: priv + + readonly property var accountsModel: WalletAccountsModel {} + readonly property var selectedAccount: selectedAccountEntry.item + + readonly property var networksModel: NetworksModel.flatNetworks + readonly property var selectedNetwork: selectedNetworkEntry.item + } + + ModelEntry { + id: selectedAccountEntry + sourceModel: priv.accountsModel + key: "address" + value: ctrlAccount.currentValue + } + + ModelEntry { + id: selectedNetworkEntry + sourceModel: priv.networksModel + key: "chainId" + value: ctrlNetwork.currentValue + } + + Item { + SplitView.fillWidth: true + SplitView.fillHeight: true + + PopupBackground { + id: popupBg + anchors.fill: parent + + Button { + anchors.centerIn: parent + text: "Reopen" + + onClicked: createAndOpenDialog() + } + + Component { + id: dlgComponent + SendSignModal { + anchors.centerIn: parent + destroyOnClose: true + modal: false + + formatBigNumber: (number, symbol, noSymbolOption) => parseFloat(number).toLocaleString(Qt.locale(), 'f', 2) + + (noSymbolOption ? "" : " " + (symbol || Qt.locale().currencySymbol(Locale.CurrencyIsoCode))) + + fromTokenSymbol: ctrlFromSymbol.text + fromTokenAmount: ctrlFromAmount.text + fromTokenContractAddress: "0x6B175474E89094C44Da98b954EedeAC495271d0F" + + accountName: priv.selectedAccount.name + accountAddress: priv.selectedAccount.address + accountEmoji: priv.selectedAccount.emoji + accountColor: Utils.getColorForId(priv.selectedAccount.colorId) + + recipientAddress: ctrlRecipient.text + + networkShortName: priv.selectedNetwork.shortName + networkName: priv.selectedNetwork.chainName + networkIconPath: Theme.svg(priv.selectedNetwork.iconUrl) + networkBlockExplorerUrl: priv.selectedNetwork.blockExplorerURL + + fiatFees: formatBigNumber(42.542567, "EUR") + cryptoFees: formatBigNumber(0.06, "ETH") + estimatedTime: qsTr("> 5 minutes") + + isCollectible: isCollectibleCheckbox.checked + collectibleContractAddress: !!collectibleComboBox.currentCollectible ? + collectibleComboBox.currentCollectible.contractAddress: "" + collectibleTokenId: !!collectibleComboBox.currentCollectible ? + collectibleComboBox.currentCollectible.tokenId: "" + collectibleName: !!collectibleComboBox.currentCollectible ? + collectibleComboBox.currentCollectible.name: "" + collectibleBackgroundColor: !!collectibleComboBox.currentCollectible ? + collectibleComboBox.currentCollectible.backgroundColor: "" + collectibleIsMetadataValid: !!collectibleComboBox.currentCollectible ? + collectibleComboBox.currentCollectible.isMetadataValid : false + collectibleMediaUrl: !!collectibleComboBox.currentCollectible ? + collectibleComboBox.currentCollectible.mediaUrl ?? "" : "" + collectibleMediaType: "" + collectibleFallbackImageUrl:!!collectibleComboBox.currentCollectible ? + collectibleComboBox.currentCollectible.imageUrl : "" + + loginType: ctrlLoginType.currentIndex + + feesLoading: ctrlLoading.checked + + expirationSeconds: !!ctrlExpiration.text && parseInt(ctrlExpiration.text) ? parseInt(ctrlExpiration.text) : 0 + onExpirationSecondsChanged: requestTimestamp = new Date() + + fnGetOpenSeaExplorerUrl: function(networkShortName) { + return "%1/assets/%2".arg(Constants.openseaExplorerLinks.mainnetLink).arg(Constants.openseaExplorerLinks.ethereum) + } + + onAccepted: logs.logEvent("accepted") + onRejected: logs.logEvent("rejected") + onClosed: logs.logEvent("closed") + } + } + } + } + + LogsAndControlsPanel { + SplitView.minimumWidth: 250 + SplitView.preferredWidth: 250 + + logsView.logText: logs.logText + + ColumnLayout { + Layout.fillWidth: true + TextField { + Layout.fillWidth: true + id: ctrlFromSymbol + text: "DAI" + placeholderText: "From symbol" + } + CheckBox { + id: isCollectibleCheckbox + text:"is collectible" + } + ComboBox { + id: collectibleComboBox + property var currentCollectible + Layout.fillWidth: true + textRole: "name" + model: ManageCollectiblesModel {} + currentIndex: 0 + onCurrentIndexChanged: { + currentCollectible = ModelUtils.get(model, collectibleComboBox.currentIndex) + } + enabled: isCollectibleCheckbox.checked + } + Text { + text: "Selected Send Amount" + } + TextField { + Layout.fillWidth: true + id: ctrlFromAmount + text: "100" + placeholderText: "From amount" + } + Text { + text: "Selected From Account" + } + ComboBox { + id: ctrlAccount + textRole: "name" + valueRole: "address" + model: priv.accountsModel + currentIndex: 0 + } + + TextField { + Layout.fillWidth: true + id: ctrlRecipient + text: "0xA858DDc0445d8131daC4d1DE01f834ffcbA52Ef1" + placeholderText: "Selected recipient" + } + + Text { + text: "Selected Network" + } + ComboBox { + id: ctrlNetwork + textRole: "chainName" + valueRole: "chainId" + model: priv.networksModel + currentIndex: 0 + } + + Switch { + id: ctrlLoading + text: "Fees loading" + } + + Text { + text: "Login Type" + } + ComboBox { + id: ctrlLoginType + model: Constants.authenticationIconByType + } + + TextField { + id: ctrlExpiration + placeholderText: "Expiration in seconds" + } + } + } +} + +// category: Popups + +// https://www.figma.com/design/FkFClTCYKf83RJWoifWgoX/Wallet-v2?node-id=25214-40565&m=dev diff --git a/storybook/qmlTests/tests/tst_SendSignModal.qml b/storybook/qmlTests/tests/tst_SendSignModal.qml new file mode 100644 index 00000000000..8e04c3c708e --- /dev/null +++ b/storybook/qmlTests/tests/tst_SendSignModal.qml @@ -0,0 +1,530 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtTest 1.15 +import QtQml 2.15 + +import Models 1.0 + +import StatusQ.Core.Utils 0.1 as SQUtils +import StatusQ.Core.Theme 0.1 + +import AppLayouts.Wallet.popups.simpleSend 1.0 + +import utils 1.0 + +Item { + id: root + width: 600 + height: 400 + + Component { + id: componentUnderTest + SendSignModal { + anchors.centerIn: parent + + formatBigNumber: (number, symbol, noSymbolOption) => parseFloat(number).toLocaleString(Qt.locale(), 'f', 2) + (noSymbolOption ? "" : " " + symbol) + + fromTokenSymbol: "DAI" + fromTokenAmount: "100.07" + fromTokenContractAddress: "0x6B175474E89094C44Da98b954EedeAC495271d0F" + + accountName: "Hot wallet (generated)" + accountAddress: "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8881" + accountEmoji: "🚗" + accountColor: Utils.getColorForId(Constants.walletAccountColors.primary) + + recipientAddress: "0xA858DDc0445d8131daC4d1DE01f834ffcbA52Ef1" + + networkShortName: Constants.networkShortChainNames.mainnet + networkName: "Mainnet" + networkIconPath: Theme.svg("network/Network=Ethereum") + networkBlockExplorerUrl: "https://etherscan.io/" + + fiatFees: "1.54 EUR" + cryptoFees: "0.001 ETH" + estimatedTime: qsTr("> 5 minutes") + + loginType: Constants.LoginType.Password + + isCollectible: false + collectibleContractAddress: "" + collectibleTokenId: "" + collectibleName: "" + collectibleBackgroundColor: "" + collectibleIsMetadataValid: false + collectibleMediaUrl: "" + collectibleMediaType: "" + collectibleFallbackImageUrl: "" + + fnGetOpenSeaExplorerUrl: function(networkShortName) { + return "%1/assets/%2".arg(Constants.openseaExplorerLinks.mainnetLink).arg(Constants.openseaExplorerLinks.ethereum) + } + } + } + + SignalSpy { + id: signalSpyAccepted + target: controlUnderTest + signalName: "accepted" + } + + SignalSpy { + id: signalSpyRejected + target: controlUnderTest + signalName: "rejected" + } + + SignalSpy { + id: signalSpyOpenLink + target: Global + signalName: "openLinkWithConfirmation" + } + + property SendSignModal controlUnderTest: null + + TestCase { + name: "SendSignModal" + when: windowShown + + function init() { + controlUnderTest = createTemporaryObject(componentUnderTest, root) + signalSpyAccepted.clear() + signalSpyRejected.clear() + signalSpyOpenLink.clear() + } + + function test_basicGeometry() { + verify(!!controlUnderTest) + verify(controlUnderTest.width > 0) + verify(controlUnderTest.height > 0) + } + + function test_fromToProps() { + verify(!!controlUnderTest) + controlUnderTest.fromTokenSymbol = "DAI" + controlUnderTest.fromTokenAmount = "1000.123456789" + controlUnderTest.fromTokenContractAddress = "Oxdeadbeef" + + // title + compare(controlUnderTest.title, qsTr("Sign Send")) + + // subtitle + compare(controlUnderTest.subtitle, qsTr("%1 to %2").arg(controlUnderTest.formatBigNumber(controlUnderTest.fromTokenAmount, controlUnderTest.fromTokenSymbol)) + .arg(SQUtils.Utils.elideAndFormatWalletAddress(controlUnderTest.recipientAddress))) + + compare(controlUnderTest.gradientColor, controlUnderTest.accountColor) + + // info tag + compare(controlUnderTest.infoTagText, qsTr("Review all details before signing")) + + // info box + const headerText = findChild(controlUnderTest.contentItem, "headerText") + verify(!!headerText) + compare(headerText.text, qsTr("Send %1 to %2 on %3"). + arg(controlUnderTest.formatBigNumber(controlUnderTest.fromTokenAmount, controlUnderTest.fromTokenSymbol)). + arg(SQUtils.Utils.elideAndFormatWalletAddress(controlUnderTest.recipientAddress)).arg(controlUnderTest.networkName)) + const fromImage = findChild(controlUnderTest.contentItem, "fromImageIdenticon") + verify(!!fromImage) + compare(fromImage.asset.name, "filled-account") + compare(fromImage.asset.emoji, controlUnderTest.accountEmoji) + compare(fromImage.asset.color, controlUnderTest.accountColor) + compare(fromImage.asset.isLetterIdenticon, !!controlUnderTest.accountEmoji) + + const toImage = findChild(controlUnderTest.contentItem, "toImageIdenticon") + verify(!!toImage) + compare(toImage.asset.name, Constants.tokenIcon(controlUnderTest.fromTokenSymbol)) + + // send box + const sendBox = findChild(controlUnderTest.contentItem, "sendAssetBox") + verify(!!sendBox) + compare(sendBox.caption, qsTr("Send")) + compare(sendBox.primaryText, "1,000.12 DAI") + compare(sendBox.secondaryText, SQUtils.Utils.elideAndFormatWalletAddress(controlUnderTest.fromTokenContractAddress)) + } + + function test_tokenContextmenu() { + verify(!!controlUnderTest) + + controlUnderTest.open() + + // send box + const sendBox = findChild(controlUnderTest.contentItem, "sendAssetBox") + verify(!!sendBox) + + const contractInfoButtonWithMenu = findChild(sendBox, "contractInfoButtonWithMenu") + verify(!!contractInfoButtonWithMenu) + + const contextMenu = findChild(contractInfoButtonWithMenu, "moreMenu") + verify(!!contextMenu) + verify(!contextMenu.opened) + + contractInfoButtonWithMenu.clicked(0) + verify(contextMenu.opened) + + compare(contextMenu.contentModel.count, 2) + + const externalLink = findChild(contextMenu, "externalLink") + verify(!!externalLink) + compare(externalLink.text, !!controlUnderTest.fromTokenSymbol ? + qsTr("View %1 %2 contract address on %3").arg(controlUnderTest.networkName).arg(controlUnderTest.fromTokenSymbol).arg("Etherscan") + : qsTr("View %1 contract address on %2").arg(controlUnderTest.networkName).arg("Etherscan")) + compare(externalLink.icon.name, "external-link") + externalLink.triggered() + tryCompare(signalSpyOpenLink, "count", 1) + compare(signalSpyOpenLink.signalArguments[0][0], + "%1/%2/%3".arg(controlUnderTest.networkBlockExplorerUrl).arg(Constants.networkExplorerLinks.addressPath).arg(controlUnderTest.fromTokenContractAddress)) + verify(!contextMenu.opened) + + contractInfoButtonWithMenu.clicked(0) + verify(contextMenu.opened) + + const copyButton = findChild(contextMenu, "copyButton") + verify(!!copyButton) + compare(copyButton.text, qsTr("Copy contract address")) + compare(copyButton.icon.name, "copy") + copyButton.triggered() + verify(contextMenu.opened) + } + + function test_collectible_data() { + return [ + { + contractAddress: "0x216b4b4ba9f3e719726886d34a177484278bfcae", + tokenId: "403", + name: "Punx not dead!", + backgroundColor: "ivory", + isMetadataValid: true, + mediaUrl: "https://i.seadn.io/gcs/files/4a875f997063f4f3772190852c1c44f0.png?w=128&auto=format", + mediaType: "image", + fallbackImageUrl: "https://i.seadn.io/gcs/files/4a875f997063f4f3772190852c1c44f0.png?w=128&auto=format" + }, + { + contractAddress: "0x216b4b4ba9f3e719726886d34a177484278bfcae", + tokenId: "403", + name: "", + backgroundColor: "ivory", + isMetadataValid: true, + mediaUrl: "", + mediaType: "", + fallbackImageUrl: "" + } + ] + } + + function test_collectible(data) { + verify(!!controlUnderTest) + controlUnderTest.open() + + verify(!controlUnderTest.isCollectible) + + const sendCollectibleBox = findChild(controlUnderTest.contentItem, "sendCollectibleBox") + verify(!!sendCollectibleBox) + verify(!sendCollectibleBox.visible) + + // send box + const sendAssetBox = findChild(controlUnderTest.contentItem, "sendAssetBox") + verify(!!sendAssetBox) + verify(sendAssetBox.visible) + + controlUnderTest.isCollectible = true + verify(sendCollectibleBox.visible) + verify(!sendAssetBox.visible) + controlUnderTest.collectibleContractAddress = data.contractAddress + controlUnderTest.collectibleTokenId = data.tokenId + controlUnderTest.collectibleName = data.name + controlUnderTest.collectibleBackgroundColor = data.backgroundColor + controlUnderTest.collectibleIsMetadataValid = data.isMetadataValid + controlUnderTest.collectibleMediaUrl = data.mediaUrl + controlUnderTest.collectibleMediaType = data.mediaType + controlUnderTest.collectibleFallbackImageUrl = data.fallbackImageUrl + + // title + compare(controlUnderTest.title, qsTr("Sign Send")) + + // subtitle + compare(controlUnderTest.subtitle, qsTr("%1 to %2").arg(data.name) + .arg(SQUtils.Utils.elideAndFormatWalletAddress(controlUnderTest.recipientAddress))) + + compare(controlUnderTest.gradientColor, controlUnderTest.accountColor) + + // info tag + compare(controlUnderTest.infoTagText, qsTr("Review all details before signing")) + + // info box + const headerText = findChild(controlUnderTest.contentItem, "headerText") + verify(!!headerText) + compare(headerText.text, qsTr("Send %1 to %2 on %3"). + arg(data.name). + arg(SQUtils.Utils.elideAndFormatWalletAddress(controlUnderTest.recipientAddress)). + arg(controlUnderTest.networkName)) + const collectibleMedia = findChild(controlUnderTest.contentItem, "collectibleMedia") + verify(!!collectibleMedia) + compare(collectibleMedia.width, 120) + compare(collectibleMedia.height, 120) + compare(collectibleMedia.radius, 12) + compare(collectibleMedia.mediaUrl, data.mediaUrl) + + if(collectibleMedia.mediaUrl && !collectibleMedia.fallbackImageUrl) { + const loadingErrorComponent = findChild(collectibleMedia, "loadingErrorComponent") + verify(!!loadingErrorComponent) + compare(loadingErrorComponent.icon, "help") + } + + const loadingComponent = findChild(collectibleMedia, "loadingComponent") + verify(!!loadingComponent) + verify(loadingComponent.visible) + + const fromAccountSmartIdenticon = findChild(controlUnderTest.contentItem, "fromAccountSmartIdenticon") + verify(!!fromAccountSmartIdenticon) + compare(fromAccountSmartIdenticon.asset.name, "filled-account") + compare(fromAccountSmartIdenticon.asset.emoji, controlUnderTest.accountEmoji) + compare(fromAccountSmartIdenticon.asset.color, controlUnderTest.accountColor) + compare(fromAccountSmartIdenticon.asset.isLetterIdenticon, !!controlUnderTest.accountEmoji) + + // send collectible box + const collectibleCaption = findChild(controlUnderTest.contentItem, "collectibleCaption") + verify(!!collectibleCaption) + compare(collectibleCaption.text, qsTr("Send")) + const primaryText = findChild(sendCollectibleBox, "primaryText") + verify(!!primaryText) + compare(primaryText.text, !!data.name ? data.name: qsTr("Unknown")) + const secondaryText = findChild(sendCollectibleBox, "secondaryText") + verify(!!secondaryText) + compare(secondaryText.text, data.tokenId) + const smallCollectibleMedia = findChild(sendCollectibleBox, "collectibleMedia") + verify(!!smallCollectibleMedia) + compare(smallCollectibleMedia.width, 40) + compare(smallCollectibleMedia.height, 40) + compare(smallCollectibleMedia.radius, 4) + compare(smallCollectibleMedia.mediaUrl, "") + compare(smallCollectibleMedia.fallbackImageUrl, data.fallbackImageUrl) + if(smallCollectibleMedia.mediaUrl && !smallCollectibleMedia.fallbackImageUrl) { + const loadingErrorComponent = findChild(smallCollectibleMedia, "loadingErrorComponent") + verify(!!loadingErrorComponent) + compare(loadingErrorComponent.icon, "help") + } + + // collectible context menu + const moreMenu = findChild(sendCollectibleBox, "moreMenu") + verify(!!moreMenu) + + const openSeaExternalLink = findChild(moreMenu, "openSeaExternalLink") + verify(!!openSeaExternalLink) + compare(openSeaExternalLink.text, qsTr("View collectible on OpenSea")) + compare(openSeaExternalLink.icon.name, "external-link") + openSeaExternalLink.triggered() + tryCompare(signalSpyOpenLink, "count", 1) + compare(signalSpyOpenLink.signalArguments[0][0], + "%1/%2/%3".arg(controlUnderTest.fnGetOpenSeaExplorerUrl(controlUnderTest.networkShortName)). + arg(data.contractAddress).arg(data.tokenId)) + + const blockchainExternalLink = findChild(moreMenu, "blockchainExternalLink") + verify(!!blockchainExternalLink) + compare(blockchainExternalLink.text, qsTr("View collectible on Etherscan")) + compare(blockchainExternalLink.icon.name, "external-link") + blockchainExternalLink.triggered() + tryCompare(signalSpyOpenLink, "count", 2) + compare(signalSpyOpenLink.signalArguments[1][0], + "%1/nft/%2/%3".arg("Etherscan").arg(data.contractAddress).arg(data.tokenId)) + + const copyButton = findChild(moreMenu, "copyButton") + verify(!!copyButton) + compare(copyButton.text, qsTr("Copy Etherscan collectible address")) + compare(copyButton.icon.name, "copy") + } + + function test_recpientInfo() { + verify(!!controlUnderTest) + + // account box + const recipientBox = findChild(controlUnderTest.contentItem, "recipientBox") + verify(!!recipientBox) + + compare(recipientBox.caption, qsTr("To")) + compare(recipientBox.primaryText, controlUnderTest.recipientAddress) + compare(recipientBox.asset.name, "address") + verify(!recipientBox.asset.isLetterIdenticon) + verify(!recipientBox.asset.isImage) + } + + function test_recpientContextMenu() { + verify(!!controlUnderTest) + + controlUnderTest.open() + + // recipient box + const recipientBox = findChild(controlUnderTest.contentItem, "recipientBox") + verify(!!recipientBox) + + const recipientInfoButtonWithMenu = findChild(recipientBox, "recipientInfoButtonWithMenu") + verify(!!recipientInfoButtonWithMenu) + + const contextMenu = findChild(recipientInfoButtonWithMenu, "moreMenu") + verify(!!contextMenu) + verify(!contextMenu.opened) + + recipientInfoButtonWithMenu.clicked(0) + verify(contextMenu.opened) + + compare(contextMenu.contentModel.count, 2) + + const externalLink = findChild(contextMenu, "externalLink") + verify(!!externalLink) + compare(externalLink.text, qsTr("View receiver address on Etherscan")) + compare(externalLink.icon.name, "external-link") + externalLink.triggered() + tryCompare(signalSpyOpenLink, "count", 1) + compare(signalSpyOpenLink.signalArguments[0][0], + "%1/%2/%3".arg(controlUnderTest.networkBlockExplorerUrl).arg(Constants.networkExplorerLinks.addressPath).arg(controlUnderTest.recipientAddress)) + verify(!contextMenu.opened) + + recipientInfoButtonWithMenu.clicked(0) + verify(contextMenu.opened) + + const copyButton = findChild(contextMenu, "copyButton") + verify(!!copyButton) + compare(copyButton.text, qsTr("Copy receiver address")) + compare(copyButton.icon.name, "copy") + copyButton.triggered() + verify(contextMenu.opened) + } + + function test_accountInfo() { + verify(!!controlUnderTest) + + // account box + const accountBox = findChild(controlUnderTest.contentItem, "accountBox") + verify(!!accountBox) + + compare(accountBox.caption, qsTr("From")) + compare(accountBox.primaryText, controlUnderTest.accountName) + compare(accountBox.secondaryText, SQUtils.Utils.elideAndFormatWalletAddress(controlUnderTest.accountAddress)) + compare(accountBox.asset.emoji, controlUnderTest.accountEmoji) + compare(accountBox.asset.color, controlUnderTest.accountColor) + } + + function test_networkInfo() { + verify(!!controlUnderTest) + + // network box + const networkBox = findChild(controlUnderTest.contentItem, "networkBox") + verify(!!networkBox) + + compare(networkBox.caption, qsTr("Network")) + compare(networkBox.primaryText, controlUnderTest.networkName) + compare(networkBox.icon, controlUnderTest.networkIconPath) + } + + function test_feesInfo() { + verify(!!controlUnderTest) + + // fees box + const feesBox = findChild(controlUnderTest.contentItem, "feesBox") + verify(!!feesBox) + + compare(feesBox.caption, qsTr("Fees")) + compare(feesBox.primaryText, qsTr("Max. fees on %1").arg(controlUnderTest.networkName)) + + const fiatFeesText = findChild(feesBox, "fiatFeesText") + verify(!!fiatFeesText) + compare(fiatFeesText.text, controlUnderTest.fiatFees) + + const cryptoFeesText = findChild(feesBox, "cryptoFeesText") + verify(!!cryptoFeesText) + compare(cryptoFeesText.text, controlUnderTest.cryptoFees) + } + + function test_loginType_data() { + return [ + { tag: "password", loginType: Constants.LoginType.Password, iconName: "password" }, + { tag: "touchId", loginType: Constants.LoginType.Biometrics, iconName: "touch-id" }, + { tag: "keycard", loginType: Constants.LoginType.Keycard, iconName: "keycard" } + ] + } + + function test_loginType(data) { + const loginType = data.loginType + const iconName = data.iconName + + verify(!!controlUnderTest) + + controlUnderTest.loginType = loginType + + const signButton = findChild(controlUnderTest.footer, "signButton") + verify(!!signButton) + compare(signButton.icon.name, iconName) + } + + function test_loading() { + verify(!!controlUnderTest) + + compare(controlUnderTest.feesLoading, false) + + const signButton = findChild(controlUnderTest.footer, "signButton") + verify(!!signButton) + compare(signButton.interactive, true) + + const footerFiatFeesText = findChild(controlUnderTest.footer, "footerFiatFeesText") + verify(!!footerFiatFeesText) + compare(footerFiatFeesText.loading, false) + + const footerEstTimeText = findChild(controlUnderTest.footer, "footerEstTimeText") + verify(!!footerEstTimeText) + compare(footerEstTimeText.loading, false) + + const fiatFeesText = findChild(controlUnderTest.contentItem, "fiatFeesText") + verify(!!fiatFeesText) + compare(fiatFeesText.loading, false) + + const cryptoFeesText = findChild(controlUnderTest.contentItem, "cryptoFeesText") + verify(!!cryptoFeesText) + compare(cryptoFeesText.loading, false) + + controlUnderTest.feesLoading = true + + compare(signButton.interactive, false) + compare(footerFiatFeesText.loading, true) + compare(footerEstTimeText.loading, true) + compare(fiatFeesText.loading, true) + compare(cryptoFeesText.loading, true) + } + + function test_footerInfo() { + verify(!!controlUnderTest) + + const fiatFeesText = findChild(controlUnderTest.footer, "footerFiatFeesText") + verify(!!fiatFeesText) + compare(fiatFeesText.text, controlUnderTest.fiatFees) + + const footerEstTimeText = findChild(controlUnderTest.footer, "footerEstTimeText") + verify(!!footerEstTimeText) + compare(footerEstTimeText.text, controlUnderTest.estimatedTime) + } + + function test_signButton() { + verify(!!controlUnderTest) + + const signButton = findChild(controlUnderTest.footer, "signButton") + verify(!!signButton) + compare(signButton.interactive, true) + + signButton.clicked() + compare(signalSpyAccepted.count, 1) + compare(controlUnderTest.opened, false) + compare(controlUnderTest.result, Dialog.Accepted) + } + + function test_rejectButton() { + verify(!!controlUnderTest) + + const rejectButton = findChild(controlUnderTest.footer, "rejectButton") + verify(!!rejectButton) + compare(rejectButton.interactive, true) + + rejectButton.clicked() + compare(signalSpyRejected.count, 1) + compare(controlUnderTest.opened, false) + compare(controlUnderTest.result, Dialog.Rejected) + } + } +} diff --git a/ui/StatusQ/src/StatusQ/Components/LoadingErrorComponent.qml b/ui/StatusQ/src/StatusQ/Components/LoadingErrorComponent.qml index 20f23843e86..12022402a1a 100644 --- a/ui/StatusQ/src/StatusQ/Components/LoadingErrorComponent.qml +++ b/ui/StatusQ/src/StatusQ/Components/LoadingErrorComponent.qml @@ -40,8 +40,14 @@ Control { */ property string text: qsTr("Failed\nto load") + /*! + \qmlproperty string LoadingComponent::icon + This property lets user set error icon + */ + property alias icon: errorIcon.icon + background: Rectangle { - color: Theme.palette.baseColor5 + color: Theme.palette.baseColor4 radius: root.radius } @@ -50,10 +56,11 @@ Control { anchors.centerIn: parent spacing: 10 StatusIcon { + id: errorIcon + anchors.horizontalCenter: parent.horizontalCenter icon: "frowny" - opacity: 0.1 - color: Theme.palette.directColor1 + color: Theme.palette.directColor7 } StatusBaseText { anchors.horizontalCenter: parent.horizontalCenter diff --git a/ui/app/AppLayouts/Wallet/panels/ContractInfoButtonWithMenu.qml b/ui/app/AppLayouts/Wallet/panels/ContractInfoButtonWithMenu.qml index 7f7288949ca..41b04d081ec 100644 --- a/ui/app/AppLayouts/Wallet/panels/ContractInfoButtonWithMenu.qml +++ b/ui/app/AppLayouts/Wallet/panels/ContractInfoButtonWithMenu.qml @@ -25,16 +25,18 @@ StatusFlatButton { icon.color: highlighted ? Theme.palette.directColor1 : Theme.palette.directColor5 highlighted: moreMenu.opened - onClicked: moreMenu.popup(-moreMenu.width + width, height + 4) + onClicked: moreMenu.popup(root, 0, height + 4) function getExplorerName() { return Utils.getChainExplorerName(root.networkShortName) } StatusMenu { + objectName: "moreMenu" id: moreMenu StatusAction { + objectName: "externalLink" //: e.g. "View Optimism (DAI) contract address on Optimistic" text: !!root.symbol ? qsTr("View %1 %2 contract address on %3").arg(root.networkName).arg(root.symbol).arg(getExplorerName()) : qsTr("View %1 contract address on %2").arg(root.networkName).arg(getExplorerName()) @@ -45,6 +47,7 @@ StatusFlatButton { } } StatusSuccessAction { + objectName: "copyButton" text: qsTr("Copy contract address") successText: qsTr("Copied") icon.name: "copy" diff --git a/ui/app/AppLayouts/Wallet/panels/RecipientInfoButtonWithMenu.qml b/ui/app/AppLayouts/Wallet/panels/RecipientInfoButtonWithMenu.qml new file mode 100644 index 00000000000..3f7e8ef0a58 --- /dev/null +++ b/ui/app/AppLayouts/Wallet/panels/RecipientInfoButtonWithMenu.qml @@ -0,0 +1,52 @@ + +import StatusQ 0.1 +import StatusQ.Core.Theme 0.1 +import StatusQ.Controls 0.1 +import StatusQ.Popups 0.1 + +import utils 1.0 + +StatusFlatButton { + id: root + + required property string recipientAddress + required property string networkName + required property string networkShortName + required property string networkBlockExplorerUrl + + signal openLink(string link) + + icon.name: "more" + icon.color: highlighted ? Theme.palette.directColor1 : Theme.palette.directColor5 + + highlighted: moreMenu.opened + onClicked: moreMenu.popup(root, 0, height + 4) + + function getExplorerName() { + return Utils.getChainExplorerName(root.networkShortName) + } + + StatusMenu { + id: moreMenu + objectName: "moreMenu" + + StatusAction { + objectName: "externalLink" + //: e.g. "View receiver address on Etherscan" + text: qsTr("View receiver address on %1").arg(getExplorerName()) + icon.name: "external-link" + onTriggered: { + var link = "%1/%2/%3".arg(root.networkBlockExplorerUrl).arg(Constants.networkExplorerLinks.addressPath).arg(root.recipientAddress) + root.openLink(link) + } + } + StatusSuccessAction { + objectName: "copyButton" + text: qsTr("Copy receiver address") + successText: qsTr("Copied") + icon.name: "copy" + autoDismissMenu: true + onTriggered: ClipboardUtils.setText(root.recipientAddress) + } + } +} diff --git a/ui/app/AppLayouts/Wallet/panels/SignCollectibleInfoBox.qml b/ui/app/AppLayouts/Wallet/panels/SignCollectibleInfoBox.qml new file mode 100644 index 00000000000..813e8538dca --- /dev/null +++ b/ui/app/AppLayouts/Wallet/panels/SignCollectibleInfoBox.qml @@ -0,0 +1,144 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 + +import StatusQ 0.1 +import StatusQ.Core 0.1 +import StatusQ.Core.Theme 0.1 +import StatusQ.Controls 0.1 +import StatusQ.Popups 0.1 + +import utils 1.0 + +import AppLayouts.Wallet.views.collectibles 1.0 + +Control { + id: root + + required property string name + + required property string backgroundColor + required property bool isMetadataValid + required property string fallbackImageUrl + required property string contractAddress + required property string tokenId + + required property string networkShortName + required property string networkBlockExplorerUrl + required property string openSeaExplorerUrl + + signal openLink(string link) + + QtObject { + id: d + + function getExplorerName() { + return Utils.getChainExplorerName(root.networkShortName) + } + + readonly property string collectibleBlockExplorerLink: { + if (root.networkShortName === Constants.networkShortChainNames.mainnet) { + return "%1/nft/%2/%3".arg(getExplorerName()).arg(root.contractAddress).arg(root.tokenId) + } + else { + return "%1/token/%2?a=%3".arg(getExplorerName()).arg(root.contractAddress).arg(root.tokenId) + } + } + } + + padding: Theme.smallPadding + + background: Rectangle { + color: moreButton.hovered + ? Theme.palette.statusMenu.hoverBackgroundColor + : "transparent" + + radius: 8 + border.width: 1 + border.color: Theme.palette.baseColor2 + } + + contentItem: StatusItemDelegate { + contentItem: RowLayout { + spacing: 12 + CollectibleMedia { + id: collectibleMedia + + objectName: "collectibleMedia" + backgroundColor: root.backgroundColor + isMetadataValid: root.isMetadataValid + fallbackImageUrl: root.fallbackImageUrl + + manualMaxDimension: 40 + Layout.preferredWidth: 40 + Layout.preferredHeight: 40 + radius: 4 + } + + ColumnLayout { + spacing: 0 + StatusBaseText { + objectName: "primaryText" + text: !!root.name ? + root.name : "" + font.pixelSize: Theme.additionalTextSize + elide: Text.ElideRight + } + StatusBaseText { + objectName: "secondaryText" + text: !!root.tokenId ? + root.tokenId : "" + font.pixelSize: Theme.additionalTextSize + color: Theme.palette.baseColor1 + } + } + + RowLayout { + Layout.fillWidth: true + } + + StatusFlatButton { + id: moreButton + + objectName: "moreButton" + width: 40 + height: 40 + icon.name: "more" + icon.color: highlighted ? Theme.palette.directColor1 : Theme.palette.directColor5 + + highlighted: moreMenu.opened + onClicked: moreMenu.popup(moreButton, 0, height + 4) + } + } + } + + StatusMenu { + objectName: "moreMenu" + id: moreMenu + + StatusAction { + objectName: "openSeaExternalLink" + text: qsTr("View collectible on OpenSea") + icon.name: "external-link" + onTriggered: { + const link = "%1/%2/%3".arg(root.openSeaExplorerUrl).arg(root.contractAddress).arg(root.tokenId) + root.openLink(link) + } + } + StatusAction { + objectName: "blockchainExternalLink" + //: e.g. "View collectible on Etherscan" + text: qsTr("View collectible on %1").arg(d.getExplorerName()) + icon.name: "external-link" + onTriggered: root.openLink(d.collectibleBlockExplorerLink) + } + StatusSuccessAction { + objectName: "copyButton" + text: qsTr("Copy %1 collectible address").arg(d.getExplorerName()) + successText: qsTr("Copied") + icon.name: "copy" + autoDismissMenu: true + onTriggered: ClipboardUtils.setText(d.collectibleBlockExplorerLink) + } + } +} diff --git a/ui/app/AppLayouts/Wallet/panels/SignInfoBox.qml b/ui/app/AppLayouts/Wallet/panels/SignInfoBox.qml index c0108b7d765..11f373f69e0 100644 --- a/ui/app/AppLayouts/Wallet/panels/SignInfoBox.qml +++ b/ui/app/AppLayouts/Wallet/panels/SignInfoBox.qml @@ -20,6 +20,7 @@ ColumnLayout { property alias asset: listItem.asset property alias components: listItem.components property int listItemHeight: 76 + property bool highlighted StatusBaseText { text: root.caption @@ -41,6 +42,7 @@ ColumnLayout { asset.bgHeight: 40 border.width: 1 border.color: Theme.palette.baseColor2 + highlighted: root.highlighted sensor.enabled: false diff --git a/ui/app/AppLayouts/Wallet/panels/qmldir b/ui/app/AppLayouts/Wallet/panels/qmldir index 647aff01cb8..1287bba6468 100644 --- a/ui/app/AppLayouts/Wallet/panels/qmldir +++ b/ui/app/AppLayouts/Wallet/panels/qmldir @@ -16,3 +16,5 @@ SendModalHeader 1.0 SendModalHeader.qml StickySendModalHeader 1.0 StickySendModalHeader.qml RecipientSelectorPanel 1.0 RecipientSelectorPanel.qml SimpleTransactionsFees 1.0 SimpleTransactionsFees.qml +RecipientInfoButtonWithMenu 1.0 RecipientInfoButtonWithMenu.qml +SignCollectibleInfoBox 1.0 SignCollectibleInfoBox.qml diff --git a/ui/app/AppLayouts/Wallet/popups/SignTransactionModalBase.qml b/ui/app/AppLayouts/Wallet/popups/SignTransactionModalBase.qml index d0730b18e48..3c3becbe9b9 100644 --- a/ui/app/AppLayouts/Wallet/popups/SignTransactionModalBase.qml +++ b/ui/app/AppLayouts/Wallet/popups/SignTransactionModalBase.qml @@ -16,6 +16,8 @@ import StatusQ.Popups.Dialog 0.1 import shared.controls 1.0 import shared.stores 1.0 +import AppLayouts.Wallet.views.collectibles 1.0 + import utils 1.0 StatusDialog { @@ -88,6 +90,9 @@ StatusDialog { property string infoTagText readonly property alias infoTag: infoTag property bool showHeaderDivider: true + property bool isCollectible + readonly property alias fromAccountSmartIdenticon: fromAccountSmartIdenticon + readonly property alias collectibleMedia: collectibleMedia default property alias contents: contentsLayout.data @@ -139,7 +144,7 @@ StatusDialog { Layout.rightMargin: -parent.anchors.rightMargin - scrollView.rightPadding Layout.preferredHeight: childrenRect.height + 80 - countdownPill.height // 40 + 40 top/bottomMargin gradient: Gradient { - GradientStop { position: 0.0; color: root.gradientColor } + GradientStop { position: 0.0; color: Utils.setColorAlpha(root.gradientColor, 0.05) } GradientStop { position: 1.0; color: root.backgroundColor } } @@ -148,7 +153,7 @@ StatusDialog { spacing: 12 anchors.centerIn: parent - Row { + RowLayout { Layout.alignment: Qt.AlignHCenter Layout.topMargin: 4 spacing: -10 @@ -200,6 +205,54 @@ StatusDialog { asset.color: "transparent" asset.bgColor: "transparent" } + visible: !root.isCollectible + } + + RowLayout { + Layout.alignment: Qt.AlignCenter + spacing: -fromAccountSmartIdenticon.width+4 + Item { + height: 120 + width: 120 + CollectibleMedia { + id: collectibleMedia + + objectName: "collectibleMedia" + manualMaxDimension: 120 + radius: 12 + } + layer.enabled: true + layer.effect: DropShadow { + horizontalOffset: 0 + verticalOffset: 0 + samples: 37 + color: Utils.setColorAlpha(root.gradientColor, 0.15) + } + } + Rectangle { + Layout.alignment: Qt.AlignBottom + Layout.bottomMargin: -4 + + Layout.preferredWidth: fromAccountSmartIdenticon.width + 4 + Layout.preferredHeight: fromAccountSmartIdenticon.height + 4 + radius: width/2 + color: root.backgroundColor + + StatusSmartIdenticon { + id: fromAccountSmartIdenticon + + anchors.centerIn: parent + objectName: "fromAccountSmartIdenticon" + asset.bgWidth: 28 + asset.bgHeight: 28 + visible: !!asset.name || !!asset.source + asset.width: 28 + asset.height: 28 + asset.color: "transparent" + asset.bgColor: "transparent" + } + } + visible: root.isCollectible } StatusBaseText { diff --git a/ui/app/AppLayouts/Wallet/popups/simpleSend/SendSignModal.qml b/ui/app/AppLayouts/Wallet/popups/simpleSend/SendSignModal.qml new file mode 100644 index 00000000000..064ac24d324 --- /dev/null +++ b/ui/app/AppLayouts/Wallet/popups/simpleSend/SendSignModal.qml @@ -0,0 +1,277 @@ +import QtQuick 2.15 +import QtQuick.Layouts 1.15 +import QtQml.Models 2.15 + +import StatusQ.Core 0.1 +import StatusQ.Core.Utils 0.1 as SQUtils +import StatusQ.Core.Theme 0.1 +import StatusQ.Controls 0.1 +import StatusQ.Components 0.1 + +import AppLayouts.Wallet.panels 1.0 +import AppLayouts.Wallet.popups 1.0 + +import utils 1.0 + +SignTransactionModalBase { + id: root + + required property string fromTokenSymbol + required property string fromTokenAmount + required property string fromTokenContractAddress + + required property string accountName + required property string accountAddress + required property string accountEmoji + required property color accountColor + + /** TODO: Use new recipients appraoch from + https://github.com/status-im/status-desktop/issues/16916 **/ + required property string recipientAddress + + required property string networkShortName // e.g. "oeth" + required property string networkName // e.g. "Optimism" + required property string networkIconPath // e.g. `Theme.svg("network/Network=Optimism")` + required property string networkBlockExplorerUrl + + required property string fiatFees + required property string cryptoFees + required property string estimatedTime + + required property string collectibleContractAddress + required property string collectibleTokenId + required property string collectibleName + required property string collectibleBackgroundColor + required property bool collectibleIsMetadataValid + required property string collectibleMediaUrl + required property string collectibleMediaType + required property string collectibleFallbackImageUrl + + required property var fnGetOpenSeaExplorerUrl + + title: qsTr("Sign Send") + //: e.g. (Send) 100 DAI to batista.eth + subtitle: { + const tokenToSend = root.isCollectible ? + root.collectibleName: + formatBigNumber(fromTokenAmount, fromTokenSymbol) + return qsTr("%1 to %2"). + arg(tokenToSend). + arg(SQUtils.Utils.elideAndFormatWalletAddress(root.recipientAddress)) + } + + gradientColor: root.accountColor + fromImageSmartIdenticon.asset.name: "filled-account" + fromImageSmartIdenticon.asset.emoji: root.accountEmoji + fromImageSmartIdenticon.asset.color: root.accountColor + fromImageSmartIdenticon.asset.isLetterIdenticon: !!root.accountEmoji + + fromAccountSmartIdenticon.asset.name: "filled-account" + fromAccountSmartIdenticon.asset.emoji: root.accountEmoji + fromAccountSmartIdenticon.asset.color: root.accountColor + fromAccountSmartIdenticon.asset.isLetterIdenticon: !!root.accountEmoji + fromAccountSmartIdenticon.asset.isImage: root.isCollectible + + toImageSource: Constants.tokenIcon(root.fromTokenSymbol) + + collectibleMedia.backgroundColor: root.collectibleBackgroundColor + collectibleMedia.isMetadataValid: root.collectibleIsMetadataValid + collectibleMedia.mediaUrl: root.collectibleMediaUrl + collectibleMedia.mediaType: root.collectibleMediaType + collectibleMedia.fallbackImageUrl: root.collectibleFallbackImageUrl + + //: e.g. "Send 100 DAI to recipient on " + headerMainText: { + const tokenToSend = root.isCollectible ? + root.collectibleName: + formatBigNumber(fromTokenAmount, fromTokenSymbol) + return qsTr("Send %1 to %2 on %3").arg(tokenToSend) + .arg(SQUtils.Utils.elideAndFormatWalletAddress(root.recipientAddress)).arg(root.networkName) + } + infoTagText: qsTr("Review all details before signing") + + headerIconComponent: StatusSmartIdenticon { + asset.name: "filled-account" + asset.emoji: root.accountEmoji + asset.color: root.accountColor + asset.isLetterIdenticon: !!root.accountEmoji + asset.bgWidth: 40 + asset.bgHeight: 40 + + bridgeBadge.visible: true + bridgeBadge.border.width: 2 + bridgeBadge.color: Theme.palette.darkBlue + bridgeBadge.image.source: Theme.svg("sign") + } + + leftFooterContents: ObjectModel { + RowLayout { + Layout.leftMargin: 4 + spacing: Theme.bigPadding + ColumnLayout { + spacing: 2 + StatusBaseText { + text: qsTr("Est time") + color: Theme.palette.baseColor1 + font.pixelSize: Theme.additionalTextSize + } + StatusTextWithLoadingState { + objectName: "footerEstTimeText" + text: loading ? Constants.dummyText : root.estimatedTime + loading: root.feesLoading + } + } + ColumnLayout { + spacing: 2 + StatusBaseText { + text: qsTr("Max fees") + color: Theme.palette.baseColor1 + font.pixelSize: Theme.additionalTextSize + } + StatusTextWithLoadingState { + objectName: "footerFiatFeesText" + text: loading ? Constants.dummyText : root.fiatFees + loading: root.feesLoading + } + } + } + } + + // Send Asset + SignInfoBox { + objectName: "sendAssetBox" + Layout.fillWidth: true + Layout.bottomMargin: Theme.bigPadding + caption: qsTr("Send") + primaryText: formatBigNumber(root.fromTokenAmount, root.fromTokenSymbol) + secondaryText: root.fromTokenSymbol !== Constants.ethToken ? + SQUtils.Utils.elideAndFormatWalletAddress(root.fromTokenContractAddress) : "" + icon: Constants.tokenIcon(root.fromTokenSymbol) + badge: root.networkIconPath + highlighted: contractInfoButtonWithMenu.hovered + components: [ + ContractInfoButtonWithMenu { + id: contractInfoButtonWithMenu + + objectName: "contractInfoButtonWithMenu" + visible: root.fromTokenSymbol !== Constants.ethToken + symbol: root.fromTokenSymbol + contractAddress: root.fromTokenContractAddress + networkName: root.networkName + networkShortName: root.networkShortName + networkBlockExplorerUrl: root.networkBlockExplorerUrl + onOpenLink: (link) => root.openLinkWithConfirmation(link) + } + ] + visible: !root.isCollectible + } + + // Send Collectible + ColumnLayout { + objectName: "sendCollectibleBox" + StatusBaseText { + objectName: "collectibleCaption" + text: qsTr("Send") + font.pixelSize: Theme.additionalTextSize + } + SignCollectibleInfoBox { + Layout.fillWidth: true + Layout.bottomMargin: Theme.bigPadding + name: !!root.collectibleName ? root.collectibleName: qsTr("Unknown") + backgroundColor: root.collectibleBackgroundColor + isMetadataValid: root.collectibleIsMetadataValid + fallbackImageUrl: root.collectibleFallbackImageUrl + contractAddress: root.collectibleContractAddress + tokenId: root.collectibleTokenId + networkShortName: root.networkShortName + networkBlockExplorerUrl: root.networkBlockExplorerUrl + openSeaExplorerUrl: root.fnGetOpenSeaExplorerUrl(root.networkShortName) + onOpenLink: (link) => root.openLinkWithConfirmation(link) + } + visible: root.isCollectible + } + + // From + SignInfoBox { + Layout.fillWidth: true + Layout.bottomMargin: Theme.bigPadding + objectName: "accountBox" + caption: qsTr("From") + primaryText: root.accountName + secondaryText: SQUtils.Utils.elideAndFormatWalletAddress(root.accountAddress) + asset.name: "filled-account" + asset.emoji: root.accountEmoji + asset.color: root.accountColor + asset.isLetterIdenticon: !!root.accountEmoji + } + + /** TODO: Use new recipients appraoch from + https://github.com/status-im/status-desktop/issues/16916 **/ + // To + SignInfoBox { + Layout.fillWidth: true + Layout.bottomMargin: Theme.bigPadding + objectName: "recipientBox" + caption: qsTr("To") + primaryText: root.recipientAddress + asset.name: "address" + asset.isLetterIdenticon: false + asset.isImage: false + highlighted: recipientInfoButtonWithMenu.hovered + components: [ + RecipientInfoButtonWithMenu { + id: recipientInfoButtonWithMenu + + objectName: "recipientInfoButtonWithMenu" + recipientAddress: root.recipientAddress + networkName: root.networkName + networkShortName: root.networkShortName + networkBlockExplorerUrl: root.networkBlockExplorerUrl + onOpenLink: (link) => root.openLinkWithConfirmation(link) + } + ] + } + + // Network + SignInfoBox { + Layout.fillWidth: true + Layout.bottomMargin: Theme.bigPadding + objectName: "networkBox" + caption: qsTr("Network") + primaryText: root.networkName + icon: root.networkIconPath + } + + // Fees + SignInfoBox { + Layout.fillWidth: true + Layout.bottomMargin: Theme.bigPadding + objectName: "feesBox" + caption: qsTr("Fees") + primaryText: qsTr("Max. fees on %1").arg(root.networkName) + primaryTextCustomColor: Theme.palette.baseColor1 + secondaryText: " " + components: [ + ColumnLayout { + spacing: 2 + StatusTextWithLoadingState { + objectName: "fiatFeesText" + Layout.alignment: Qt.AlignRight + text: loading ? Constants.dummyText : root.fiatFees + horizontalAlignment: Text.AlignRight + font.pixelSize: Theme.additionalTextSize + loading: root.feesLoading + } + StatusTextWithLoadingState { + objectName: "cryptoFeesText" + Layout.alignment: Qt.AlignRight + text: loading ? Constants.dummyText : root.cryptoFees + horizontalAlignment: Text.AlignRight + font.pixelSize: Theme.additionalTextSize + customColor: Theme.palette.baseColor1 + loading: root.feesLoading + } + } + ] + } +} diff --git a/ui/app/AppLayouts/Wallet/popups/simpleSend/qmldir b/ui/app/AppLayouts/Wallet/popups/simpleSend/qmldir index 47283cb9e6a..c8d1b7430d4 100644 --- a/ui/app/AppLayouts/Wallet/popups/simpleSend/qmldir +++ b/ui/app/AppLayouts/Wallet/popups/simpleSend/qmldir @@ -1 +1,2 @@ SimpleSendModal 1.0 SimpleSendModal.qml +SendSignModal 1.0 SendSignModal.qml diff --git a/ui/app/AppLayouts/Wallet/popups/swap/SwapApproveCapModal.qml b/ui/app/AppLayouts/Wallet/popups/swap/SwapApproveCapModal.qml index 45550749314..09aa2b02078 100644 --- a/ui/app/AppLayouts/Wallet/popups/swap/SwapApproveCapModal.qml +++ b/ui/app/AppLayouts/Wallet/popups/swap/SwapApproveCapModal.qml @@ -51,7 +51,7 @@ SignTransactionModalBase { title: qsTr("Approve spending cap") subtitle: root.serviceProviderHostname - gradientColor: Utils.setColorAlpha(root.accountColor, 0.05) // 5% of wallet color + gradientColor: root.accountColor fromImageSmartIdenticon.asset.name: "filled-account" fromImageSmartIdenticon.asset.emoji: root.accountEmoji fromImageSmartIdenticon.asset.color: root.accountColor diff --git a/ui/app/AppLayouts/Wallet/popups/swap/SwapSignModal.qml b/ui/app/AppLayouts/Wallet/popups/swap/SwapSignModal.qml index 3077cf71f8e..d2dc87512b8 100644 --- a/ui/app/AppLayouts/Wallet/popups/swap/SwapSignModal.qml +++ b/ui/app/AppLayouts/Wallet/popups/swap/SwapSignModal.qml @@ -48,7 +48,7 @@ SignTransactionModalBase { //: e.g. (swap) 100 DAI to 100 USDT subtitle: qsTr("%1 to %2").arg(formatBigNumber(fromTokenAmount, fromTokenSymbol)).arg(formatBigNumber(toTokenAmount, toTokenSymbol)) - gradientColor: Utils.setColorAlpha(root.accountColor, 0.05) // 5% of wallet color + gradientColor: root.accountColor fromImageSource: Constants.tokenIcon(root.fromTokenSymbol) toImageSource: Constants.tokenIcon(root.toTokenSymbol) diff --git a/ui/app/AppLayouts/Wallet/views/collectibles/CollectibleMedia.qml b/ui/app/AppLayouts/Wallet/views/collectibles/CollectibleMedia.qml index cc0ca22571f..a8d447dc8b4 100644 --- a/ui/app/AppLayouts/Wallet/views/collectibles/CollectibleMedia.qml +++ b/ui/app/AppLayouts/Wallet/views/collectibles/CollectibleMedia.qml @@ -13,6 +13,12 @@ StatusRoundedMedia { property bool isCollectibleLoading: false property bool isMetadataValid: false + QtObject { + id: d + + property bool isUnknown: root.isError && root.componentMediaType === StatusRoundedMedia.MediaType.Unknown + } + radius: Theme.radius color: isError || isEmpty ? Theme.palette.baseColor5 : backgroundColor @@ -20,22 +26,24 @@ StatusRoundedMedia { id: loadingCompLoader anchors.fill: parent active: root.isCollectibleLoading || root.isLoading - sourceComponent: LoadingComponent {radius: root.radius} + sourceComponent: LoadingComponent { + objectName: "loadingComponent" + radius: root.radius + } } Loader { anchors.fill: parent active: (root.isError || root.isEmpty) && !loadingCompLoader.active sourceComponent: LoadingErrorComponent { + objectName: "loadingErrorComponent" radius: root.radius + icon: d.isUnknown ? "frowny": "help" text: { - if (root.isError && root.componentMediaType === StatusRoundedMedia.MediaType.Unkown) { + if (d.isUnknown) { return qsTr("Unsupported\nfile format") } - if (!root.isMetadataValid) { - return qsTr("Info can't\nbe fetched") - } - return qsTr("Failed\nto load") + return "" } } } diff --git a/ui/imports/shared/popups/walletconnect/DAppSignRequestModal.qml b/ui/imports/shared/popups/walletconnect/DAppSignRequestModal.qml index 2289b36fd9b..b9670a6fb86 100644 --- a/ui/imports/shared/popups/walletconnect/DAppSignRequestModal.qml +++ b/ui/imports/shared/popups/walletconnect/DAppSignRequestModal.qml @@ -54,7 +54,7 @@ SignTransactionModalBase { badgeIcon: Theme.svg("sign-blue") } - gradientColor: Utils.setColorAlpha(root.accountColor, 0.05) // 5% of wallet color + gradientColor: root.accountColor headerMainText: root.signingTransaction ? qsTr("%1 wants you to sign this transaction with %2").arg(root.dappName).arg(root.accountName) : qsTr("%1 wants you to sign this message with %2").arg(root.dappName).arg(root.accountName)