feat(WalletConnect): Fine-tune connect dApp modal
Changes: 1. Align dialog with Figma design 2. Add new components for round image with badge and connection status tag 3. Add tests 4. Dapps service will now receive wallet `RootStore` as input and reuse existing models with account balance and other necessary info for account selection and chain selection 5. Minor updates in stores 6. Minor updates in WC toast messages to display app domain instead of app url
This commit is contained in:
parent
b08eb128ad
commit
2f050a025f
|
@ -27,42 +27,17 @@ import shared.stores 1.0
|
|||
Item {
|
||||
id: root
|
||||
|
||||
function openModal() {
|
||||
modal.openWithFilter([1, 42161], JSON.parse(`{
|
||||
"metadata": {
|
||||
"description": "React App for WalletConnect",
|
||||
"icons": [
|
||||
"https://avatars.githubusercontent.com/u/37784886"
|
||||
],
|
||||
"name": "React App",
|
||||
"url": "https://react-app.walletconnect.com",
|
||||
"verifyUrl": "https://verify.walletconnect.com"
|
||||
},
|
||||
"publicKey": "300a6a1df4cb0cd73eb652f11845f35a318541eb18ab369860be85c0c2ada54a"
|
||||
}`))
|
||||
if (pairedCheckbox.checked) {
|
||||
pairedResultTimer.restart()
|
||||
}
|
||||
}
|
||||
|
||||
// qml Splitter
|
||||
SplitView {
|
||||
anchors.fill: parent
|
||||
|
||||
ColumnLayout {
|
||||
PopupBackground {
|
||||
SplitView.fillWidth: true
|
||||
|
||||
Component.onCompleted: root.openModal()
|
||||
|
||||
StatusButton {
|
||||
id: openButton
|
||||
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.margins: 20
|
||||
|
||||
text: "Open ConnectDAppModal"
|
||||
|
||||
onClicked: root.openModal()
|
||||
Button {
|
||||
text: "Open"
|
||||
onClicked: modal.open()
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
ConnectDAppModal {
|
||||
|
@ -70,6 +45,11 @@ Item {
|
|||
|
||||
anchors.centerIn: parent
|
||||
|
||||
modal: false
|
||||
closePolicy: Popup.NoAutoClose
|
||||
|
||||
visible: true
|
||||
|
||||
spacing: 8
|
||||
|
||||
accounts: WalletAccountsModel {}
|
||||
|
@ -78,9 +58,12 @@ Item {
|
|||
sourceModel: NetworksModel.flatNetworks
|
||||
filters: ValueFilter { roleName: "isTest"; value: false; }
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {}
|
||||
dAppUrl: dAppUrlField.text
|
||||
dAppName: dAppNameField.text
|
||||
dAppIconUrl: hasIconCheckbox.checked ? "https://avatars.githubusercontent.com/u/37784886" : ""
|
||||
connectionStatus: pairedCheckbox.checked ? pairedStatusCheckbox.checked ? connectionSuccessfulStatus : connectionFailedStatus : notConnectedStatus
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
|
@ -100,6 +83,29 @@ Item {
|
|||
|
||||
checked: true
|
||||
}
|
||||
CheckBox {
|
||||
id: hasIconCheckbox
|
||||
|
||||
text: "Has Icon"
|
||||
|
||||
checked: true
|
||||
}
|
||||
Label {
|
||||
text: "DappName"
|
||||
}
|
||||
TextField {
|
||||
id: dAppNameField
|
||||
|
||||
text: "React App"
|
||||
}
|
||||
Label {
|
||||
text: "DApp URL"
|
||||
}
|
||||
TextField {
|
||||
id: dAppUrlField
|
||||
|
||||
text: "https://react-app.walletconnect.com"
|
||||
}
|
||||
Item { Layout.fillHeight: true }
|
||||
}
|
||||
}
|
||||
|
@ -111,13 +117,7 @@ Item {
|
|||
running: false
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
if (pairedCheckbox.checked) {
|
||||
if (pairedStatusCheckbox.checked) {
|
||||
modal.pairSuccessful(null)
|
||||
} else {
|
||||
modal.pairFailed(null, "Pairing failed")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
|
||||
import shared.popups.walletconnect 1.0
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
ConnectionStatusTag {
|
||||
id: connectionTag
|
||||
anchors.centerIn: parent
|
||||
success: checkbox.checked
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
id: checkbox
|
||||
anchors.bottom: connectionTag.top
|
||||
anchors.horizontalCenter: connectionTag.horizontalCenter
|
||||
text: "success"
|
||||
checked: false
|
||||
}
|
||||
}
|
||||
|
||||
// category: Components
|
||||
// https://www.figma.com/design/HrmZp1y4S77QJezRFRl6ku/dApp-Interactions---Milestone-1?node-id=481-165960&t=oshb3aHNPCiUcQdH-0
|
|
@ -18,6 +18,7 @@ import Models 1.0
|
|||
import Storybook 1.0
|
||||
|
||||
import AppLayouts.Wallet.controls 1.0
|
||||
import AppLayouts.Wallet.stores 1.0 as WalletStores
|
||||
import AppLayouts.Wallet.services.dapps 1.0
|
||||
|
||||
import SortFilterProxyModel 0.2
|
||||
|
@ -314,13 +315,18 @@ Item {
|
|||
}
|
||||
}
|
||||
|
||||
walletStore: WalletStore {
|
||||
property var flatNetworks: SortFilterProxyModel {
|
||||
walletRootStore: WalletStores.RootStore {
|
||||
property string selectedAddress: ""
|
||||
property var filteredFlatModel: SortFilterProxyModel {
|
||||
sourceModel: NetworksModel.flatNetworks
|
||||
filters: ValueFilter { roleName: "isTest"; value: settings.testNetworks; }
|
||||
}
|
||||
property var accounts: customAccountsModel.count > 0 ? customAccountsModel : defaultAccountsModel
|
||||
readonly property ListModel ownAccounts: accounts
|
||||
readonly property ListModel nonWatchAccounts: accounts
|
||||
|
||||
function getNetworkShortNames(chainIds) {
|
||||
return "eth:oeth:arb"
|
||||
}
|
||||
}
|
||||
|
||||
onDisplayToastMessage: (message, isErr) => {
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import shared.popups.walletconnect 1.0
|
||||
|
||||
SplitView {
|
||||
Pane {
|
||||
SplitView.fillWidth: true
|
||||
SplitView.fillHeight: true
|
||||
|
||||
RoundImageWithBadge {
|
||||
id: roundImageWithBadge
|
||||
|
||||
width: parent.width
|
||||
height: width
|
||||
imageUrl: addressField.text
|
||||
badgeIcon: badgeField.text
|
||||
fallbackIcon: fallbackIconField.text
|
||||
}
|
||||
}
|
||||
|
||||
Pane {
|
||||
id: controlsPane
|
||||
SplitView.fillHeight: true
|
||||
SplitView.preferredWidth: 300
|
||||
ColumnLayout {
|
||||
Label { text: "Image url" }
|
||||
TextField {
|
||||
id: addressField
|
||||
text: "https://picsum.photos/200/200"
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
Label { text: "Badge name" }
|
||||
TextField {
|
||||
id: badgeField
|
||||
text: "walletConnect"
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
Label { text: "Fallback icon name" }
|
||||
TextField {
|
||||
id: fallbackIconField
|
||||
text: "dapp"
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// category: Components
|
||||
|
||||
// https://www.figma.com/design/HrmZp1y4S77QJezRFRl6ku/dApp-Interactions---Milestone-1?node-id=481-160233&t=xyix3QX5I3jxrDir-0
|
|
@ -0,0 +1,319 @@
|
|||
import QtQuick 2.15
|
||||
import QtTest 1.15
|
||||
|
||||
import shared.popups.walletconnect 1.0
|
||||
|
||||
import Models 1.0
|
||||
|
||||
Item {
|
||||
id: root
|
||||
width: 600
|
||||
height: 800
|
||||
|
||||
Component {
|
||||
id: accountsModelComponent
|
||||
WalletAccountsModel {}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: componentUnderTest
|
||||
ConnectDAppModal {
|
||||
id: controlUnderTest
|
||||
modal: false
|
||||
anchors.centerIn: parent
|
||||
accounts: WalletAccountsModel {}
|
||||
flatNetworks: NetworksModel.flatNetworks
|
||||
}
|
||||
}
|
||||
|
||||
TestCase {
|
||||
id: testConnectDappModal
|
||||
name: "ConnectDappModalTest"
|
||||
when: windowShown
|
||||
|
||||
SignalSpy {
|
||||
id: connectSignalSpy
|
||||
target: testConnectDappModal.dappModal
|
||||
signalName: "connect"
|
||||
}
|
||||
|
||||
SignalSpy {
|
||||
id: declineSignalSpy
|
||||
target: testConnectDappModal.dappModal
|
||||
signalName: "decline"
|
||||
}
|
||||
|
||||
SignalSpy {
|
||||
id: disconnectSignalSpy
|
||||
target: testConnectDappModal.dappModal
|
||||
signalName: "disconnect"
|
||||
}
|
||||
|
||||
property ConnectDAppModal dappModal: null
|
||||
|
||||
function cleanup() {
|
||||
connectSignalSpy.clear()
|
||||
declineSignalSpy.clear()
|
||||
disconnectSignalSpy.clear()
|
||||
}
|
||||
|
||||
function test_initialState() {
|
||||
dappModal = createTemporaryObject(componentUnderTest, root, {visible: true})
|
||||
|
||||
verify(dappModal.visible, "ConnectDAppModal should be visible")
|
||||
verify(dappModal.accounts, "ConnectDAppModal should have accounts")
|
||||
verify(dappModal.flatNetworks, "ConnectDAppModal should have networks")
|
||||
|
||||
compare(dappModal.width, 480)
|
||||
compare(dappModal.height, 633)
|
||||
compare(dappModal.dAppName, "")
|
||||
compare(dappModal.dAppUrl, "")
|
||||
compare(dappModal.dAppIconUrl, "")
|
||||
compare(dappModal.connectionStatus, dappModal.notConnectedStatus)
|
||||
}
|
||||
|
||||
function test_notConnectedState() {
|
||||
dappModal = createTemporaryObject(componentUnderTest, root, {visible: true})
|
||||
|
||||
compare(dappModal.connectionStatus, dappModal.notConnectedStatus)
|
||||
|
||||
// Reject button should be enabled
|
||||
const rejectButton = findChild(dappModal, "rejectButton")
|
||||
verify(rejectButton, "Reject button should be present")
|
||||
compare(rejectButton.text, "Reject")
|
||||
compare(rejectButton.enabled, true)
|
||||
mouseClick(rejectButton)
|
||||
compare(declineSignalSpy.count, 1)
|
||||
|
||||
// Connect button should be enabled
|
||||
const connectButton = findChild(dappModal, "primaryActionButton")
|
||||
verify(connectButton, "Connect button should be present")
|
||||
compare(connectButton.text, "Connect")
|
||||
compare(connectButton.enabled, true)
|
||||
mouseClick(connectButton)
|
||||
compare(connectSignalSpy.count, 1)
|
||||
|
||||
// Disconnect button should be disabled
|
||||
const disconnectButton = findChild(dappModal, "disconnectButton")
|
||||
verify(disconnectButton, "Disconnect button should be present")
|
||||
compare(disconnectButton.text, "Disconnect")
|
||||
compare(disconnectButton.visible, false)
|
||||
mouseClick(disconnectButton)
|
||||
compare(disconnectSignalSpy.count, 0)
|
||||
|
||||
// Account selector should be enabled and user should be able to select an account
|
||||
const accountSelector = findChild(dappModal, "accountSelector")
|
||||
verify(accountSelector, "Account selector should be present")
|
||||
compare(accountSelector.enabled, true)
|
||||
compare(accountSelector.currentIndex, 0)
|
||||
mouseClick(accountSelector)
|
||||
compare(accountSelector.popup.visible, true)
|
||||
|
||||
waitForItemPolished(accountSelector.popup.contentItem)
|
||||
|
||||
const accountsList = findChild(accountSelector, "accountSelectorList")
|
||||
verify(accountsList, "Accounts list should be present")
|
||||
const selectAddress = accountsList.itemAtIndex(1).address
|
||||
mouseClick(accountsList.itemAtIndex(1))
|
||||
compare(dappModal.selectedAccountAddress, accountSelector.currentAccountAddress)
|
||||
compare(dappModal.selectedAccountAddress, selectAddress)
|
||||
|
||||
// Chain selector is enabled, all common chains preselected
|
||||
const chainSelector = findChild(dappModal, "networkFilter")
|
||||
verify(chainSelector, "Chain selector should be present")
|
||||
compare(chainSelector.enabled, true)
|
||||
compare(chainSelector.selection.length, NetworksModel.flatNetworks.count)
|
||||
compare(dappModal.selectedChains.length, NetworksModel.flatNetworks.count)
|
||||
|
||||
// User should be able to deselect a chain
|
||||
mouseClick(chainSelector)
|
||||
waitForItemPolished(chainSelector)
|
||||
const networkSelectorList = findChild(chainSelector, "networkSelectorList")
|
||||
verify(networkSelectorList, "Network selector list should be present")
|
||||
mouseClick(networkSelectorList.itemAtIndex(0))
|
||||
compare(chainSelector.selection.length, NetworksModel.flatNetworks.count - 1)
|
||||
compare(chainSelector.selection[0], NetworksModel.flatNetworks.get(1).chainId)
|
||||
compare(dappModal.selectedChains.length, NetworksModel.flatNetworks.count - 1)
|
||||
compare(dappModal.selectedChains[0], NetworksModel.flatNetworks.get(1).chainId)
|
||||
}
|
||||
|
||||
function test_connectedState() {
|
||||
dappModal = createTemporaryObject(componentUnderTest, root, {visible: true})
|
||||
dappModal.pairSuccessful()
|
||||
compare(dappModal.connectionStatus, dappModal.connectionSuccessfulStatus)
|
||||
|
||||
// Reject button should not be visible
|
||||
const rejectButton = findChild(dappModal, "rejectButton")
|
||||
verify(rejectButton, "Reject button should be present")
|
||||
compare(rejectButton.visible, false)
|
||||
mouseClick(rejectButton)
|
||||
compare(declineSignalSpy.count, 0)
|
||||
|
||||
// Close button should be enabled
|
||||
const closeButton = findChild(dappModal, "primaryActionButton")
|
||||
verify(closeButton, "Close button should be present")
|
||||
compare(closeButton.text, "Close")
|
||||
compare(closeButton.enabled, true)
|
||||
compare(closeButton.visible, true)
|
||||
mouseClick(closeButton)
|
||||
compare(dappModal.opened, false)
|
||||
dappModal.open()
|
||||
|
||||
// Disconnect button should be enabled
|
||||
const disconnectButton = findChild(dappModal, "disconnectButton")
|
||||
verify(disconnectButton, "Disconnect button should be present")
|
||||
compare(disconnectButton.text, "Disconnect")
|
||||
compare(disconnectButton.visible, true)
|
||||
compare(disconnectButton.enabled, true)
|
||||
mouseClick(disconnectButton)
|
||||
compare(disconnectSignalSpy.count, 1)
|
||||
|
||||
// Account selector should be disabled
|
||||
const accountSelector = findChild(dappModal, "accountSelector")
|
||||
verify(accountSelector, "Account selector should be present")
|
||||
compare(accountSelector.currentIndex, 0)
|
||||
mouseClick(accountSelector)
|
||||
compare(accountSelector.popup.visible, false)
|
||||
|
||||
// Chain selector is disabled
|
||||
const chainSelector = findChild(dappModal, "networkFilter")
|
||||
verify(chainSelector, "Chain selector should be present")
|
||||
compare(chainSelector.selection.length, NetworksModel.flatNetworks.count)
|
||||
|
||||
// User should not be able to deselect a chain
|
||||
mouseClick(chainSelector)
|
||||
waitForItemPolished(chainSelector)
|
||||
const networkSelectorList = findChild(chainSelector, "networkSelectorList")
|
||||
verify(networkSelectorList, "Network selector list should be present")
|
||||
mouseClick(networkSelectorList.itemAtIndex(0))
|
||||
compare(chainSelector.selection.length, NetworksModel.flatNetworks.count)
|
||||
compare(dappModal.selectedChains.length, NetworksModel.flatNetworks.count)
|
||||
|
||||
const connectionTag = findChild(dappModal, "connectionStatusTag")
|
||||
compare(connectionTag.visible, true)
|
||||
compare(connectionTag.success, true)
|
||||
}
|
||||
|
||||
function test_connectionFailedState() {
|
||||
dappModal = createTemporaryObject(componentUnderTest, root, {visible: true})
|
||||
dappModal.pairFailed()
|
||||
compare(dappModal.connectionStatus, dappModal.connectionFailedStatus)
|
||||
|
||||
// Reject button should not be visible
|
||||
const rejectButton = findChild(dappModal, "rejectButton")
|
||||
verify(rejectButton, "Reject button should be present")
|
||||
compare(rejectButton.visible, false)
|
||||
mouseClick(rejectButton)
|
||||
compare(declineSignalSpy.count, 0)
|
||||
|
||||
// Close button should be enabled
|
||||
const closeButton = findChild(dappModal, "primaryActionButton")
|
||||
verify(closeButton, "Close button should be present")
|
||||
compare(closeButton.text, "Close")
|
||||
compare(closeButton.enabled, true)
|
||||
compare(closeButton.visible, true)
|
||||
mouseClick(closeButton)
|
||||
compare(dappModal.opened, false)
|
||||
dappModal.open()
|
||||
|
||||
// Disconnect button should not be visible
|
||||
const disconnectButton = findChild(dappModal, "disconnectButton")
|
||||
verify(disconnectButton, "Disconnect button should be present")
|
||||
compare(disconnectButton.text, "Disconnect")
|
||||
compare(disconnectButton.visible, false)
|
||||
mouseClick(disconnectButton)
|
||||
compare(disconnectSignalSpy.count, 0)
|
||||
|
||||
// Account selector should be disabled
|
||||
const accountSelector = findChild(dappModal, "accountSelector")
|
||||
verify(accountSelector, "Account selector should be present")
|
||||
compare(accountSelector.currentIndex, 0)
|
||||
mouseClick(accountSelector)
|
||||
compare(accountSelector.popup.visible, false)
|
||||
|
||||
// Chain selector is disabled
|
||||
const chainSelector = findChild(dappModal, "networkFilter")
|
||||
verify(chainSelector, "Chain selector should be present")
|
||||
compare(chainSelector.selection.length, NetworksModel.flatNetworks.count)
|
||||
|
||||
// User should not be able to deselect a chain
|
||||
mouseClick(chainSelector)
|
||||
waitForItemPolished(chainSelector)
|
||||
const networkSelectorList = findChild(chainSelector, "networkSelectorList")
|
||||
verify(networkSelectorList, "Network selector list should be present")
|
||||
mouseClick(networkSelectorList.itemAtIndex(0))
|
||||
compare(chainSelector.selection.length, NetworksModel.flatNetworks.count)
|
||||
compare(dappModal.selectedChains.length, NetworksModel.flatNetworks.count)
|
||||
|
||||
const connectionTag = findChild(dappModal, "connectionStatusTag")
|
||||
compare(connectionTag.visible, true)
|
||||
compare(connectionTag.success, false)
|
||||
}
|
||||
|
||||
function test_selectingAccount() {
|
||||
dappModal = createTemporaryObject(componentUnderTest, root, {visible: true, dAppChains: [1, 11155111]})
|
||||
|
||||
const accountSelector = findChild(dappModal, "accountSelector")
|
||||
verify(accountSelector, "Account selector should be present")
|
||||
compare(accountSelector.currentIndex, 0)
|
||||
mouseClick(accountSelector)
|
||||
compare(accountSelector.popup.visible, true)
|
||||
|
||||
waitForItemPolished(accountSelector.popup.contentItem)
|
||||
|
||||
const accountsList = findChild(accountSelector, "accountSelectorList")
|
||||
verify(accountsList, "Accounts list should be present")
|
||||
compare(accountsList.count, dappModal.accounts.count)
|
||||
|
||||
const selectAddress = accountsList.itemAtIndex(1).address
|
||||
mouseClick(accountsList.itemAtIndex(1))
|
||||
compare(dappModal.selectedAccountAddress, accountSelector.currentAccountAddress)
|
||||
compare(dappModal.selectedAccountAddress, selectAddress)
|
||||
|
||||
const preselectedAddress = accountSelector.currentAccountAddress
|
||||
|
||||
mouseClick(accountSelector)
|
||||
compare(accountSelector.popup.visible, true)
|
||||
|
||||
waitForItemPolished(accountSelector.popup.contentItem)
|
||||
const selectAddress1 = accountsList.itemAtIndex(0).address
|
||||
mouseClick(accountsList.itemAtIndex(0))
|
||||
compare(dappModal.selectedAccountAddress, accountSelector.currentAccountAddress)
|
||||
compare(dappModal.selectedAccountAddress, selectAddress1)
|
||||
|
||||
// Use preselected address
|
||||
dappModal.selectedAccountAddress = preselectedAddress
|
||||
compare(accountSelector.currentAccountAddress, preselectedAddress)
|
||||
}
|
||||
|
||||
function test_chainSelection() {
|
||||
dappModal = createTemporaryObject(componentUnderTest, root, {visible: true})
|
||||
|
||||
const chainSelector = findChild(dappModal, "networkFilter")
|
||||
verify(chainSelector, "Chain selector should be present")
|
||||
// All selected
|
||||
compare(chainSelector.selection.length, NetworksModel.flatNetworks.count)
|
||||
compare(chainSelector.selection[0], NetworksModel.flatNetworks.get(0).chainId)
|
||||
compare(chainSelector.selection[1], NetworksModel.flatNetworks.get(1).chainId)
|
||||
|
||||
// User should be able to deselect a chain
|
||||
mouseClick(chainSelector)
|
||||
waitForItemPolished(chainSelector)
|
||||
const networkSelectorList = findChild(chainSelector, "networkSelectorList")
|
||||
verify(networkSelectorList, "Network selector list should be present")
|
||||
mouseClick(networkSelectorList.itemAtIndex(0))
|
||||
compare(dappModal.selectedChains.length, NetworksModel.flatNetworks.count - 1)
|
||||
compare(dappModal.selectedChains[0], NetworksModel.flatNetworks.get(1).chainId)
|
||||
|
||||
waitForItemPolished(networkSelectorList)
|
||||
mouseClick(networkSelectorList.itemAtIndex(1))
|
||||
compare(dappModal.selectedChains.length, NetworksModel.flatNetworks.count - 2)
|
||||
compare(dappModal.selectedChains[0], NetworksModel.flatNetworks.get(2).chainId)
|
||||
|
||||
const connectButton = findChild(dappModal, "primaryActionButton")
|
||||
verify(!!connectButton, "Connect button should be present")
|
||||
compare(connectButton.visible, true)
|
||||
compare(connectButton.enabled, true)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -14,6 +14,7 @@ import AppLayouts.Wallet.services.dapps 1.0
|
|||
import AppLayouts.Wallet.services.dapps.types 1.0
|
||||
import AppLayouts.Profile.stores 1.0
|
||||
import AppLayouts.Wallet.panels 1.0
|
||||
import AppLayouts.Wallet.stores 1.0 as WalletStores
|
||||
|
||||
import shared.stores 1.0
|
||||
|
||||
|
@ -115,8 +116,9 @@ Item {
|
|||
Component {
|
||||
id: walletStoreComponent
|
||||
|
||||
WalletStore {
|
||||
readonly property ListModel flatNetworks: ListModel {
|
||||
WalletStores.RootStore {
|
||||
property string selectedAddress: ""
|
||||
readonly property ListModel filteredFlatModel: ListModel {
|
||||
ListElement { chainId: 1 }
|
||||
ListElement {
|
||||
chainId: 2
|
||||
|
@ -125,7 +127,7 @@ Item {
|
|||
}
|
||||
}
|
||||
|
||||
readonly property ListModel accounts: ListModel {
|
||||
readonly property ListModel nonWatchAccounts: ListModel {
|
||||
ListElement {address: "0x1"}
|
||||
ListElement {
|
||||
address: "0x2"
|
||||
|
@ -135,9 +137,9 @@ Item {
|
|||
}
|
||||
ListElement { address: "0x3a" }
|
||||
}
|
||||
readonly property ListModel ownAccounts: accounts
|
||||
|
||||
|
||||
function getNetworkShortNames(chainIds) {
|
||||
return "eth:oeth:arb"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -167,7 +169,12 @@ Item {
|
|||
verify(!!sdk)
|
||||
let store = createTemporaryObject(dappsStoreComponent, root)
|
||||
verify(!!store)
|
||||
handler = createTemporaryObject(dappsRequestHandlerComponent, root, {sdk: sdk, store: store, walletStore: walletStore})
|
||||
handler = createTemporaryObject(dappsRequestHandlerComponent, root, {
|
||||
sdk: sdk,
|
||||
store: store,
|
||||
accountsModel: walletStore.nonWatchAccounts,
|
||||
networksModel: walletStore.filteredFlatModel
|
||||
})
|
||||
verify(!!handler)
|
||||
}
|
||||
|
||||
|
@ -176,7 +183,7 @@ Item {
|
|||
}
|
||||
|
||||
function test_TestAuthentication() {
|
||||
let td = mockSessionRequestEvent(this, handler.sdk, handler.walletStore)
|
||||
let td = mockSessionRequestEvent(this, handler.sdk, handler.accountsModel, handler.networksModel)
|
||||
handler.authenticate(td.request)
|
||||
compare(handler.store.authenticateUserCalls.length, 1, "expected a call to store.authenticateUser")
|
||||
|
||||
|
@ -239,7 +246,7 @@ Item {
|
|||
verify(!!sdk)
|
||||
let store = createTemporaryObject(dappsStoreComponent, root)
|
||||
verify(!!store)
|
||||
service = createTemporaryObject(serviceComponent, root, {wcSDK: sdk, store: store, walletStore: walletStore})
|
||||
service = createTemporaryObject(serviceComponent, root, {wcSDK: sdk, store: store, walletRootStore: walletStore})
|
||||
verify(!!service)
|
||||
}
|
||||
|
||||
|
@ -251,7 +258,7 @@ Item {
|
|||
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 walletStore = service.walletRootStore
|
||||
let store = service.store
|
||||
|
||||
service.pair("wc:12ab@1?bridge=https%3A%2F%2Fbridge.walletconnect.org&key=12ab")
|
||||
|
@ -262,12 +269,12 @@ Item {
|
|||
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)
|
||||
let networksArray = ModelUtils.modelToArray(walletStore.filteredFlatModel).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)
|
||||
let accountsArray = ModelUtils.modelToArray(walletStore.nonWatchAccounts).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"
|
||||
)
|
||||
|
@ -276,11 +283,11 @@ Item {
|
|||
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")
|
||||
compare(connectArgs[connectDAppSpy.argPos.dappChains], networksArray, "expected all provided networks (walletStore.filteredFlatModel) 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)
|
||||
let selectedAccount = walletStore.nonWatchAccounts.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]
|
||||
|
@ -314,7 +321,7 @@ Item {
|
|||
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 walletStore = service.walletRootStore
|
||||
let store = service.store
|
||||
|
||||
let testAddress = "0x3a"
|
||||
|
@ -519,7 +526,7 @@ Item {
|
|||
verify(!!sdk)
|
||||
let store = createTemporaryObject(dappsStoreComponent, root)
|
||||
verify(!!store)
|
||||
let service = createTemporaryObject(serviceComponent, root, {wcSDK: sdk, store: store, walletStore: walletStore})
|
||||
let service = createTemporaryObject(serviceComponent, root, {wcSDK: sdk, store: store, walletRootStore: walletStore})
|
||||
verify(!!service)
|
||||
controlUnderTest = createTemporaryObject(componentUnderTest, root, {wcService: service})
|
||||
verify(!!controlUnderTest)
|
||||
|
@ -581,7 +588,7 @@ Item {
|
|||
waitForRendering(controlUnderTest)
|
||||
|
||||
let service = controlUnderTest.wcService
|
||||
let td = mockSessionRequestEvent(this, service.wcSDK, service.walletStore)
|
||||
let td = mockSessionRequestEvent(this, service.wcSDK, service.walletRootStore.nonWatchAccounts, service.walletRootStore.filteredFlatModel)
|
||||
|
||||
waitForRendering(controlUnderTest)
|
||||
let popup = findChild(controlUnderTest, "dappsRequestModal")
|
||||
|
@ -604,7 +611,7 @@ Item {
|
|||
waitForRendering(controlUnderTest)
|
||||
|
||||
let service = controlUnderTest.wcService
|
||||
let td = mockSessionRequestEvent(this, service.wcSDK, service.walletStore)
|
||||
let td = mockSessionRequestEvent(this, service.wcSDK, service.walletRootStore.nonWatchAccounts, service.walletRootStore.filteredFlatModel)
|
||||
|
||||
waitForRendering(controlUnderTest)
|
||||
let popup = findChild(controlUnderTest, "dappsRequestModal")
|
||||
|
@ -625,9 +632,9 @@ Item {
|
|||
}
|
||||
}
|
||||
|
||||
function mockSessionRequestEvent(tc, sdk, walletStore) {
|
||||
let account = walletStore.accounts.get(1)
|
||||
let network = walletStore.flatNetworks.get(1)
|
||||
function mockSessionRequestEvent(tc, sdk, accountsModel, networksMdodel) {
|
||||
let account = accountsModel.get(1)
|
||||
let network = networksMdodel.get(1)
|
||||
let method = "personal_sign"
|
||||
let message = "hello world"
|
||||
let params = [Helpers.strToHex(message), account.address]
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
import QtQml 2.15
|
||||
|
||||
QtObject {
|
||||
id: root
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
CollectiblesStore 1.0 CollectiblesStore.qml
|
||||
WalletAssetsStore 1.0 WalletAssetsStore.qml
|
||||
TokensStore 1.0 TokensStore.qml
|
||||
ActivityFiltersStore 1.0 ActivityFiltersStore.qml
|
||||
CollectiblesStore 1.0 CollectiblesStore.qml
|
||||
RootStore 1.0 RootStore.qml
|
||||
SwapStore 1.0 SwapStore.qml
|
||||
TokensStore 1.0 TokensStore.qml
|
||||
WalletAssetsStore 1.0 WalletAssetsStore.qml
|
||||
|
|
|
@ -322,6 +322,7 @@
|
|||
<file>assets/img/icons/username.svg</file>
|
||||
<file>assets/img/icons/view.svg</file>
|
||||
<file>assets/img/icons/wallet.svg</file>
|
||||
<file>assets/img/icons/walletConnect.svg</file>
|
||||
<file>assets/img/icons/warning.svg</file>
|
||||
<file>assets/img/icons/youtube.svg</file>
|
||||
<file>assets/img/status-logo-icon.png</file>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg width="22" height="24" viewBox="0 0 20 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11 4.5H7.69231C6.98275 4.5 6.8299 4.50619 6.72962 4.52457C6.1166 4.63691 5.63691 5.1166 5.52457 5.72962C5.50619 5.8299 5.5 5.98275 5.5 6.69231V8.25C5.5 8.66421 5.16421 9 4.75 9C4.33579 9 4 8.66421 4 8.25V6.69231C4 6.04903 4 5.72738 4.04914 5.45923C4.27382 4.2332 5.2332 3.27382 6.45923 3.04914C6.72738 3 7.04903 3 7.69231 3H11C15.9706 3 20 7.02944 20 12C20 16.9706 15.9706 21 11 21H7.69231C7.04903 21 6.72738 21 6.45923 20.9509C5.2332 20.7262 4.27382 19.7668 4.04914 18.5408C4 18.2726 4 17.951 4 17.3077V15.75C4 15.3358 4.33579 15 4.75 15C5.16421 15 5.5 15.3358 5.5 15.75V17.3077C5.5 18.0172 5.50619 18.1701 5.52457 18.2704C5.63691 18.8834 6.1166 19.3631 6.72962 19.4754C6.8299 19.4938 6.98275 19.5 7.69231 19.5H11C15.1421 19.5 18.5 16.1421 18.5 12C18.5 7.85786 15.1421 4.5 11 4.5Z" fill="black"/>
|
||||
<path d="M3 11.25C2.58579 11.25 2.25 11.5858 2.25 12C2.25 12.4142 2.58579 12.75 3 12.75H11C11.4142 12.75 11.75 12.4142 11.75 12C11.75 11.5858 11.4142 11.25 11 11.25H3Z" fill="black"/>
|
||||
</svg>
|
||||
|
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
@ -0,0 +1 @@
|
|||
<svg fill="none" height="400" viewBox="0 0 400 400" width="400" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><clipPath id="a"><path d="m0 0h400v400h-400z"/></clipPath><g clip-path="url(#a)"><circle cx="200" cy="200" fill="#3396ff" r="199.5" stroke="#66b1ff"/><path d="m122.519 148.965c42.791-41.729 112.171-41.729 154.962 0l5.15 5.022c2.14 2.086 2.14 5.469 0 7.555l-17.617 17.18c-1.07 1.043-2.804 1.043-3.874 0l-7.087-6.911c-29.853-29.111-78.253-29.111-108.106 0l-7.59 7.401c-1.07 1.043-2.804 1.043-3.874 0l-17.617-17.18c-2.14-2.086-2.14-5.469 0-7.555zm191.397 35.529 15.679 15.29c2.14 2.086 2.14 5.469 0 7.555l-70.7 68.944c-2.139 2.087-5.608 2.087-7.748 0l-50.178-48.931c-.535-.522-1.402-.522-1.937 0l-50.178 48.931c-2.139 2.087-5.608 2.087-7.748 0l-70.7015-68.945c-2.1396-2.086-2.1396-5.469 0-7.555l15.6795-15.29c2.1396-2.086 5.6085-2.086 7.7481 0l50.1789 48.932c.535.522 1.402.522 1.937 0l50.177-48.932c2.139-2.087 5.608-2.087 7.748 0l50.179 48.932c.535.522 1.402.522 1.937 0l50.179-48.931c2.139-2.087 5.608-2.087 7.748 0z" fill="#fff"/></g></svg>
|
After Width: | Height: | Size: 1.1 KiB |
|
@ -43,13 +43,27 @@ QtObject {
|
|||
property var originModel: accountsModule.keyPairModel
|
||||
property var ownAccounts: SortFilterProxyModel {
|
||||
sourceModel: root.accounts
|
||||
proxyRoles: FastExpressionRole {
|
||||
name: "preferredSharingChainShortNames"
|
||||
expression: {
|
||||
return root.networksModuleInst.getNetworkShortNames(model.preferredSharingChainIds)
|
||||
proxyRoles: [
|
||||
FastExpressionRole {
|
||||
name: "preferredSharingChainShortNames"
|
||||
expression: {
|
||||
return root.networksModuleInst.getNetworkShortNames(model.preferredSharingChainIds)
|
||||
}
|
||||
expectedRoles: ["preferredSharingChainIds"]
|
||||
},
|
||||
FastExpressionRole {
|
||||
name: "color"
|
||||
|
||||
function getColor(colorId) {
|
||||
return Utils.getColorForId(colorId)
|
||||
}
|
||||
|
||||
// Direct call for singleton function is not handled properly by
|
||||
// SortFilterProxyModel that's why helper function is used instead.
|
||||
expression: { return getColor(model.colorId) }
|
||||
expectedRoles: ["colorId"]
|
||||
}
|
||||
expectedRoles: ["preferredSharingChainIds"]
|
||||
}
|
||||
]
|
||||
filters: ValueFilter {
|
||||
roleName: "walletType"
|
||||
value: Constants.watchWalletType
|
||||
|
|
|
@ -2,6 +2,9 @@ import QtQuick 2.15
|
|||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import StatusQ 0.1
|
||||
import SortFilterProxyModel 0.2
|
||||
|
||||
import AppLayouts.Wallet.controls 1.0
|
||||
|
||||
import shared.popups.walletconnect 1.0
|
||||
|
@ -51,19 +54,34 @@ DappsComboBox {
|
|||
|
||||
active: false
|
||||
|
||||
onLoaded: item.openWithFilter(dappChains, sessionProposal.params.proposer)
|
||||
|
||||
property var dappChains: []
|
||||
property var sessionProposal: null
|
||||
property var availableNamespaces: null
|
||||
property var sessionTopic: null
|
||||
readonly property var proposalMedatada: !!sessionProposal
|
||||
? sessionProposal.params.proposer.metadata
|
||||
: { name: "", url: "", icons: [] }
|
||||
|
||||
sourceComponent: ConnectDAppModal {
|
||||
visible: true
|
||||
|
||||
onClosed: connectDappLoader.active = false
|
||||
accounts: root.wcService.validAccounts
|
||||
flatNetworks: root.wcService.flatNetworks
|
||||
flatNetworks: SortFilterProxyModel {
|
||||
sourceModel: root.wcService.flatNetworks
|
||||
filters: [
|
||||
FastExpressionFilter {
|
||||
inverted: true
|
||||
expression: connectDappLoader.dappChains.indexOf(chainId) === -1
|
||||
expectedRoles: ["chainId"]
|
||||
}
|
||||
]
|
||||
}
|
||||
selectedAccountAddress: root.wcService.selectedAccountAddress
|
||||
|
||||
dAppUrl: proposalMedatada.url
|
||||
dAppName: proposalMedatada.name
|
||||
dAppIconUrl: !!proposalMedatada.icons && proposalMedatada.icons.length > 0 ? proposalMedatada.icons[0] : ""
|
||||
|
||||
onConnect: {
|
||||
root.wcService.approvePairSession(sessionProposal, selectedChains, selectedAccount)
|
||||
|
@ -171,9 +189,6 @@ DappsComboBox {
|
|||
}
|
||||
|
||||
function onApproveSessionResult(session, err) {
|
||||
connectDappLoader.dappChains = []
|
||||
connectDappLoader.sessionProposal = null
|
||||
connectDappLoader.availableNamespaces = null
|
||||
connectDappLoader.sessionTopic = session.topic
|
||||
|
||||
let modal = connectDappLoader.item
|
||||
|
|
|
@ -14,8 +14,9 @@ QObject {
|
|||
id: root
|
||||
|
||||
required property WalletConnectSDKBase sdk
|
||||
required property var walletStore
|
||||
required property DAppsStore store
|
||||
required property var accountsModel
|
||||
required property var networksModel
|
||||
|
||||
property alias requestsModel: requests
|
||||
|
||||
|
@ -189,8 +190,8 @@ QObject {
|
|||
}
|
||||
address = event.params.request.params[0].from
|
||||
}
|
||||
return ModelUtils.getFirstModelEntryIf(walletStore.ownAccounts, (account) => {
|
||||
return account.address.toLowerCase() === address.toLowerCase()
|
||||
return ModelUtils.getFirstModelEntryIf(root.accountsModel, (account) => {
|
||||
return account.address.toLowerCase() === address.toLowerCase();
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -200,13 +201,7 @@ QObject {
|
|||
return null
|
||||
}
|
||||
let chainId = Helpers.chainIdFromEip155(event.params.chainId)
|
||||
for (let i = 0; i < walletStore.flatNetworks.count; i++) {
|
||||
let network = ModelUtils.get(walletStore.flatNetworks, i)
|
||||
if (network.chainId === chainId) {
|
||||
return network
|
||||
}
|
||||
}
|
||||
return null
|
||||
return ModelUtils.getByKey(root.networksModel, "chainId", chainId)
|
||||
}
|
||||
|
||||
function extractMethodData(event, method) {
|
||||
|
|
|
@ -5,6 +5,7 @@ import StatusQ.Core.Theme 0.1
|
|||
import StatusQ.Core.Utils 0.1
|
||||
|
||||
import AppLayouts.Wallet 1.0
|
||||
import AppLayouts.Wallet.stores 1.0 as WalletStores
|
||||
import AppLayouts.Wallet.services.dapps 1.0
|
||||
import AppLayouts.Wallet.services.dapps.types 1.0
|
||||
import AppLayouts.Profile.stores 1.0
|
||||
|
@ -21,23 +22,20 @@ QObject {
|
|||
|
||||
required property WalletConnectSDKBase wcSDK
|
||||
required property DAppsStore store
|
||||
required property WalletStore walletStore
|
||||
required property WalletStores.RootStore walletRootStore
|
||||
|
||||
readonly property string selectedAccountAddress: walletRootStore.selectedAddress
|
||||
|
||||
readonly property alias dappsModel: dappsProvider.dappsModel
|
||||
readonly property alias requestHandler: requestHandler
|
||||
|
||||
readonly property var validAccounts: SortFilterProxyModel {
|
||||
sourceModel: root.walletStore ? root.walletStore.accounts : null
|
||||
filters: ValueFilter {
|
||||
roleName: "walletType"
|
||||
value: Constants.watchWalletType
|
||||
inverted: true
|
||||
}
|
||||
sourceModel: root.walletRootStore.nonWatchAccounts
|
||||
proxyRoles: [
|
||||
FastExpressionRole {
|
||||
name: "colorizedChainPrefixes"
|
||||
function getChainShortNames(chainIds) {
|
||||
const chainShortNames = root.walletStore.getNetworkShortNames(chainIds)
|
||||
const chainShortNames = root.walletRootStore.getNetworkShortNames(chainIds)
|
||||
return WalletUtils.colorizedChainPrefix(chainShortNames)
|
||||
}
|
||||
expression: getChainShortNames(model.preferredSharingChainIds)
|
||||
|
@ -45,7 +43,7 @@ QObject {
|
|||
}
|
||||
]
|
||||
}
|
||||
readonly property var flatNetworks: root.walletStore ? root.walletStore.flatNetworks : null
|
||||
readonly property var flatNetworks: root.walletRootStore.filteredFlatModel
|
||||
|
||||
function pair(uri) {
|
||||
d.acceptedSessionProposal = null
|
||||
|
@ -109,8 +107,9 @@ QObject {
|
|||
}
|
||||
|
||||
// TODO #14754: implement custom dApp notification
|
||||
let app_url = d.currentSessionProposal ? d.currentSessionProposal.params.proposer.metadata.url : "-"
|
||||
root.displayToastMessage(qsTr("Connected to %1 via WalletConnect").arg(app_url), false)
|
||||
const app_url = d.currentSessionProposal ? d.currentSessionProposal.params.proposer.metadata.url : "-"
|
||||
const app_domain = StringUtils.extractDomainFromLink(app_url)
|
||||
root.displayToastMessage(qsTr("Connected to %1 via WalletConnect").arg(app_domain), false)
|
||||
|
||||
// Persist session
|
||||
if(!store.addWalletConnectSession(JSON.stringify(session))) {
|
||||
|
@ -124,20 +123,22 @@ QObject {
|
|||
}
|
||||
|
||||
function onRejectSessionResult(err) {
|
||||
let app_url = d.currentSessionProposal ? d.currentSessionProposal.params.proposer.metadata.url : "-"
|
||||
const app_url = d.currentSessionProposal ? d.currentSessionProposal.params.proposer.metadata.url : "-"
|
||||
const app_domain = StringUtils.extractDomainFromLink(app_url)
|
||||
if(err) {
|
||||
root.displayToastMessage(qsTr("Failed to reject connection request for %1").arg(app_url), true)
|
||||
root.displayToastMessage(qsTr("Failed to reject connection request for %1").arg(app_domain), true)
|
||||
} else {
|
||||
root.displayToastMessage(qsTr("Connection request for %1 was rejected").arg(app_url), false)
|
||||
root.displayToastMessage(qsTr("Connection request for %1 was rejected").arg(app_domain), false)
|
||||
}
|
||||
}
|
||||
|
||||
function onSessionDelete(topic, err) {
|
||||
let app_url = d.currentSessionProposal ? d.currentSessionProposal.params.proposer.metadata.url : "-"
|
||||
const app_url = d.currentSessionProposal ? d.currentSessionProposal.params.proposer.metadata.url : "-"
|
||||
const app_domain = StringUtils.extractDomainFromLink(app_url)
|
||||
if(err) {
|
||||
root.displayToastMessage(qsTr("Failed to disconnect from %1").arg(app_url), true)
|
||||
root.displayToastMessage(qsTr("Failed to disconnect from %1").arg(app_domain), true)
|
||||
} else {
|
||||
root.displayToastMessage(qsTr("Disconnected from %1").arg(app_url), false)
|
||||
root.displayToastMessage(qsTr("Disconnected from %1").arg(app_domain), false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -175,7 +176,8 @@ QObject {
|
|||
|
||||
sdk: root.wcSDK
|
||||
store: root.store
|
||||
walletStore: root.walletStore
|
||||
accountsModel: root.validAccounts
|
||||
networksModel: root.flatNetworks
|
||||
|
||||
onSessionRequest: (request) => {
|
||||
root.sessionRequest(request)
|
||||
|
|
|
@ -7,6 +7,7 @@ import shared.stores 1.0 as Stores
|
|||
|
||||
import utils 1.0
|
||||
|
||||
import StatusQ 0.1
|
||||
import SortFilterProxyModel 0.2
|
||||
import StatusQ.Core.Theme 0.1
|
||||
import StatusQ.Core.Utils 0.1 as SQUtils
|
||||
|
@ -65,7 +66,7 @@ QtObject {
|
|||
property var nonWatchAccounts: SortFilterProxyModel {
|
||||
sourceModel: accounts
|
||||
proxyRoles: [
|
||||
ExpressionRole {
|
||||
FastExpressionRole {
|
||||
name: "color"
|
||||
|
||||
function getColor(colorId) {
|
||||
|
@ -75,6 +76,7 @@ QtObject {
|
|||
// Direct call for singleton function is not handled properly by
|
||||
// SortFilterProxyModel that's why helper function is used instead.
|
||||
expression: { return getColor(model.colorId) }
|
||||
expectedRoles: ["colorId"]
|
||||
}
|
||||
]
|
||||
filters: ValueFilter {
|
||||
|
@ -159,8 +161,8 @@ QtObject {
|
|||
return d.chainColors[chainShortName]
|
||||
}
|
||||
|
||||
property var flatNetworks: networksModule.flatNetworks
|
||||
property SortFilterProxyModel filteredFlatModel: SortFilterProxyModel {
|
||||
readonly property var flatNetworks: networksModule.flatNetworks
|
||||
readonly property SortFilterProxyModel filteredFlatModel: SortFilterProxyModel {
|
||||
sourceModel: root.flatNetworks
|
||||
filters: ValueFilter { roleName: "isTest"; value: root.areTestNetworksEnabled }
|
||||
}
|
||||
|
|
|
@ -36,6 +36,8 @@ StatusListView {
|
|||
|
||||
signal toggleNetwork(int chainId, int index)
|
||||
|
||||
objectName: "networkSelectorList"
|
||||
|
||||
onSelectionChanged: {
|
||||
if (!root.multiSelection && selection.length > 1) {
|
||||
console.warn("Warning: Multi-selection is disabled, but multiple items are selected. Automatically selecting the last inserted item.")
|
||||
|
|
|
@ -2057,7 +2057,7 @@ Item {
|
|||
store: DAppsStore {
|
||||
controller: WalletStore.RootStore.walletConnectController
|
||||
}
|
||||
walletStore: appMain.rootStore.profileSectionStore.walletStore
|
||||
walletRootStore: WalletStore.RootStore
|
||||
|
||||
Component.onCompleted: {
|
||||
Global.walletConnectService = walletConnectService
|
||||
|
|
|
@ -48,10 +48,7 @@ StatusComboBox {
|
|||
type: StatusComboBox.Type.Secondary
|
||||
size: StatusComboBox.Size.Small
|
||||
|
||||
currentIndex: {
|
||||
if (count === 0) return
|
||||
return Math.max(control.indexOfValue(d.currentAccountSelection), 0)
|
||||
}
|
||||
currentIndex: 0
|
||||
|
||||
objectName: "accountSelector"
|
||||
popupContentItemObjectName: "accountSelectorList"
|
||||
|
@ -129,10 +126,10 @@ StatusComboBox {
|
|||
|
||||
QtObject {
|
||||
id: d
|
||||
property string currentAccountSelection: root.selectedAddress
|
||||
property string currentAccountSelection: root.selectedAddress || root.currentValue
|
||||
|
||||
Binding on currentAccountSelection {
|
||||
value: root.selectedAddress
|
||||
value: root.selectedAddress || root.currentValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ Control {
|
|||
property alias middleLabel: middleLabel
|
||||
property alias asset: smartIdenticon.asset
|
||||
property alias rightComponent: rightComponent.sourceComponent
|
||||
property alias leftComponent: leftComponent.sourceComponent
|
||||
property bool loading: false
|
||||
property int secondarylabelMaxWidth: 100
|
||||
|
||||
|
@ -48,6 +49,9 @@ Control {
|
|||
contentItem: RowLayout {
|
||||
spacing: root.spacing
|
||||
visible: !root.loading
|
||||
Loader {
|
||||
id: leftComponent
|
||||
}
|
||||
StatusSmartIdenticon {
|
||||
id: smartIdenticon
|
||||
Layout.maximumWidth: visible ? 16 : 0
|
||||
|
@ -60,8 +64,10 @@ Control {
|
|||
}
|
||||
StatusBaseText {
|
||||
id: tagPrimaryLabel
|
||||
Layout.maximumWidth: root.availableWidth
|
||||
font.pixelSize: Style.current.tertiaryTextFontSize
|
||||
visible: text !== ""
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
StatusBaseText {
|
||||
id: middleLabel
|
||||
|
|
|
@ -22,73 +22,81 @@ import shared.popups.walletconnect.controls 1.0
|
|||
import AppLayouts.Wallet.controls 1.0
|
||||
|
||||
import utils 1.0
|
||||
import shared.popups.walletconnect.private 1.0
|
||||
|
||||
StatusDialog {
|
||||
id: root
|
||||
|
||||
width: 480
|
||||
implicitHeight: d.connectionStatus === root.notConnectedStatus ? 633 : 681
|
||||
/*
|
||||
Accounts model
|
||||
|
||||
Expected model structure:
|
||||
name [string] - account name e.g. "Piggy Bank"
|
||||
address [string] - wallet account address e.g. "0x1234567890"
|
||||
colorizedChainPrefixes [string] - chain prefixes with rich text colors e.g. "<font color=\"red\">eth:</font><font color=\"blue\">oeth:</font><font color=\"green\">arb:</font>"
|
||||
emoji [string] - emoji for account e.g. "🐷"
|
||||
colorId [string] - color id for account e.g. "1"
|
||||
currencyBalance [var] - fiat currency balance
|
||||
amount [number] - amount of currency e.g. 1234
|
||||
symbol [string] - currency symbol e.g. "USD"
|
||||
optDisplayDecimals [number] - optional number of decimals to display
|
||||
stripTrailingZeroes [bool] - strip trailing zeroes
|
||||
walletType [string] - wallet type e.g. Constants.watchWalletType. See `Constants` for possible values
|
||||
migratedToKeycard [bool] - whether account is migrated to keycard
|
||||
accountBalance [var] - account balance for a specific network
|
||||
formattedBalance [string] - formatted balance e.g. "1234.56B"
|
||||
balance [string] - balance e.g. "123456000000"
|
||||
iconUrl [string] - icon url e.g. "network/Network=Hermez"
|
||||
chainColor [string] - chain color e.g. "#FF0000"
|
||||
*/
|
||||
required property var accounts
|
||||
/*
|
||||
Networks model
|
||||
Expected model structure:
|
||||
chainName [string] - chain long name. e.g. "Ethereum" or "Optimism"
|
||||
chainId [int] - chain unique identifier
|
||||
iconUrl [string] - SVG icon name. e.g. "network/Network=Ethereum"
|
||||
layer [int] - chain layer. e.g. 1 or 2
|
||||
isTest [bool] - true if the chain is a testnet
|
||||
*/
|
||||
required property var flatNetworks
|
||||
|
||||
readonly property alias selectedAccount: d.selectedAccount
|
||||
property alias dAppUrl: dappCard.dAppUrl
|
||||
property alias dAppName: dappCard.name
|
||||
property alias dAppIconUrl: dappCard.iconUrl
|
||||
property alias connectionStatus: d.connectionStatus
|
||||
|
||||
/*
|
||||
Selected account address holds the initial account address selection for the account selector.
|
||||
It is used to preselect the account in the account selector.
|
||||
*/
|
||||
property string selectedAccountAddress: contextCard.selectedAccount.address ?? ""
|
||||
|
||||
readonly property alias selectedAccount: contextCard.selectedAccount
|
||||
readonly property alias selectedChains: d.selectedChains
|
||||
|
||||
readonly property int notConnectedStatus: 0
|
||||
readonly property int connectionSuccessfulStatus: 1
|
||||
readonly property int connectionFailedStatus: 2
|
||||
|
||||
function openWithFilter(dappChains, proposer) {
|
||||
d.connectionStatus = root.notConnectedStatus
|
||||
d.afterTwoSecondsFromStatus = false
|
||||
|
||||
let m = proposer.metadata
|
||||
dappCard.name = m.name
|
||||
dappCard.url = m.url
|
||||
if(m.icons.length > 0) {
|
||||
dappCard.iconUrl = m.icons[0]
|
||||
} else {
|
||||
dappCard.iconUrl = ""
|
||||
}
|
||||
|
||||
d.dappChains.clear()
|
||||
for (let i = 0; i < dappChains.length; i++) {
|
||||
// Convert to int
|
||||
d.dappChains.append({ chainId: parseInt(dappChains[i]) })
|
||||
}
|
||||
|
||||
root.open()
|
||||
}
|
||||
|
||||
function pairSuccessful(session) {
|
||||
d.connectionStatus = root.connectionSuccessfulStatus
|
||||
closeAndRetryTimer.start()
|
||||
}
|
||||
function pairFailed(session, err) {
|
||||
d.connectionStatus = root.connectionFailedStatus
|
||||
closeAndRetryTimer.start()
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: closeAndRetryTimer
|
||||
|
||||
interval: 2000
|
||||
running: false
|
||||
repeat: false
|
||||
|
||||
onTriggered: {
|
||||
d.afterTwoSecondsFromStatus = true
|
||||
}
|
||||
}
|
||||
|
||||
signal connect()
|
||||
signal decline()
|
||||
signal disconnect()
|
||||
|
||||
width: 480
|
||||
implicitHeight: !d.connectionAttempted ? 633 : 681
|
||||
|
||||
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
|
||||
|
||||
title: qsTr("Connection request")
|
||||
title: d.connectionSuccessful ? qsTr("dApp connected") :
|
||||
qsTr("Connection request")
|
||||
|
||||
padding: 20
|
||||
|
||||
|
@ -98,37 +106,40 @@ 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.maximumWidth: root.availableWidth - Layout.leftMargin * 2
|
||||
Layout.leftMargin: 12
|
||||
Layout.rightMargin: Layout.leftMargin
|
||||
Layout.topMargin: 20
|
||||
Layout.topMargin: 14
|
||||
Layout.bottomMargin: Layout.topMargin
|
||||
}
|
||||
|
||||
ContextCard {
|
||||
id: contextCard
|
||||
Layout.maximumWidth: root.availableWidth
|
||||
Layout.fillWidth: true
|
||||
accountsProxy: d.accountsProxy
|
||||
selectedAccount: d.selectedAccount
|
||||
selectedChains: d.selectedChains
|
||||
filteredChains: d.filteredChains
|
||||
notConnected: d.connectionStatus === root.notConnectedStatus
|
||||
|
||||
selectedAccountAddress: root.selectedAccountAddress
|
||||
connectionAttempted: d.connectionAttempted
|
||||
accountsModel: d.accountsProxy
|
||||
chainsModel: root.flatNetworks
|
||||
chainSelection: d.selectedChains
|
||||
|
||||
onChainSelectionChanged: {
|
||||
if (d.selectedChains !== chainSelection) {
|
||||
d.selectedChains = chainSelection
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PermissionsCard {
|
||||
Layout.maximumWidth: root.availableWidth
|
||||
Layout.fillWidth: true
|
||||
|
||||
Layout.leftMargin: 12
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: Layout.leftMargin
|
||||
Layout.topMargin: 20
|
||||
Layout.topMargin: 12
|
||||
Layout.bottomMargin: Layout.topMargin
|
||||
dappName: dappCard.name
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -136,31 +147,39 @@ StatusDialog {
|
|||
id: footer
|
||||
rightButtons: ObjectModel {
|
||||
StatusButton {
|
||||
objectName: "rejectButton"
|
||||
height: 44
|
||||
text: qsTr("Decline")
|
||||
text: qsTr("Reject")
|
||||
|
||||
visible: d.connectionStatus === root.notConnectedStatus
|
||||
visible: !d.connectionAttempted
|
||||
|
||||
onClicked: root.decline()
|
||||
}
|
||||
StatusButton {
|
||||
StatusFlatButton {
|
||||
objectName: "disconnectButton"
|
||||
height: 44
|
||||
text: qsTr("Disconnect")
|
||||
|
||||
visible: d.connectionStatus === root.connectionSuccessfulStatus
|
||||
visible: d.connectionSuccessful
|
||||
|
||||
type: StatusBaseButton.Type.Danger
|
||||
|
||||
onClicked: root.disconnect()
|
||||
}
|
||||
StatusButton {
|
||||
objectName: "primaryActionButton"
|
||||
height: 44
|
||||
text: d.connectionStatus === root.notConnectedStatus
|
||||
? qsTr("Connect")
|
||||
: qsTr("Close")
|
||||
text: d.connectionAttempted
|
||||
? qsTr("Close")
|
||||
: qsTr("Connect")
|
||||
enabled: {
|
||||
if (!d.connectionAttempted)
|
||||
return root.selectedChains.length > 0
|
||||
return true
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
if (d.connectionStatus === root.notConnectedStatus)
|
||||
if (!d.connectionAttempted)
|
||||
root.connect()
|
||||
else
|
||||
root.close()
|
||||
|
@ -178,27 +197,19 @@ StatusDialog {
|
|||
sorters: RoleSorter { roleName: "position"; sortOrder: Qt.AscendingOrder }
|
||||
}
|
||||
|
||||
property var selectedAccount: ({})
|
||||
property var selectedChains: allChainIdsAggregator.value
|
||||
|
||||
readonly property var filteredChains: LeftJoinModel {
|
||||
leftModel: d.dappChains
|
||||
rightModel: root.flatNetworks
|
||||
|
||||
joinRole: "chainId"
|
||||
}
|
||||
|
||||
readonly property FunctionAggregator allChainIdsAggregator: FunctionAggregator {
|
||||
model: d.filteredChains
|
||||
model: root.flatNetworks
|
||||
initialValue: []
|
||||
roleName: "chainId"
|
||||
|
||||
aggregateFunction: (aggr, value) => [...aggr, value]
|
||||
}
|
||||
|
||||
readonly property var dappChains: ListModel {}
|
||||
|
||||
property int connectionStatus: notConnectedStatus
|
||||
property bool afterTwoSecondsFromStatus: false
|
||||
property int connectionStatus: root.notConnectedStatus
|
||||
readonly property bool connectionSuccessful: d.connectionStatus === root.connectionSuccessfulStatus
|
||||
readonly property bool connectionFailed: d.connectionStatus === root.connectionFailedStatus
|
||||
readonly property bool connectionAttempted: d.connectionStatus !== root.notConnectedStatus
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
import QtQuick 2.15
|
||||
|
||||
import StatusQ.Core.Theme 0.1
|
||||
|
||||
import shared.controls 1.0
|
||||
import utils 1.0
|
||||
|
||||
InformationTag {
|
||||
id: root
|
||||
|
||||
property bool success: false
|
||||
|
||||
tagPrimaryLabel.text: qsTr("Connected. You can now go back to the dApp.")
|
||||
tagPrimaryLabel.color: Theme.palette.directColor1
|
||||
tagPrimaryLabel.font.pixelSize: Style.current.additionalTextSize
|
||||
backgroundColor: Theme.palette.successColor3
|
||||
bgBorderColor: Theme.palette.alphaColor(Theme.palette.successColor1, 0.4)
|
||||
asset.color: tagPrimaryLabel.color
|
||||
verticalPadding: Style.current.halfPadding
|
||||
horizontalPadding: 12
|
||||
leftComponent: successBadge
|
||||
|
||||
states: [
|
||||
State {
|
||||
name: "error"
|
||||
when: !root.success
|
||||
PropertyChanges { target: tagPrimaryLabel; text: qsTr("Error connecting to dApp. Close and try again") }
|
||||
PropertyChanges { target: tagPrimaryLabel; color: Theme.palette.dangerColor1 }
|
||||
PropertyChanges { target: asset; name: "warning" }
|
||||
PropertyChanges {
|
||||
target: root
|
||||
backgroundColor: Theme.palette.dangerColor3
|
||||
bgBorderColor: Theme.palette.alphaColor(Theme.palette.dangerColor1, 0.4)
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
ColorAnimation on backgroundColor { running: root.success && root.visible; to: Theme.palette.successColor2; duration: 2000 }
|
||||
ColorAnimation on backgroundColor { running: !root.success && root.visible; to: "transparent"; duration: 2000 }
|
||||
ColorAnimation on bgBorderColor { running: root.success && root.visible; to: Theme.palette.successColor3; duration: 2000 }
|
||||
ColorAnimation on bgBorderColor { running: !root.success && root.visible; to: Theme.palette.dangerColor2; duration: 2000 }
|
||||
|
||||
Component {
|
||||
id: successBadge
|
||||
Item {
|
||||
width: visible ? 10 : 0
|
||||
height: visible ? 6 : 0
|
||||
visible: root.success
|
||||
Rectangle {
|
||||
width: 6
|
||||
height: 6
|
||||
radius: width / 2
|
||||
color: Theme.palette.successColor1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
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.Components 0.1
|
||||
|
||||
import utils 1.0
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property url imageUrl: ""
|
||||
property string badgeIcon: "walletConnect"
|
||||
property string fallbackIcon: "dapp"
|
||||
|
||||
readonly property bool iconLoaded: !mainImage.isError && !mainImage.isLoading && mainImage.image.source !== ""
|
||||
|
||||
implicitWidth: mainImage.implicitWidth
|
||||
implicitHeight: mainImage.implicitHeight
|
||||
|
||||
Item {
|
||||
id: imageContainer
|
||||
|
||||
width: parent.width
|
||||
height: width
|
||||
|
||||
StatusRoundedImage {
|
||||
id: mainImage
|
||||
|
||||
width: parent.width
|
||||
height: width
|
||||
visible: !isError && !isLoading && root.imageUrl != ""
|
||||
image.source: root.imageUrl
|
||||
}
|
||||
|
||||
Loader {
|
||||
anchors.fill: mainImage
|
||||
active: !mainImage.visible
|
||||
sourceComponent: StatusRoundedComponent {
|
||||
color: Theme.palette.primaryColor3
|
||||
StatusIcon {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.current.padding
|
||||
color: Theme.palette.primaryColor1
|
||||
icon: "dapp"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
layer.enabled: true
|
||||
layer.effect: OpacityMask {
|
||||
id: mask
|
||||
invert: true
|
||||
|
||||
maskSource: Item {
|
||||
width: mask.width + 2
|
||||
height: mask.height + 2
|
||||
|
||||
Rectangle {
|
||||
x: badge.x + 1
|
||||
y: badge.y + 1
|
||||
|
||||
width: badge.width + 2
|
||||
height: badge.width + 2
|
||||
radius: badge.width / 2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StatusRoundIcon {
|
||||
id: badge
|
||||
width: (root.width / 2) - Style.current.padding
|
||||
height: width
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: parent.right
|
||||
asset.name: root.badgeIcon
|
||||
asset.color: "transparent"
|
||||
asset.width: width
|
||||
asset.height: height
|
||||
asset.bgWidth: width
|
||||
asset.bgHeight: height
|
||||
}
|
||||
}
|
|
@ -1,137 +0,0 @@
|
|||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,17 +6,19 @@ 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
|
||||
property string selectedAccountAddress: ""
|
||||
property bool connectionAttempted: false
|
||||
property var accountsModel
|
||||
property var chainsModel
|
||||
property alias chainSelection: networkFilter.selection
|
||||
|
||||
readonly property alias selectedAccount: accountsDropdown.currentAccount
|
||||
|
||||
|
||||
implicitWidth: contextLayout.implicitWidth
|
||||
implicitHeight: contextLayout.implicitHeight
|
||||
|
@ -45,10 +47,13 @@ Rectangle {
|
|||
id: accountsDropdown
|
||||
|
||||
Layout.preferredWidth: 204
|
||||
|
||||
control.enabled: root.notConnected && count > 1
|
||||
model: accountsProxy
|
||||
onCurrentAccountChanged: root.selectedAccount = currentAccount
|
||||
Layout.preferredHeight: 38
|
||||
control.horizontalPadding: 12
|
||||
control.verticalPadding: 4
|
||||
control.enabled: !root.connectionAttempted && count > 1
|
||||
model: root.accountsModel
|
||||
indicator.visible: control.enabled
|
||||
selectedAddress: root.selectedAccountAddress
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -59,7 +64,7 @@ Rectangle {
|
|||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.margins: 16
|
||||
Layout.margins: 15
|
||||
|
||||
StatusBaseText {
|
||||
text: qsTr("On")
|
||||
|
@ -69,19 +74,14 @@ Rectangle {
|
|||
|
||||
NetworkFilter {
|
||||
id: networkFilter
|
||||
objectName: "networkFilter"
|
||||
Layout.preferredWidth: accountsDropdown.Layout.preferredWidth
|
||||
|
||||
flatNetworks: root.filteredChains
|
||||
flatNetworks: root.chainsModel
|
||||
showTitle: true
|
||||
multiSelection: true
|
||||
selectionAllowed: notConnected && root.selectedChains.length > 1
|
||||
selection: root.selectedChains
|
||||
|
||||
onSelectionChanged: {
|
||||
if (root.selectedChains !== networkFilter.selection) {
|
||||
root.selectedChains = networkFilter.selection
|
||||
}
|
||||
}
|
||||
showAllSelectedText: false
|
||||
selectionAllowed: !root.connectionAttempted && root.chainsModel.ModelCount.count > 1
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import StatusQ 0.1
|
||||
import StatusQ.Controls 0.1
|
||||
import StatusQ.Core 0.1
|
||||
import StatusQ.Core.Theme 0.1
|
||||
import StatusQ.Core.Utils 0.1
|
||||
|
||||
import shared.popups.walletconnect 1.0
|
||||
import utils 1.0
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
property alias name: appNameText.text
|
||||
property url dAppUrl: ""
|
||||
property url iconUrl: ""
|
||||
|
||||
spacing: Style.current.padding
|
||||
|
||||
RoundImageWithBadge {
|
||||
objectName: "dappIcon"
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.preferredWidth: 72
|
||||
Layout.preferredHeight: Layout.preferredWidth
|
||||
|
||||
imageUrl: iconUrl
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 4
|
||||
|
||||
StatusBaseText {
|
||||
id: appNameText
|
||||
objectName: "appNameText"
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumWidth: root.width
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
elide: Text.ElideRight
|
||||
font.bold: true
|
||||
font.pixelSize: 17
|
||||
}
|
||||
|
||||
StatusFlatButton {
|
||||
id: appUrlText
|
||||
objectName: "appUrlControl"
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.maximumWidth: root.width
|
||||
icon.name: "external-link"
|
||||
icon.color: hovered ? Theme.palette.baseColor1 : Theme.palette.directColor1
|
||||
textPosition: StatusBaseButton.TextPosition.Left
|
||||
size: StatusBaseButton.Size.Tiny
|
||||
textColor: Theme.palette.directColor1
|
||||
hoverColor: "transparent"
|
||||
spacing: 0
|
||||
font.pixelSize: 15
|
||||
font.weight: Font.Normal
|
||||
horizontalPadding: 0
|
||||
verticalPadding: 0
|
||||
text: StringUtils.extractDomainFromLink(dAppUrl)
|
||||
onClicked: {
|
||||
Global.openLinkWithConfirmation(dAppUrl, text)
|
||||
}
|
||||
}
|
||||
ConnectionStatusTag {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.maximumWidth: root.width
|
||||
objectName: "connectionStatusTag"
|
||||
success: d.connectionSuccessful
|
||||
visible: d.connectionAttempted
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,23 +5,29 @@ import StatusQ.Core 0.1
|
|||
import StatusQ.Core.Theme 0.1
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: 8
|
||||
|
||||
StatusBaseText {
|
||||
text: qsTr("Uniswap Interface will be able to:")
|
||||
property string dappName: ""
|
||||
|
||||
StatusBaseText {
|
||||
objectName: "permissionsTitle"
|
||||
text: qsTr("%1 will be able to:").arg(root.dappName)
|
||||
Layout.preferredHeight: 18
|
||||
font.pixelSize: 13
|
||||
color: Theme.palette.baseColor1
|
||||
}
|
||||
|
||||
StatusBaseText {
|
||||
text: qsTr("Check your account balance and activity")
|
||||
Layout.preferredHeight: 18
|
||||
|
||||
font.pixelSize: 13
|
||||
}
|
||||
|
||||
StatusBaseText {
|
||||
text: qsTr("Request transactions and message signing")
|
||||
Layout.preferredHeight: 18
|
||||
|
||||
font.pixelSize: 13
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
ContextCard 1.0 ContextCard.qml
|
||||
DAppCard 1.0 DAppCard.qml
|
||||
PermissionsCard 1.0 PermissionsCard.qml
|
|
@ -1,5 +1,7 @@
|
|||
PairWCModal 1.0 PairWCModal.qml
|
||||
DAppsListPopup 1.0 DAppsListPopup.qml
|
||||
ConnectDAppModal 1.0 ConnectDAppModal.qml
|
||||
ConnectionStatusTag 1.0 ConnectionStatusTag.qml
|
||||
DAppRequestModal 1.0 DAppRequestModal.qml
|
||||
DAppsUriCopyInstructionsPopup 1.0 DAppsUriCopyInstructionsPopup.qml
|
||||
RoundImageWithBadge 1.0 RoundImageWithBadge.qml
|
||||
|
|
Loading…
Reference in New Issue