diff --git a/storybook/qmlTests/tests/tst_DAppsWorkflow.qml b/storybook/qmlTests/tests/tst_DAppsWorkflow.qml index b98e95d1c3..025e364b2c 100644 --- a/storybook/qmlTests/tests/tst_DAppsWorkflow.qml +++ b/storybook/qmlTests/tests/tst_DAppsWorkflow.qml @@ -25,630 +25,631 @@ Item { width: 600 height: 400 - // // TODO #15151 fix CI crash and re-enable tests - // Component { - // id: sdkComponent - - // WalletConnectSDKBase { - // property bool sdkReady: true - - // property var getActiveSessionsCallbacks: [] - // getActiveSessions: function(callback) { - // getActiveSessionsCallbacks.push({callback}) - // } - - // property int pairCalled: 0 - // pair: function() { - // pairCalled++ - // } - - // property var buildApprovedNamespacesCalls: [] - // buildApprovedNamespaces: function(params, supportedNamespaces) { - // buildApprovedNamespacesCalls.push({params, supportedNamespaces}) - // } - - // property var approveSessionCalls: [] - // approveSession: function(sessionProposalJson, approvedNamespaces) { - // approveSessionCalls.push({sessionProposalJson, approvedNamespaces}) - // } - - // property var rejectSessionRequestCalls: [] - // rejectSessionRequest: function(topic, id, error) { - // rejectSessionRequestCalls.push({topic, id, error}) - // } - // } - // } - - // Component { - // id: serviceComponent - - // WalletConnectService { - // property var onApproveSessionResultTriggers: [] - // onApproveSessionResult: function(session, error) { - // onApproveSessionResultTriggers.push({session, error}) - // } - - // property var onDisplayToastMessageTriggers: [] - // onDisplayToastMessage: function(message, error) { - // onDisplayToastMessageTriggers.push({message, error}) - // } - // } - // } - - // Component { - // id: dappsStoreComponent - - - // DAppsStore { - // property string dappsListReceivedJsonStr: '[]' - - // signal dappsListReceived(string dappsJson) - // signal userAuthenticated(string topic, string id, string password, string pin) - // signal userAuthenticationFailed(string topic, string id) - - // // By default, return no dapps in store - // function getDapps() { - // dappsListReceived(dappsListReceivedJsonStr) - // return true - // } - - // property var addWalletConnectSessionCalls: [] - // function addWalletConnectSession(sessionJson) { - // addWalletConnectSessionCalls.push({sessionJson}) - // } - - // property var authenticateUserCalls: [] - // function authenticateUser(topic, id, address) { - // authenticateUserCalls.push({topic, id, address}) - // } - - // property var signMessageCalls: [] - // function signMessage(topic, id, address, password, message) { - // signMessageCalls.push({topic, id, address, password, message}) - // } - // property var signTypedDataV4Calls: [] - // function signTypedDataV4(topic, id, address, password, message) { - // signTypedDataV4Calls.push({topic, id, address, password, message}) - // } - // } - // } - - // Component { - // id: walletStoreComponent - - // WalletStore { - // readonly property ListModel flatNetworks: ListModel { - // ListElement { chainId: 1 } - // ListElement { - // chainId: 2 - // chainName: "Test Chain" - // iconUrl: "network/Network=Ethereum" - // } - // } - - // readonly property ListModel accounts: ListModel { - // ListElement {address: "0x1"} - // ListElement { - // address: "0x2" - // name: "helloworld" - // emoji: "😋" - // color: "#2A4AF5" - // } - // ListElement { address: "0x3a" } - // } - // readonly property ListModel ownAccounts: accounts - // } - // } - - // Component { - // id: dappsRequestHandlerComponent - - // DAppsRequestHandler { - // } - // } - - // TestCase { - // id: requestHandlerTest - // name: "DAppsRequestHandler" - - // property DAppsRequestHandler handler: null - - // SignalSpy { - // id: displayToastMessageSpy - // target: requestHandlerTest.handler - // signalName: "onDisplayToastMessage" - // } - - // function init() { - // let walletStore = createTemporaryObject(walletStoreComponent, root) - // verify(!!walletStore) - // let sdk = createTemporaryObject(sdkComponent, root, { projectId: "12ab" }) - // verify(!!sdk) - // let store = createTemporaryObject(dappsStoreComponent, root) - // verify(!!store) - // handler = createTemporaryObject(dappsRequestHandlerComponent, root, {sdk: sdk, store: store, walletStore: walletStore}) - // verify(!!handler) - // } - - // function cleanup() { - // displayToastMessageSpy.clear() - // } - - // function test_TestAuthentication() { - // let td = mockSessionRequestEvent(this, handler.sdk, handler.walletStore) - // handler.authenticate(td.request) - // compare(handler.store.authenticateUserCalls.length, 1, "expected a call to store.authenticateUser") - - // let store = handler.store - // store.userAuthenticated(td.topic, td.request.id, "password", "") - // compare(store.signMessageCalls.length, 1, "expected a call to store.signMessage") - // compare(store.signMessageCalls[0].message, td.request.data) - // } - - // function test_onSessionRequestEventDifferentCaseForAddress() { - // let sdk = handler.sdk - - // let testAddressUpper = "0x3A" - // let chainId = 2 - // let method = "personal_sign" - // let message = "hello world" - // let params = [Helpers.strToHex(message), testAddressUpper] - // let topic = "b536a" - // let session = JSON.parse(Testing.formatSessionRequest(chainId, method, params, topic)) - // // Expect to have calls to getActiveSessions from service initialization - // let prevRequests = sdk.getActiveSessionsCallbacks.length - // sdk.sessionRequestEvent(session) - - // compare(sdk.getActiveSessionsCallbacks.length, 1, "expected DAppsRequestHandler call sdk.getActiveSessions") - // } - // } - - // TestCase { - // id: walletConnectServiceTest - // name: "WalletConnectService" - - // property WalletConnectService service: null - - // SignalSpy { - // id: connectDAppSpy - // target: walletConnectServiceTest.service - // signalName: "connectDApp" - - // property var argPos: { - // "dappChains": 0, - // "sessionProposalJson": 1, - // "availableNamespaces": 0 - // } - // } - - // SignalSpy { - // id: sessionRequestSpy - // target: walletConnectServiceTest.service - // signalName: "sessionRequest" - - // property var argPos: { - // "request": 0 - // } - // } - - // function init() { - // let walletStore = createTemporaryObject(walletStoreComponent, root) - // verify(!!walletStore) - // let sdk = createTemporaryObject(sdkComponent, root, { projectId: "12ab" }) - // verify(!!sdk) - // let store = createTemporaryObject(dappsStoreComponent, root) - // verify(!!store) - // service = createTemporaryObject(serviceComponent, root, {wcSDK: sdk, store: store, walletStore: walletStore}) - // verify(!!service) - // } - - // function cleanup() { - // connectDAppSpy.clear() - // sessionRequestSpy.clear() - // } - - // function test_TestPairing() { - // // All calls to SDK are expected as events to be made by the wallet connect SDK - // let sdk = service.wcSDK - // let walletStore = service.walletStore - // let store = service.store - - // service.pair("wc:12ab@1?bridge=https%3A%2F%2Fbridge.walletconnect.org&key=12ab") - // compare(sdk.pairCalled, 1, "expected a call to sdk.pair") - - // sdk.sessionProposal(JSON.parse(Testing.formatSessionProposal())) - // compare(sdk.buildApprovedNamespacesCalls.length, 1, "expected a call to sdk.buildApprovedNamespaces") - // var args = sdk.buildApprovedNamespacesCalls[0] - // verify(!!args.supportedNamespaces, "expected supportedNamespaces to be set") - // let chainsForApproval = args.supportedNamespaces.eip155.chains - // let networksArray = ModelUtils.modelToArray(walletStore.flatNetworks).map(entry => entry.chainId) - // verify(networksArray.every(chainId => chainsForApproval.some(eip155Chain => eip155Chain === `eip155:${chainId}`)), - // "expect all the networks to be present") - // // We test here all accounts for one chain only, we have separate tests to validate that all accounts are present - // let allAccountsForApproval = args.supportedNamespaces.eip155.accounts - // let accountsArray = ModelUtils.modelToArray(walletStore.accounts).map(entry => entry.address) - // verify(accountsArray.every(address => allAccountsForApproval.some(eip155Address => eip155Address === `eip155:${networksArray[0]}:${address}`)), - // "expect at least all accounts for the first chain to be present" - // ) - - // let allApprovedNamespaces = JSON.parse(Testing.formatBuildApprovedNamespacesResult(networksArray, accountsArray)) - // sdk.buildApprovedNamespacesResult(allApprovedNamespaces, "") - // compare(connectDAppSpy.count, 1, "expected a call to service.connectDApp") - // let connectArgs = connectDAppSpy.signalArguments[0] - // compare(connectArgs[connectDAppSpy.argPos.dappChains], networksArray, "expected all provided networks (walletStore.flatNetworks) for the dappChains") - // verify(!!connectArgs[connectDAppSpy.argPos.sessionProposalJson], "expected sessionProposalJson to be set") - // verify(!!connectArgs[connectDAppSpy.argPos.availableNamespaces], "expected availableNamespaces to be set") - - // let selectedAccount = walletStore.accounts.get(1) - // service.approvePairSession(connectArgs[connectDAppSpy.argPos.sessionProposalJson], connectArgs[connectDAppSpy.argPos.dappChains], selectedAccount) - // compare(sdk.buildApprovedNamespacesCalls.length, 2, "expected a call to sdk.buildApprovedNamespaces") - // args = sdk.buildApprovedNamespacesCalls[1] - // verify(!!args.supportedNamespaces, "expected supportedNamespaces to be set") - // // We test here that only one account for all chains is provided - // let accountsForApproval = args.supportedNamespaces.eip155.accounts - // compare(accountsForApproval.length, networksArray.length, "expect only one account per chain") - // compare(accountsForApproval[0], `eip155:${networksArray[0]}:${selectedAccount.address}`) - // compare(accountsForApproval[1], `eip155:${networksArray[1]}:${selectedAccount.address}`) - - // let approvedNamespaces = JSON.parse(Testing.formatBuildApprovedNamespacesResult(networksArray, [selectedAccount.address])) - // sdk.buildApprovedNamespacesResult(approvedNamespaces, "") - - // compare(sdk.approveSessionCalls.length, 1, "expected a call to sdk.approveSession") - // verify(!!sdk.approveSessionCalls[0].sessionProposalJson, "expected sessionProposalJson to be set") - // verify(!!sdk.approveSessionCalls[0].approvedNamespaces, "expected approvedNamespaces to be set") - - // let finalApprovedNamespaces = JSON.parse(Testing.formatApproveSessionResponse(networksArray, [selectedAccount.address])) - // sdk.approveSessionResult(finalApprovedNamespaces, "") - // verify(store.addWalletConnectSessionCalls.length === 1) - // verify(store.addWalletConnectSessionCalls[0].sessionJson, "expected sessionJson to be set") - - // verify(service.onApproveSessionResultTriggers.length === 1) - // verify(service.onApproveSessionResultTriggers[0].session, "expected session to be set") - - // compare(service.onDisplayToastMessageTriggers.length, 1, "expected a success message to be displayed") - // verify(!service.onDisplayToastMessageTriggers[0].error, "expected no error") - // verify(service.onDisplayToastMessageTriggers[0].message, "expected message to be set") - // } - - // function test_SessionRequestMainFlow() { - // // All calls to SDK are expected as events to be made by the wallet connect SDK - // let sdk = service.wcSDK - // let walletStore = service.walletStore - // let store = service.store - - // let testAddress = "0x3a" - // let chainId = 2 - // let method = "personal_sign" - // let message = "hello world" - // let params = [Helpers.strToHex(message), testAddress] - // let topic = "b536a" - // let session = JSON.parse(Testing.formatSessionRequest(chainId, method, params, topic)) - // // Expect to have calls to getActiveSessions from service initialization - // let prevRequests = sdk.getActiveSessionsCallbacks.length - // sdk.sessionRequestEvent(session) - - // compare(sdk.getActiveSessionsCallbacks.length, prevRequests + 1, "expected DAppsRequestHandler call sdk.getActiveSessions") - // let callback = sdk.getActiveSessionsCallbacks[prevRequests].callback - // callback({"b536a": JSON.parse(Testing.formatApproveSessionResponse([chainId, 7], [testAddress]))}) - - // compare(sessionRequestSpy.count, 1, "expected service.sessionRequest trigger") - // let request = sessionRequestSpy.signalArguments[0][sessionRequestSpy.argPos.request] - // compare(request.topic, topic, "expected topic to be set") - // compare(request.method, method, "expected method to be set") - // compare(request.event, session, "expected event to be the one sent by the sdk") - // compare(request.dappName, Testing.dappName, "expected dappName to be set") - // compare(request.dappUrl, Testing.dappUrl, "expected dappUrl to be set") - // compare(request.dappIcon, Testing.dappFirstIcon, "expected dappIcon to be set") - // verify(!!request.account, "expected account to be set") - // compare(request.account.address, testAddress, "expected look up of the right account") - // verify(!!request.network, "expected network to be set") - // compare(request.network.chainId, chainId, "expected look up of the right network") - // verify(!!request.data, "expected data to be set") - // compare(request.data.message, message, "expected message to be set") - // } - - // // TODO #14757: add tests with multiple session requests coming in; validate that authentication is serialized and in order - // // function tst_SessionRequestQueueMultiple() { - // // } - // } - - // Component { - // id: dappsListProviderComponent - // DAppsListProvider { - // } - // } - - // TestCase { - // name: "DAppsListProvider" - - // property DAppsListProvider provider: null - - // readonly property var dappsListReceivedJsonStr: '[{"url":"https://tst1.com","name":"name1","iconUrl":"https://tst1.com/u/1"},{"url":"https://tst2.com","name":"name2","iconUrl":"https://tst2.com/u/2"}]' - - // function init() { - // // Simulate the SDK not being ready - // let sdk = createTemporaryObject(sdkComponent, root, {projectId: "12ab", sdkReady: false}) - // verify(!!sdk) - // let store = createTemporaryObject(dappsStoreComponent, root, { - // dappsListReceivedJsonStr: dappsListReceivedJsonStr - // }) - // verify(!!store) - // provider = createTemporaryObject(dappsListProviderComponent, root, {sdk: sdk, store: store}) - // verify(!!provider) - // } - - // function cleanup() { - // } - - // // Implemented as a regression to metamask not having icons which failed dapps list - // function test_TestUpdateDapps() { - // provider.updateDapps() - - // // Validate that persistance fallback is working - // compare(provider.dappsModel.count, 2, "expected dappsModel have the right number of elements") - // let persistanceList = JSON.parse(dappsListReceivedJsonStr) - // compare(provider.dappsModel.get(0).url, persistanceList[0].url, "expected url to be set") - // compare(provider.dappsModel.get(0).iconUrl, persistanceList[0].iconUrl, "expected iconUrl to be set") - // compare(provider.dappsModel.get(1).name, persistanceList[1].name, "expected name to be set") - - // // Validate that SDK's `getActiveSessions` is not called if not ready - // let sdk = provider.sdk - // compare(sdk.getActiveSessionsCallbacks.length, 0, "expected no calls to sdk.getActiveSessions yet") - // sdk.sdkReady = true - // compare(sdk.getActiveSessionsCallbacks.length, 1, "expected a call to sdk.getActiveSessions when SDK becomes ready") - // let callback = sdk.getActiveSessionsCallbacks[0].callback - // let session = JSON.parse(Testing.formatApproveSessionResponse([1, 2], ["0x1"], {dappMetadataJsonString: Testing.noIconsDappMetadataJsonString})) - // callback({"b536a": session, "b537b": session}) - // compare(provider.dappsModel.count, 1, "expected dappsModel have the SDK's reported dapps") - // compare(provider.dappsModel.get(0).iconUrl, "", "expected iconUrl to be missing") - // } - // } - - // TestCase { - // name: "ServiceHelpers" - - // function test_extractChainsAndAccountsFromApprovedNamespaces() { - // let res = Helpers.extractChainsAndAccountsFromApprovedNamespaces(JSON.parse(`{ - // "eip155": { - // "accounts": [ - // "eip155:1:0x1", - // "eip155:1:0x2", - // "eip155:2:0x1", - // "eip155:2:0x2" - // ], - // "chains": [ - // "eip155:1", - // "eip155:2" - // ], - // "events": [ - // "accountsChanged", - // "chainChanged" - // ], - // "methods": [ - // "eth_sendTransaction", - // "personal_sign" - // ] - // } - // }`)) - // verify(res.chains.length === 2) - // verify(res.accounts.length === 2) - // verify(res.chains[0] === 1) - // verify(res.chains[1] === 2) - // verify(res.accounts[0] === "0x1") - // verify(res.accounts[1] === "0x2") - // } - - // readonly property ListModel chainsModel: ListModel { - // ListElement { chainId: 1 } - // ListElement { chainId: 2 } - // } - - // readonly property ListModel accountsModel: ListModel { - // ListElement { address: "0x1" } - // ListElement { address: "0x2" } - // } - - // function test_buildSupportedNamespacesFromModels() { - // let methods = ["eth_sendTransaction", "personal_sign"] - // let resStr = Helpers.buildSupportedNamespacesFromModels(chainsModel, accountsModel, methods) - // let jsonObj = JSON.parse(resStr) - // verify(jsonObj.hasOwnProperty("eip155")) - // let eip155 = jsonObj.eip155 - - // verify(eip155.hasOwnProperty("chains")) - // let chains = eip155.chains - // verify(chains.length === 2) - // verify(chains[0] === "eip155:1") - // verify(chains[1] === "eip155:2") - - // verify(eip155.hasOwnProperty("accounts")) - // let accounts = eip155.accounts - // verify(accounts.length === 4) - // for (let chainI = 0; chainI < chainsModel.count; chainI++) { - // for (let accountI = 0; accountI < chainsModel.count; accountI++) { - // var found = false - // for (let entry of accounts) { - // if(entry === `eip155:${chainsModel.get(chainI).chainId}:${accountsModel.get(accountI).address}`) { - // found = true - // break - // } - // } - // verify(found, `found ${accountsModel.get(accountI).address} for chain ${chainsModel.get(chainI).chainId}`) - // } - // } - - // verify(eip155.hasOwnProperty("methods")) - // verify(eip155.methods.length > 0) - // verify(eip155.hasOwnProperty("events")) - // verify(eip155.events.length > 0) - // } - // } - - // Component { - // id: componentUnderTest - // DAppsWorkflow { - // } - // } - - // // TODO #15151: this TestCase if placed before ServiceHelpers was not run with `when: windowShown`. Check if related to the CI crash - // TestCase { - // id: dappsWorkflowTest - - // name: "DAppsWorkflow" - // when: windowShown - - // property DAppsWorkflow controlUnderTest: null - - // SignalSpy { - // id: dappsListReadySpy - // target: dappsWorkflowTest.controlUnderTest - // signalName: "dappsListReady" - // } - - // SignalSpy { - // id: pairWCReadySpy - // target: dappsWorkflowTest.controlUnderTest - // signalName: "pairWCReady" - // } - - // function init() { - // let walletStore = createTemporaryObject(walletStoreComponent, root) - // verify(!!walletStore) - // let sdk = createTemporaryObject(sdkComponent, root, { projectId: "12ab" }) - // verify(!!sdk) - // let store = createTemporaryObject(dappsStoreComponent, root) - // verify(!!store) - // let service = createTemporaryObject(serviceComponent, root, {wcSDK: sdk, store: store, walletStore: walletStore}) - // verify(!!service) - // controlUnderTest = createTemporaryObject(componentUnderTest, root, {wcService: service}) - // verify(!!controlUnderTest) - // } - - // function cleanup() { - // dappsListReadySpy.clear() - // pairWCReadySpy.clear() - // } - - // function test_OpenAndCloseDappList() { - // waitForRendering(controlUnderTest) - - // compare(dappsListReadySpy.count, 0, "expected NO dappsListReady signal to be emitted") - // mouseClick(controlUnderTest) - // waitForRendering(controlUnderTest) - // compare(dappsListReadySpy.count, 1, "expected dappsListReady signal to be emitted") - - // let popup = findChild(controlUnderTest, "dappsListPopup") - // verify(!!popup) - // verify(popup.opened) - - // popup.close() - // waitForRendering(controlUnderTest) - - // verify(!popup.opened) - // } - - // function test_OpenPairModal() { - // waitForRendering(controlUnderTest) - - // mouseClick(controlUnderTest) - // waitForRendering(controlUnderTest) - - // let popup = findChild(controlUnderTest, "dappsListPopup") - // verify(!!popup) - // verify(popup.opened) - - // let connectButton = findChild(popup, "connectDappButton") - // verify(!!connectButton) - - // verify(pairWCReadySpy.count === 0, "expected NO pairWCReady signal to be emitted") - // mouseClick(connectButton) - // waitForRendering(controlUnderTest) - // verify(pairWCReadySpy.count === 1, "expected pairWCReady signal to be emitted") - - // let pairWCModal = findChild(controlUnderTest, "pairWCModal") - // verify(!!pairWCModal) - // } - - // Component { - // id: sessionRequestComponent - - // SessionRequestResolved { - // } - // } - - // function test_OpenDappRequestModal() { - // waitForRendering(controlUnderTest) - - // let service = controlUnderTest.wcService - // let td = mockSessionRequestEvent(this, service.wcSDK, service.walletStore) - - // waitForRendering(controlUnderTest) - // let popup = findChild(controlUnderTest, "dappsRequestModal") - // verify(!!popup) - // verify(popup.opened) - // verify(popup.visible) - - // compare(popup.dappName, td.session.peer.metadata.name) - // compare(popup.account.name, td.account.name) - // compare(popup.account.address, td.account.address) - // compare(popup.network.chainId, td.network.chainId) - - // popup.close() - // waitForRendering(controlUnderTest) - // verify(!popup.opened) - // verify(!popup.visible) - // } - - // function test_RejectDappRequestModal() { - // waitForRendering(controlUnderTest) - - // let service = controlUnderTest.wcService - // let td = mockSessionRequestEvent(this, service.wcSDK, service.walletStore) - - // waitForRendering(controlUnderTest) - // let popup = findChild(controlUnderTest, "dappsRequestModal") - // verify(popup.opened) - - // let rejectButton = findChild(popup, "rejectButton") - - // mouseClick(rejectButton) - // compare(td.sdk.rejectSessionRequestCalls.length, 1, "expected a call to service.rejectSessionRequest") - // let args = td.sdk.rejectSessionRequestCalls[0] - // compare(args.topic, td.topic, "expected topic to be set") - // compare(args.id, td.request.id, "expected id to be set") - // compare(args.error, false, "expected no error; it was user rejected") - - // waitForRendering(controlUnderTest) - // verify(!popup.opened) - // verify(!popup.visible) - // } - // } - - // function mockSessionRequestEvent(tc, sdk, walletStore) { - // let account = walletStore.accounts.get(1) - // let network = walletStore.flatNetworks.get(1) - // let method = "personal_sign" - // let message = "hello world" - // let params = [Helpers.strToHex(message), account.address] - // let topic = "b536a" - // let requestEvent = JSON.parse(Testing.formatSessionRequest(network.chainId, method, params, topic)) - // let request = tc.createTemporaryObject(sessionRequestComponent, root, { - // event: requestEvent, - // topic, - // id: requestEvent.id, - // method: Constants.personal_sign, - // account, - // network, - // data: message - // }) - // // Expect to have calls to getActiveSessions from service initialization - // let prevRequests = sdk.getActiveSessionsCallbacks.length - // sdk.sessionRequestEvent(requestEvent) - // // Service might trigger a sessionRequest event following the getActiveSessions call - // let callback = sdk.getActiveSessionsCallbacks[prevRequests].callback - // let session = JSON.parse(Testing.formatApproveSessionResponse([network.chainId, 7], [account.address])) - // callback({"b536a": session}) - - // return {sdk, session, account, network, topic, request} - // } + Component { + id: sdkComponent + + WalletConnectSDKBase { + property bool sdkReady: true + + property var getActiveSessionsCallbacks: [] + getActiveSessions: function(callback) { + getActiveSessionsCallbacks.push({callback}) + } + + property int pairCalled: 0 + pair: function() { + pairCalled++ + } + + property var buildApprovedNamespacesCalls: [] + buildApprovedNamespaces: function(params, supportedNamespaces) { + buildApprovedNamespacesCalls.push({params, supportedNamespaces}) + } + + property var approveSessionCalls: [] + approveSession: function(sessionProposalJson, approvedNamespaces) { + approveSessionCalls.push({sessionProposalJson, approvedNamespaces}) + } + + property var rejectSessionRequestCalls: [] + rejectSessionRequest: function(topic, id, error) { + rejectSessionRequestCalls.push({topic, id, error}) + } + } + } + + Component { + id: serviceComponent + + WalletConnectService { + property var onApproveSessionResultTriggers: [] + onApproveSessionResult: function(session, error) { + onApproveSessionResultTriggers.push({session, error}) + } + + property var onDisplayToastMessageTriggers: [] + onDisplayToastMessage: function(message, error) { + onDisplayToastMessageTriggers.push({message, error}) + } + } + } + + Component { + id: dappsStoreComponent + + + DAppsStore { + property string dappsListReceivedJsonStr: '[]' + + signal dappsListReceived(string dappsJson) + signal userAuthenticated(string topic, string id, string password, string pin) + signal userAuthenticationFailed(string topic, string id) + + // By default, return no dapps in store + function getDapps() { + dappsListReceived(dappsListReceivedJsonStr) + return true + } + + property var addWalletConnectSessionCalls: [] + function addWalletConnectSession(sessionJson) { + addWalletConnectSessionCalls.push({sessionJson}) + } + + property var authenticateUserCalls: [] + function authenticateUser(topic, id, address) { + authenticateUserCalls.push({topic, id, address}) + } + + property var signMessageCalls: [] + function signMessage(topic, id, address, password, message) { + signMessageCalls.push({topic, id, address, password, message}) + } + property var signTypedDataV4Calls: [] + function signTypedDataV4(topic, id, address, password, message) { + signTypedDataV4Calls.push({topic, id, address, password, message}) + } + } + } + + Component { + id: walletStoreComponent + + WalletStore { + readonly property ListModel flatNetworks: ListModel { + ListElement { chainId: 1 } + ListElement { + chainId: 2 + chainName: "Test Chain" + iconUrl: "network/Network=Ethereum" + } + } + + readonly property ListModel accounts: ListModel { + ListElement {address: "0x1"} + ListElement { + address: "0x2" + name: "helloworld" + emoji: "😋" + color: "#2A4AF5" + } + ListElement { address: "0x3a" } + } + readonly property ListModel ownAccounts: accounts + + + } + } + + Component { + id: dappsRequestHandlerComponent + + DAppsRequestHandler { + } + } + + TestCase { + id: requestHandlerTest + name: "DAppsRequestHandler" + + property DAppsRequestHandler handler: null + + SignalSpy { + id: displayToastMessageSpy + target: requestHandlerTest.handler + signalName: "onDisplayToastMessage" + } + + function init() { + let walletStore = createTemporaryObject(walletStoreComponent, root) + verify(!!walletStore) + let sdk = createTemporaryObject(sdkComponent, root, { projectId: "12ab" }) + verify(!!sdk) + let store = createTemporaryObject(dappsStoreComponent, root) + verify(!!store) + handler = createTemporaryObject(dappsRequestHandlerComponent, root, {sdk: sdk, store: store, walletStore: walletStore}) + verify(!!handler) + } + + function cleanup() { + displayToastMessageSpy.clear() + } + + function test_TestAuthentication() { + let td = mockSessionRequestEvent(this, handler.sdk, handler.walletStore) + handler.authenticate(td.request) + compare(handler.store.authenticateUserCalls.length, 1, "expected a call to store.authenticateUser") + + let store = handler.store + store.userAuthenticated(td.topic, td.request.id, "password", "") + compare(store.signMessageCalls.length, 1, "expected a call to store.signMessage") + compare(store.signMessageCalls[0].message, td.request.data) + } + + function test_onSessionRequestEventDifferentCaseForAddress() { + let sdk = handler.sdk + + let testAddressUpper = "0x3A" + let chainId = 2 + let method = "personal_sign" + let message = "hello world" + let params = [Helpers.strToHex(message), testAddressUpper] + let topic = "b536a" + let session = JSON.parse(Testing.formatSessionRequest(chainId, method, params, topic)) + // Expect to have calls to getActiveSessions from service initialization + let prevRequests = sdk.getActiveSessionsCallbacks.length + sdk.sessionRequestEvent(session) + + compare(sdk.getActiveSessionsCallbacks.length, 1, "expected DAppsRequestHandler call sdk.getActiveSessions") + } + } + + TestCase { + id: walletConnectServiceTest + name: "WalletConnectService" + + property WalletConnectService service: null + + SignalSpy { + id: connectDAppSpy + target: walletConnectServiceTest.service + signalName: "connectDApp" + + property var argPos: { + "dappChains": 0, + "sessionProposalJson": 1, + "availableNamespaces": 0 + } + } + + SignalSpy { + id: sessionRequestSpy + target: walletConnectServiceTest.service + signalName: "sessionRequest" + + property var argPos: { + "request": 0 + } + } + + function init() { + let walletStore = createTemporaryObject(walletStoreComponent, root) + verify(!!walletStore) + let sdk = createTemporaryObject(sdkComponent, root, { projectId: "12ab" }) + verify(!!sdk) + let store = createTemporaryObject(dappsStoreComponent, root) + verify(!!store) + service = createTemporaryObject(serviceComponent, root, {wcSDK: sdk, store: store, walletStore: walletStore}) + verify(!!service) + } + + function cleanup() { + connectDAppSpy.clear() + sessionRequestSpy.clear() + } + + function test_TestPairing() { + // All calls to SDK are expected as events to be made by the wallet connect SDK + let sdk = service.wcSDK + let walletStore = service.walletStore + let store = service.store + + service.pair("wc:12ab@1?bridge=https%3A%2F%2Fbridge.walletconnect.org&key=12ab") + compare(sdk.pairCalled, 1, "expected a call to sdk.pair") + + sdk.sessionProposal(JSON.parse(Testing.formatSessionProposal())) + compare(sdk.buildApprovedNamespacesCalls.length, 1, "expected a call to sdk.buildApprovedNamespaces") + var args = sdk.buildApprovedNamespacesCalls[0] + verify(!!args.supportedNamespaces, "expected supportedNamespaces to be set") + let chainsForApproval = args.supportedNamespaces.eip155.chains + let networksArray = ModelUtils.modelToArray(walletStore.flatNetworks).map(entry => entry.chainId) + verify(networksArray.every(chainId => chainsForApproval.some(eip155Chain => eip155Chain === `eip155:${chainId}`)), + "expect all the networks to be present") + // We test here all accounts for one chain only, we have separate tests to validate that all accounts are present + let allAccountsForApproval = args.supportedNamespaces.eip155.accounts + let accountsArray = ModelUtils.modelToArray(walletStore.accounts).map(entry => entry.address) + verify(accountsArray.every(address => allAccountsForApproval.some(eip155Address => eip155Address === `eip155:${networksArray[0]}:${address}`)), + "expect at least all accounts for the first chain to be present" + ) + + let allApprovedNamespaces = JSON.parse(Testing.formatBuildApprovedNamespacesResult(networksArray, accountsArray)) + sdk.buildApprovedNamespacesResult(allApprovedNamespaces, "") + compare(connectDAppSpy.count, 1, "expected a call to service.connectDApp") + let connectArgs = connectDAppSpy.signalArguments[0] + compare(connectArgs[connectDAppSpy.argPos.dappChains], networksArray, "expected all provided networks (walletStore.flatNetworks) for the dappChains") + verify(!!connectArgs[connectDAppSpy.argPos.sessionProposalJson], "expected sessionProposalJson to be set") + verify(!!connectArgs[connectDAppSpy.argPos.availableNamespaces], "expected availableNamespaces to be set") + + let selectedAccount = walletStore.accounts.get(1) + service.approvePairSession(connectArgs[connectDAppSpy.argPos.sessionProposalJson], connectArgs[connectDAppSpy.argPos.dappChains], selectedAccount) + compare(sdk.buildApprovedNamespacesCalls.length, 2, "expected a call to sdk.buildApprovedNamespaces") + args = sdk.buildApprovedNamespacesCalls[1] + verify(!!args.supportedNamespaces, "expected supportedNamespaces to be set") + // We test here that only one account for all chains is provided + let accountsForApproval = args.supportedNamespaces.eip155.accounts + compare(accountsForApproval.length, networksArray.length, "expect only one account per chain") + compare(accountsForApproval[0], `eip155:${networksArray[0]}:${selectedAccount.address}`) + compare(accountsForApproval[1], `eip155:${networksArray[1]}:${selectedAccount.address}`) + + let approvedNamespaces = JSON.parse(Testing.formatBuildApprovedNamespacesResult(networksArray, [selectedAccount.address])) + sdk.buildApprovedNamespacesResult(approvedNamespaces, "") + + compare(sdk.approveSessionCalls.length, 1, "expected a call to sdk.approveSession") + verify(!!sdk.approveSessionCalls[0].sessionProposalJson, "expected sessionProposalJson to be set") + verify(!!sdk.approveSessionCalls[0].approvedNamespaces, "expected approvedNamespaces to be set") + + let finalApprovedNamespaces = JSON.parse(Testing.formatApproveSessionResponse(networksArray, [selectedAccount.address])) + sdk.approveSessionResult(finalApprovedNamespaces, "") + verify(store.addWalletConnectSessionCalls.length === 1) + verify(store.addWalletConnectSessionCalls[0].sessionJson, "expected sessionJson to be set") + + verify(service.onApproveSessionResultTriggers.length === 1) + verify(service.onApproveSessionResultTriggers[0].session, "expected session to be set") + + compare(service.onDisplayToastMessageTriggers.length, 1, "expected a success message to be displayed") + verify(!service.onDisplayToastMessageTriggers[0].error, "expected no error") + verify(service.onDisplayToastMessageTriggers[0].message, "expected message to be set") + } + + function test_SessionRequestMainFlow() { + // All calls to SDK are expected as events to be made by the wallet connect SDK + let sdk = service.wcSDK + let walletStore = service.walletStore + let store = service.store + + let testAddress = "0x3a" + let chainId = 2 + let method = "personal_sign" + let message = "hello world" + let params = [Helpers.strToHex(message), testAddress] + let topic = "b536a" + let session = JSON.parse(Testing.formatSessionRequest(chainId, method, params, topic)) + // Expect to have calls to getActiveSessions from service initialization + let prevRequests = sdk.getActiveSessionsCallbacks.length + sdk.sessionRequestEvent(session) + + compare(sdk.getActiveSessionsCallbacks.length, prevRequests + 1, "expected DAppsRequestHandler call sdk.getActiveSessions") + let callback = sdk.getActiveSessionsCallbacks[prevRequests].callback + callback({"b536a": JSON.parse(Testing.formatApproveSessionResponse([chainId, 7], [testAddress]))}) + + compare(sessionRequestSpy.count, 1, "expected service.sessionRequest trigger") + let request = sessionRequestSpy.signalArguments[0][sessionRequestSpy.argPos.request] + compare(request.topic, topic, "expected topic to be set") + compare(request.method, method, "expected method to be set") + compare(request.event, session, "expected event to be the one sent by the sdk") + compare(request.dappName, Testing.dappName, "expected dappName to be set") + compare(request.dappUrl, Testing.dappUrl, "expected dappUrl to be set") + compare(request.dappIcon, Testing.dappFirstIcon, "expected dappIcon to be set") + verify(!!request.account, "expected account to be set") + compare(request.account.address, testAddress, "expected look up of the right account") + verify(!!request.network, "expected network to be set") + compare(request.network.chainId, chainId, "expected look up of the right network") + verify(!!request.data, "expected data to be set") + compare(request.data.message, message, "expected message to be set") + } + + // TODO #14757: add tests with multiple session requests coming in; validate that authentication is serialized and in order + // function tst_SessionRequestQueueMultiple() { + // } + } + + Component { + id: dappsListProviderComponent + DAppsListProvider { + } + } + + TestCase { + name: "DAppsListProvider" + + property DAppsListProvider provider: null + + readonly property var dappsListReceivedJsonStr: '[{"url":"https://tst1.com","name":"name1","iconUrl":"https://tst1.com/u/1"},{"url":"https://tst2.com","name":"name2","iconUrl":"https://tst2.com/u/2"}]' + + function init() { + // Simulate the SDK not being ready + let sdk = createTemporaryObject(sdkComponent, root, {projectId: "12ab", sdkReady: false}) + verify(!!sdk) + let store = createTemporaryObject(dappsStoreComponent, root, { + dappsListReceivedJsonStr: dappsListReceivedJsonStr + }) + verify(!!store) + provider = createTemporaryObject(dappsListProviderComponent, root, {sdk: sdk, store: store}) + verify(!!provider) + } + + function cleanup() { + } + + // Implemented as a regression to metamask not having icons which failed dapps list + function test_TestUpdateDapps() { + provider.updateDapps() + + // Validate that persistance fallback is working + compare(provider.dappsModel.count, 2, "expected dappsModel have the right number of elements") + let persistanceList = JSON.parse(dappsListReceivedJsonStr) + compare(provider.dappsModel.get(0).url, persistanceList[0].url, "expected url to be set") + compare(provider.dappsModel.get(0).iconUrl, persistanceList[0].iconUrl, "expected iconUrl to be set") + compare(provider.dappsModel.get(1).name, persistanceList[1].name, "expected name to be set") + + // Validate that SDK's `getActiveSessions` is not called if not ready + let sdk = provider.sdk + compare(sdk.getActiveSessionsCallbacks.length, 0, "expected no calls to sdk.getActiveSessions yet") + sdk.sdkReady = true + compare(sdk.getActiveSessionsCallbacks.length, 1, "expected a call to sdk.getActiveSessions when SDK becomes ready") + let callback = sdk.getActiveSessionsCallbacks[0].callback + let session = JSON.parse(Testing.formatApproveSessionResponse([1, 2], ["0x1"], {dappMetadataJsonString: Testing.noIconsDappMetadataJsonString})) + callback({"b536a": session, "b537b": session}) + compare(provider.dappsModel.count, 1, "expected dappsModel have the SDK's reported dapps") + compare(provider.dappsModel.get(0).iconUrl, "", "expected iconUrl to be missing") + } + } + + TestCase { + name: "ServiceHelpers" + + function test_extractChainsAndAccountsFromApprovedNamespaces() { + let res = Helpers.extractChainsAndAccountsFromApprovedNamespaces(JSON.parse(`{ + "eip155": { + "accounts": [ + "eip155:1:0x1", + "eip155:1:0x2", + "eip155:2:0x1", + "eip155:2:0x2" + ], + "chains": [ + "eip155:1", + "eip155:2" + ], + "events": [ + "accountsChanged", + "chainChanged" + ], + "methods": [ + "eth_sendTransaction", + "personal_sign" + ] + } + }`)) + verify(res.chains.length === 2) + verify(res.accounts.length === 2) + verify(res.chains[0] === 1) + verify(res.chains[1] === 2) + verify(res.accounts[0] === "0x1") + verify(res.accounts[1] === "0x2") + } + + readonly property ListModel chainsModel: ListModel { + ListElement { chainId: 1 } + ListElement { chainId: 2 } + } + + readonly property ListModel accountsModel: ListModel { + ListElement { address: "0x1" } + ListElement { address: "0x2" } + } + + function test_buildSupportedNamespacesFromModels() { + let methods = ["eth_sendTransaction", "personal_sign"] + let resStr = Helpers.buildSupportedNamespacesFromModels(chainsModel, accountsModel, methods) + let jsonObj = JSON.parse(resStr) + verify(jsonObj.hasOwnProperty("eip155")) + let eip155 = jsonObj.eip155 + + verify(eip155.hasOwnProperty("chains")) + let chains = eip155.chains + verify(chains.length === 2) + verify(chains[0] === "eip155:1") + verify(chains[1] === "eip155:2") + + verify(eip155.hasOwnProperty("accounts")) + let accounts = eip155.accounts + verify(accounts.length === 4) + for (let chainI = 0; chainI < chainsModel.count; chainI++) { + for (let accountI = 0; accountI < chainsModel.count; accountI++) { + var found = false + for (let entry of accounts) { + if(entry === `eip155:${chainsModel.get(chainI).chainId}:${accountsModel.get(accountI).address}`) { + found = true + break + } + } + verify(found, `found ${accountsModel.get(accountI).address} for chain ${chainsModel.get(chainI).chainId}`) + } + } + + verify(eip155.hasOwnProperty("methods")) + verify(eip155.methods.length > 0) + verify(eip155.hasOwnProperty("events")) + verify(eip155.events.length > 0) + } + } + + Component { + id: componentUnderTest + DAppsWorkflow { + } + } + + // TODO #15151: this TestCase if placed before ServiceHelpers was not run with `when: windowShown`. Check if related to the CI crash + TestCase { + id: dappsWorkflowTest + + name: "DAppsWorkflow" + when: windowShown + + property DAppsWorkflow controlUnderTest: null + + SignalSpy { + id: dappsListReadySpy + target: dappsWorkflowTest.controlUnderTest + signalName: "dappsListReady" + } + + SignalSpy { + id: pairWCReadySpy + target: dappsWorkflowTest.controlUnderTest + signalName: "pairWCReady" + } + + function init() { + let walletStore = createTemporaryObject(walletStoreComponent, root) + verify(!!walletStore) + let sdk = createTemporaryObject(sdkComponent, root, { projectId: "12ab" }) + verify(!!sdk) + let store = createTemporaryObject(dappsStoreComponent, root) + verify(!!store) + let service = createTemporaryObject(serviceComponent, root, {wcSDK: sdk, store: store, walletStore: walletStore}) + verify(!!service) + controlUnderTest = createTemporaryObject(componentUnderTest, root, {wcService: service}) + verify(!!controlUnderTest) + } + + function cleanup() { + dappsListReadySpy.clear() + pairWCReadySpy.clear() + } + + function test_OpenAndCloseDappList() { + waitForRendering(controlUnderTest) + + compare(dappsListReadySpy.count, 0, "expected NO dappsListReady signal to be emitted") + mouseClick(controlUnderTest) + waitForRendering(controlUnderTest) + compare(dappsListReadySpy.count, 1, "expected dappsListReady signal to be emitted") + + let popup = findChild(controlUnderTest, "dappsListPopup") + verify(!!popup) + verify(popup.opened) + + popup.close() + waitForRendering(controlUnderTest) + + verify(!popup.opened) + } + + function test_OpenPairModal() { + waitForRendering(controlUnderTest) + + mouseClick(controlUnderTest) + waitForRendering(controlUnderTest) + + let popup = findChild(controlUnderTest, "dappsListPopup") + verify(!!popup) + verify(popup.opened) + + let connectButton = findChild(popup, "connectDappButton") + verify(!!connectButton) + + verify(pairWCReadySpy.count === 0, "expected NO pairWCReady signal to be emitted") + mouseClick(connectButton) + waitForRendering(controlUnderTest) + verify(pairWCReadySpy.count === 1, "expected pairWCReady signal to be emitted") + + let pairWCModal = findChild(controlUnderTest, "pairWCModal") + verify(!!pairWCModal) + } + + Component { + id: sessionRequestComponent + + SessionRequestResolved { + } + } + + function test_OpenDappRequestModal() { + waitForRendering(controlUnderTest) + + let service = controlUnderTest.wcService + let td = mockSessionRequestEvent(this, service.wcSDK, service.walletStore) + + waitForRendering(controlUnderTest) + let popup = findChild(controlUnderTest, "dappsRequestModal") + verify(!!popup) + verify(popup.opened) + verify(popup.visible) + + compare(popup.dappName, td.session.peer.metadata.name) + compare(popup.account.name, td.account.name) + compare(popup.account.address, td.account.address) + compare(popup.network.chainId, td.network.chainId) + + popup.close() + waitForRendering(controlUnderTest) + verify(!popup.opened) + verify(!popup.visible) + } + + function test_RejectDappRequestModal() { + waitForRendering(controlUnderTest) + + let service = controlUnderTest.wcService + let td = mockSessionRequestEvent(this, service.wcSDK, service.walletStore) + + waitForRendering(controlUnderTest) + let popup = findChild(controlUnderTest, "dappsRequestModal") + verify(popup.opened) + + let rejectButton = findChild(popup, "rejectButton") + + mouseClick(rejectButton) + compare(td.sdk.rejectSessionRequestCalls.length, 1, "expected a call to service.rejectSessionRequest") + let args = td.sdk.rejectSessionRequestCalls[0] + compare(args.topic, td.topic, "expected topic to be set") + compare(args.id, td.request.id, "expected id to be set") + compare(args.error, false, "expected no error; it was user rejected") + + waitForRendering(controlUnderTest) + verify(!popup.opened) + verify(!popup.visible) + } + } + + function mockSessionRequestEvent(tc, sdk, walletStore) { + let account = walletStore.accounts.get(1) + let network = walletStore.flatNetworks.get(1) + let method = "personal_sign" + let message = "hello world" + let params = [Helpers.strToHex(message), account.address] + let topic = "b536a" + let requestEvent = JSON.parse(Testing.formatSessionRequest(network.chainId, method, params, topic)) + let request = tc.createTemporaryObject(sessionRequestComponent, root, { + event: requestEvent, + topic, + id: requestEvent.id, + method: Constants.personal_sign, + account, + network, + data: message + }) + // Expect to have calls to getActiveSessions from service initialization + let prevRequests = sdk.getActiveSessionsCallbacks.length + sdk.sessionRequestEvent(requestEvent) + // Service might trigger a sessionRequest event following the getActiveSessions call + let callback = sdk.getActiveSessionsCallbacks[prevRequests].callback + let session = JSON.parse(Testing.formatApproveSessionResponse([network.chainId, 7], [account.address])) + callback({"b536a": session}) + + return {sdk, session, account, network, topic, request} + } } diff --git a/storybook/qmlTests/tests/tst_DappsComboBox.qml b/storybook/qmlTests/tests/tst_DappsComboBox.qml index 13efad2243..2daec2ea70 100644 --- a/storybook/qmlTests/tests/tst_DappsComboBox.qml +++ b/storybook/qmlTests/tests/tst_DappsComboBox.qml @@ -97,4 +97,4 @@ Item { compare(dappTooltip.visible, false) } } -} \ No newline at end of file +} diff --git a/ui/app/AppLayouts/Wallet/controls/DappsComboBox.qml b/ui/app/AppLayouts/Wallet/controls/DappsComboBox.qml index af08743c03..389d523cbb 100644 --- a/ui/app/AppLayouts/Wallet/controls/DappsComboBox.qml +++ b/ui/app/AppLayouts/Wallet/controls/DappsComboBox.qml @@ -5,6 +5,7 @@ import QtGraphicalEffects 1.15 import shared.controls 1.0 import shared.popups.walletconnect 1.0 +import shared.popups.walletconnect.controls 1.0 import StatusQ.Core 0.1 import StatusQ.Core.Theme 0.1 diff --git a/ui/imports/shared/popups/walletconnect/ConnectDAppModal.qml b/ui/imports/shared/popups/walletconnect/ConnectDAppModal.qml index 9f9c68a9ff..4b27038752 100644 --- a/ui/imports/shared/popups/walletconnect/ConnectDAppModal.qml +++ b/ui/imports/shared/popups/walletconnect/ConnectDAppModal.qml @@ -17,6 +17,7 @@ import StatusQ.Core.Theme 0.1 import shared.controls 1.0 // TODO extract the components to StatusQ import shared.popups.send.controls 1.0 +import shared.popups.walletconnect.controls 1.0 import AppLayouts.Wallet.controls 1.0 @@ -98,6 +99,13 @@ StatusDialog { DAppCard { id: dappCard + afterTwoSecondsFromStatus: d.afterTwoSecondsFromStatus + + isConnectedSuccessfully: d.connectionStatus === root.connectionSuccessfulStatus + isConnectionFailed: d.connectionStatus === root.connectionFailedStatus + isConnectionStarted: d.connectionStatus !== root.notConnectedStatus + isConnectionFailedOrDisconnected: d.connectionStatus !== root.connectionSuccessfulStatus + Layout.alignment: Qt.AlignHCenter Layout.leftMargin: 12 Layout.rightMargin: Layout.leftMargin @@ -107,6 +115,11 @@ StatusDialog { ContextCard { Layout.fillWidth: true + accountsProxy: d.accountsProxy + selectedAccount: d.selectedAccount + selectedChains: d.selectedChains + filteredChains: d.filteredChains + notConnected: d.connectionStatus === root.notConnectedStatus } PermissionsCard { @@ -156,221 +169,6 @@ StatusDialog { } } - component ContextCard: Rectangle { - id: contextCard - - implicitWidth: contextLayout.implicitWidth - implicitHeight: contextLayout.implicitHeight - - radius: 8 - // TODO: the color matched the design color (grey4); It is also matching the intention or we should add some another color to the theme? (e.g. sectionBorder)? - border.color: Theme.palette.baseColor2 - border.width: 1 - color: "transparent" - - ColumnLayout { - id: contextLayout - - anchors.fill: parent - - RowLayout { - Layout.margins: 16 - - StatusBaseText { - text: qsTr("Connect with") - - Layout.fillWidth: true - } - - AccountSelector { - id: accountsDropdown - - Layout.preferredWidth: 204 - - control.enabled: d.connectionStatus === root.notConnectedStatus && count > 1 - model: d.accountsProxy - onCurrentAccountChanged: d.selectedAccount = currentAccount - } - } - - Rectangle { - Layout.fillWidth: true - height: 1 - color: contextCard.border.color - } - - RowLayout { - Layout.margins: 16 - - StatusBaseText { - text: qsTr("On") - - Layout.fillWidth: true - } - - NetworkFilter { - id: networkFilter - Layout.preferredWidth: accountsDropdown.Layout.preferredWidth - - flatNetworks: d.filteredChains - showTitle: true - multiSelection: true - selectionAllowed: d.connectionStatus === root.notConnectedStatus && d.allChainIdsAggregator.value.length > 1 - selection: d.selectedChains - - onSelectionChanged: { - if (d.selectedChains !== networkFilter.selection) { - d.selectedChains = networkFilter.selection - } - } - } - } - } - } - - component DAppCard: ColumnLayout { - property alias name: appNameText.text - property alias url: appUrlText.text - property string iconUrl: "" - - Rectangle { - Layout.alignment: Qt.AlignHCenter - Layout.preferredWidth: 72 - Layout.preferredHeight: Layout.preferredWidth - - radius: width / 2 - color: Theme.palette.primaryColor3 - - StatusRoundedImage { - id: iconDisplay - - anchors.fill: parent - - visible: !fallbackImage.visible - - image.source: iconUrl - } - - StatusIcon { - id: fallbackImage - - anchors.centerIn: parent - - width: 40 - height: 40 - - icon: "dapp" - color: Theme.palette.primaryColor1 - - visible: iconDisplay.image.isLoading || iconDisplay.image.isError || !iconUrl - } - } - - StatusBaseText { - id: appNameText - - Layout.alignment: Qt.AlignHCenter - Layout.bottomMargin: 4 - - font.bold: true - font.pixelSize: 17 - } - - // TODO replace with the proper URL control - StatusLinkText { - id: appUrlText - - Layout.alignment: Qt.AlignHCenter - font.pixelSize: 15 - } - - Rectangle { - Layout.preferredWidth: pairingStatusLayout.implicitWidth + 32 - Layout.preferredHeight: pairingStatusLayout.implicitHeight + 14 - - Layout.alignment: Qt.AlignHCenter - Layout.topMargin: 16 - - visible: d.connectionStatus !== root.notConnectedStatus - - color: d.connectionStatus === root.connectionSuccessfulStatus - ? d.afterTwoSecondsFromStatus - ? Theme.palette.successColor2 - : Theme.palette.successColor3 - : d.afterTwoSecondsFromStatus - ? "transparent" - : Theme.palette.dangerColor3 - border.color: d.connectionStatus === root.connectionSuccessfulStatus - ? Theme.palette.successColor2 - : Theme.palette.dangerColor2 - border.width: 1 - radius: height / 2 - - RowLayout { - id: pairingStatusLayout - - anchors.centerIn: parent - - spacing: 8 - - Rectangle { - width: 6 - height: 6 - radius: width / 2 - - visible: d.connectionStatus === root.connectionSuccessfulStatus - color: Theme.palette.successColor1 - } - - StatusIcon { - Layout.preferredWidth: 16 - Layout.preferredHeight: 16 - - visible: d.connectionStatus !== root.connectionSuccessfulStatus - - color: Theme.palette.dangerColor1 - icon: "warning" - } - - StatusBaseText { - text: { - if (d.connectionStatus === root.connectionSuccessfulStatus) - return qsTr("Connected. You can now go back to the dApp.") - else if (d.connectionStatus === root.connectionFailedStatus) - return qsTr("Error connecting to dApp. Close and try again") - return "" - } - - font.pixelSize: 12 - color: d.connectionStatus === root.connectionSuccessfulStatus ? Theme.palette.directColor1 : Theme.palette.dangerColor1 - } - } - } - } - - component PermissionsCard: ColumnLayout { - spacing: 8 - - StatusBaseText { - text: qsTr("Uniswap Interface will be able to:") - - font.pixelSize: 13 - color: Theme.palette.baseColor1 - } - - StatusBaseText { - text: qsTr("Check your account balance and activity") - - font.pixelSize: 13 - } - - StatusBaseText { - text: qsTr("Request transactions and message signing") - - font.pixelSize: 13 - } - } - QtObject { id: d diff --git a/ui/imports/shared/popups/walletconnect/DAppRequestModal.qml b/ui/imports/shared/popups/walletconnect/DAppRequestModal.qml index 4a6acfed1d..df58361385 100644 --- a/ui/imports/shared/popups/walletconnect/DAppRequestModal.qml +++ b/ui/imports/shared/popups/walletconnect/DAppRequestModal.qml @@ -9,6 +9,7 @@ import StatusQ.Components 0.1 import StatusQ.Core.Theme 0.1 import StatusQ.Core.Utils 0.1 as StatusQ +import shared.popups.walletconnect.panels 1.0 import utils 1.0 import AppLayouts.Wallet.services.dapps.types 1.0 @@ -59,11 +60,15 @@ StatusDialog { dappName: root.dappName dappIcon: root.dappIcon account: root.account + + userDisplayNaming: d.userDisplayNaming } ContentPanel { Layout.fillWidth: true Layout.maximumHeight: 340 + + payloadToDisplay: d.payloadToDisplay } // TODO: externalize as a TargetPanel @@ -268,11 +273,14 @@ StatusDialog { leftButtons: ObjectModel { MaxFeesDisplay { + maxFeesText: root.maxFeesText + feesTextColor: root.enoughFunds ? Theme.palette.directColor1 : Theme.palette.dangerColor1 } Item { width: 20 } EstimatedTimeDisplay { + estimatedTimeText: root.estimatedTimeText visible: !!root.estimatedTimeText } } @@ -299,191 +307,6 @@ StatusDialog { } } - component MaxFeesDisplay: ColumnLayout { - StatusBaseText { - text: qsTr("Max fees:") - - font.pixelSize: 12 - color: Theme.palette.directColor1 - } - StatusBaseText { - id: maxFeesDisplay - text: root.maxFeesText - - visible: !!root.maxFeesText - - font.pixelSize: 16 - color: root.enoughFunds ? Theme.palette.directColor1 : Theme.palette.dangerColor1 - } - StatusBaseText { - text: qsTr("No fees") - - visible: !maxFeesDisplay.visible - - font.pixelSize: maxFeesDisplay.font.pixelSize - font.weight: maxFeesDisplay.font.weight - } - } - - component EstimatedTimeDisplay: ColumnLayout { - StatusBaseText { - text: qsTr("Est. time:") - font.pixelSize: 12 - color: Theme.palette.directColor1 - } - StatusBaseText { - text: root.estimatedTimeText - font.pixelSize: 16 - } - } - - component IntentionPanel: ColumnLayout { - spacing: 8 - - required property string dappName - required property url dappIcon - required property var account - - // 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 %2 with %3").arg(dappName).arg(d.userDisplayNaming).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 - - implicitHeight: contentScrollView.implicitHeight + (2 * contentText.anchors.margins) - - MouseArea { - anchors.fill: parent - cursorShape: contentScrollView.enabled || !enabled ? undefined : Qt.PointingHandCursor - enabled: contentScrollView.height < contentScrollView.contentHeight - - onClicked: { - contentScrollView.enabled = !contentScrollView.enabled - } - z: contentScrollView.z + 1 - } - - StatusScrollView { - id: contentScrollView - anchors.fill: parent - - contentWidth: availableWidth - contentHeight: contentText.contentHeight - - padding: 0 - - enabled: false - - StatusBaseText { - id: contentText - anchors.fill: parent - anchors.margins: 20 - - width: contentScrollView.availableWidth - - text: d.payloadToDisplay - - wrapMode: Text.WrapAnywhere - } - } - } - QtObject { id: d diff --git a/ui/imports/shared/popups/walletconnect/controls/ContextCard.qml b/ui/imports/shared/popups/walletconnect/controls/ContextCard.qml new file mode 100644 index 0000000000..d9c23778bf --- /dev/null +++ b/ui/imports/shared/popups/walletconnect/controls/ContextCard.qml @@ -0,0 +1,88 @@ +import QtQuick 2.15 +import QtQuick.Layouts 1.15 + +import StatusQ 0.1 +import StatusQ.Core 0.1 +import StatusQ.Core.Theme 0.1 + +import AppLayouts.Wallet.controls 1.0 + +import shared.controls 1.0 + +Rectangle { + id: root + + property var accountsProxy + property var selectedAccount + property var selectedChains + property var filteredChains + property bool notConnected: true + + implicitWidth: contextLayout.implicitWidth + implicitHeight: contextLayout.implicitHeight + + radius: 8 + // TODO: the color matched the design color (grey4); It is also matching the intention or we should add some another color to the theme? (e.g. sectionBorder)? + border.color: Theme.palette.baseColor2 + border.width: 1 + color: "transparent" + + ColumnLayout { + id: contextLayout + + anchors.fill: parent + + RowLayout { + Layout.margins: 16 + + StatusBaseText { + text: qsTr("Connect with") + + Layout.fillWidth: true + } + + AccountSelector { + id: accountsDropdown + + Layout.preferredWidth: 204 + + control.enabled: root.notConnected && count > 1 + model: accountsProxy + onCurrentAccountChanged: root.selectedAccount = currentAccount + } + } + + Rectangle { + Layout.fillWidth: true + height: 1 + color: root.border.color + } + + RowLayout { + Layout.margins: 16 + + StatusBaseText { + text: qsTr("On") + + Layout.fillWidth: true + } + + NetworkFilter { + id: networkFilter + Layout.preferredWidth: accountsDropdown.Layout.preferredWidth + + flatNetworks: root.filteredChains + showTitle: true + multiSelection: true + selectionAllowed: notConnected && root.selectedChains.length > 1 + selection: root.selectedChains + + onSelectionChanged: { + if (root.selectedChains !== networkFilter.selection) { + root.selectedChains = networkFilter.selection + } + } + } + } + } +} diff --git a/ui/imports/shared/popups/walletconnect/controls/DAppCard.qml b/ui/imports/shared/popups/walletconnect/controls/DAppCard.qml new file mode 100644 index 0000000000..7cdfe9ef3c --- /dev/null +++ b/ui/imports/shared/popups/walletconnect/controls/DAppCard.qml @@ -0,0 +1,137 @@ +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.Components 0.1 + +ColumnLayout { + id: root + + property alias name: appNameText.text + property alias url: appUrlText.text + property string iconUrl: "" + + property bool afterTwoSecondsFromStatus + property bool isConnectedSuccessfully: false + property bool isConnectionFailed: true + property bool isConnectionStarted: false + property bool isConnectionFailedOrDisconnected: true + + Rectangle { + Layout.alignment: Qt.AlignHCenter + Layout.preferredWidth: 72 + Layout.preferredHeight: Layout.preferredWidth + + radius: width / 2 + color: Theme.palette.primaryColor3 + + StatusRoundedImage { + id: iconDisplay + + anchors.fill: parent + + visible: !fallbackImage.visible + + image.source: iconUrl + } + + StatusIcon { + id: fallbackImage + + anchors.centerIn: parent + + width: 40 + height: 40 + + icon: "dapp" + color: Theme.palette.primaryColor1 + + visible: iconDisplay.image.isLoading || iconDisplay.image.isError || !iconUrl + } + } + + StatusBaseText { + id: appNameText + + Layout.alignment: Qt.AlignHCenter + Layout.bottomMargin: 4 + + font.bold: true + font.pixelSize: 17 + } + + // TODO replace with the proper URL control + StatusLinkText { + id: appUrlText + + Layout.alignment: Qt.AlignHCenter + font.pixelSize: 15 + } + + Rectangle { + Layout.preferredWidth: pairingStatusLayout.implicitWidth + 32 + Layout.preferredHeight: pairingStatusLayout.implicitHeight + 14 + + Layout.alignment: Qt.AlignHCenter + Layout.topMargin: 16 + + visible: root.isConnectionStarted + + color: root.isConnectedSuccessfully + ? root.afterTwoSecondsFromStatus + ? Theme.palette.successColor2 + : Theme.palette.successColor3 + : root.afterTwoSecondsFromStatus + ? "transparent" + : Theme.palette.dangerColor3 + border.color: root.isConnectedSuccessfully + ? Theme.palette.successColor2 + : Theme.palette.dangerColor2 + border.width: 1 + radius: height / 2 + + RowLayout { + id: pairingStatusLayout + + anchors.centerIn: parent + + spacing: 8 + + Rectangle { + width: 6 + height: 6 + radius: width / 2 + + visible: root.isConnectedSuccessfully + color: Theme.palette.successColor1 + } + + StatusIcon { + Layout.preferredWidth: 16 + Layout.preferredHeight: 16 + + visible: root.isConnectionFailedOrDisconnected + + color: Theme.palette.dangerColor1 + icon: "warning" + } + + StatusBaseText { + text: { + if (root.isConnectedSuccessfully) + return qsTr("Connected. You can now go back to the dApp.") + else if (root.isConnectionFailed) + return qsTr("Error connecting to dApp. Close and try again") + return "" + } + + font.pixelSize: 12 + color: root.isConnectedSuccessfully ? Theme.palette.directColor1 : Theme.palette.dangerColor1 + } + } + } +} diff --git a/ui/app/AppLayouts/Wallet/controls/DAppDelegate.qml b/ui/imports/shared/popups/walletconnect/controls/DAppDelegate.qml similarity index 74% rename from ui/app/AppLayouts/Wallet/controls/DAppDelegate.qml rename to ui/imports/shared/popups/walletconnect/controls/DAppDelegate.qml index 68e3b96f74..144335a8da 100644 --- a/ui/app/AppLayouts/Wallet/controls/DAppDelegate.qml +++ b/ui/imports/shared/popups/walletconnect/controls/DAppDelegate.qml @@ -1,106 +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 +import QtQuick 2.15 +import QtQuick.Layouts 1.15 +import QtGraphicalEffects 1.15 + +import StatusQ.Core 0.1 +import StatusQ.Core.Theme 0.1 +import StatusQ.Controls 0.1 +import StatusQ.Components 0.1 + +MouseArea { + id: root + implicitHeight: 50 + + hoverEnabled: true + + 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: root.iconUrl + visible: !fallbackImage.visible + } + + StatusIcon { + id: fallbackImage + + anchors.fill: parent + + icon: "dapp" + color: Theme.palette.baseColor1 + + visible: iconImage.isLoading || iconImage.isError || !root.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: root.name + + Layout.fillWidth: true + + font.pixelSize: 13 + font.bold: true + + elide: Text.ElideRight + + clip: true + } + StatusBaseText { + text: root.url + + Layout.fillWidth: true + + font.pixelSize: 12 + color: Theme.palette.baseColor1 + + elide: Text.ElideRight + + clip: true + } + } + + StatusFlatButton { + size: StatusBaseButton.Size.Large + + asset.color: root.containsMouse ? Theme.palette.directColor1 + : Theme.palette.baseColor1 + + icon.name: "disconnect" + tooltip.text: qsTr("Disconnect dApp") + + onClicked: { + console.debug(`TODO #14755 - Disconnect ${root.name}`) + // root.disconnectDapp() + } + } + } +} diff --git a/ui/imports/shared/popups/walletconnect/controls/PermissionsCard.qml b/ui/imports/shared/popups/walletconnect/controls/PermissionsCard.qml new file mode 100644 index 0000000000..ba7aa0c096 --- /dev/null +++ b/ui/imports/shared/popups/walletconnect/controls/PermissionsCard.qml @@ -0,0 +1,28 @@ +import QtQuick 2.15 +import QtQuick.Layouts 1.15 + +import StatusQ.Core 0.1 +import StatusQ.Core.Theme 0.1 + +ColumnLayout { + spacing: 8 + + StatusBaseText { + text: qsTr("Uniswap Interface will be able to:") + + font.pixelSize: 13 + color: Theme.palette.baseColor1 + } + + StatusBaseText { + text: qsTr("Check your account balance and activity") + + font.pixelSize: 13 + } + + StatusBaseText { + text: qsTr("Request transactions and message signing") + + font.pixelSize: 13 + } +} diff --git a/ui/imports/shared/popups/walletconnect/controls/qmldir b/ui/imports/shared/popups/walletconnect/controls/qmldir new file mode 100644 index 0000000000..51da5d211b --- /dev/null +++ b/ui/imports/shared/popups/walletconnect/controls/qmldir @@ -0,0 +1,4 @@ +DAppCard 1.0 DAppCard.qml +DAppDelegate 1.0 DAppDelegate.qml +ContextCard 1.0 ContextCard.qml +PermissionsCard 1.0 PermissionsCard.qml diff --git a/ui/imports/shared/popups/walletconnect/panels/ContentPanel.qml b/ui/imports/shared/popups/walletconnect/panels/ContentPanel.qml new file mode 100644 index 0000000000..df35e13b89 --- /dev/null +++ b/ui/imports/shared/popups/walletconnect/panels/ContentPanel.qml @@ -0,0 +1,53 @@ +import QtQuick 2.15 +import QtQuick.Layouts 1.15 + +import StatusQ.Core 0.1 +import StatusQ.Core.Theme 0.1 + +Rectangle { + id: root + + property alias payloadToDisplay: contentText.text + + border.width: 1 + border.color: Theme.palette.baseColor2 + color: "transparent" + radius: 8 + + implicitHeight: contentScrollView.implicitHeight + (2 * contentText.anchors.margins) + + MouseArea { + anchors.fill: parent + cursorShape: contentScrollView.enabled || !enabled ? undefined : Qt.PointingHandCursor + enabled: contentScrollView.height < contentScrollView.contentHeight + + onClicked: { + contentScrollView.enabled = !contentScrollView.enabled + } + z: contentScrollView.z + 1 + } + + StatusScrollView { + id: contentScrollView + anchors.fill: parent + + contentWidth: availableWidth + contentHeight: contentText.contentHeight + + padding: 0 + + enabled: false + + StatusBaseText { + id: contentText + anchors.fill: parent + anchors.margins: 20 + + width: contentScrollView.availableWidth + + text: root.payloadToDisplay + + wrapMode: Text.WrapAnywhere + } + } +} diff --git a/ui/imports/shared/popups/walletconnect/panels/EstimatedTimeDisplay.qml b/ui/imports/shared/popups/walletconnect/panels/EstimatedTimeDisplay.qml new file mode 100644 index 0000000000..d291e91bdc --- /dev/null +++ b/ui/imports/shared/popups/walletconnect/panels/EstimatedTimeDisplay.qml @@ -0,0 +1,23 @@ +import QtQuick 2.15 +import QtQuick.Layouts 1.15 + +import StatusQ.Core 0.1 +import StatusQ.Core.Theme 0.1 + +ColumnLayout { + id: root + + property alias estimatedTimeText: contentText.text + + StatusBaseText { + text: qsTr("Est. time:") + font.pixelSize: 12 + color: Theme.palette.directColor1 + } + StatusBaseText { + id: contentText + + font.pixelSize: 16 + font.weight: Font.DemiBold + } +} diff --git a/ui/imports/shared/popups/walletconnect/panels/IntentionPanel.qml b/ui/imports/shared/popups/walletconnect/panels/IntentionPanel.qml new file mode 100644 index 0000000000..f3d4468e23 --- /dev/null +++ b/ui/imports/shared/popups/walletconnect/panels/IntentionPanel.qml @@ -0,0 +1,110 @@ +import QtQuick 2.15 +import QtQuick.Layouts 1.15 + +import StatusQ.Components 0.1 +import StatusQ.Core 0.1 +import StatusQ.Core.Theme 0.1 + +ColumnLayout { + id: root + + spacing: 8 + + required property string dappName + required property url dappIcon + required property var account + + property string userDisplayNaming + + // Icons + Item { + Layout.fillWidth: true + Layout.preferredHeight: 40 + Layout.alignment: Qt.AlignHCenter + Layout.bottomMargin: 8 + + StatusRoundedImage { + 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 %2 with %3").arg(dappName).arg(root.userDisplayNaming).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 + } + } + } +} diff --git a/ui/imports/shared/popups/walletconnect/panels/MaxFeesDisplay.qml b/ui/imports/shared/popups/walletconnect/panels/MaxFeesDisplay.qml new file mode 100644 index 0000000000..95d232ed1b --- /dev/null +++ b/ui/imports/shared/popups/walletconnect/panels/MaxFeesDisplay.qml @@ -0,0 +1,35 @@ +import QtQuick 2.15 +import QtQuick.Layouts 1.15 + +import StatusQ.Core 0.1 +import StatusQ.Core.Theme 0.1 + +ColumnLayout { + id: root + + property alias maxFeesText: maxFeesDisplay.text + property alias feesTextColor: maxFeesDisplay.color + + StatusBaseText { + text: qsTr("Max fees:") + + font.pixelSize: 12 + color: Theme.palette.directColor1 + } + StatusBaseText { + id: maxFeesDisplay + text: root.maxFeesText + + visible: !!text + + font.pixelSize: 16 + } + StatusBaseText { + text: qsTr("No fees") + + visible: !maxFeesDisplay.visible + + font.pixelSize: maxFeesDisplay.font.pixelSize + font.weight: maxFeesDisplay.font.weight + } +} diff --git a/ui/imports/shared/popups/walletconnect/panels/qmldir b/ui/imports/shared/popups/walletconnect/panels/qmldir new file mode 100644 index 0000000000..24aa1916fb --- /dev/null +++ b/ui/imports/shared/popups/walletconnect/panels/qmldir @@ -0,0 +1,4 @@ +MaxFeesDisplay 1.0 MaxFeesDisplay.qml +EstimatedTimeDisplay 1.0 EstimatedTimeDisplay.qml +IntentionPanel 1.0 IntentionPanel.qml +ContentPanel 1.0 ContentPanel.qml