From 429203cd66ebd4c5d82c730d9a7720565b6fe6b7 Mon Sep 17 00:00:00 2001 From: Alex Jbanca Date: Wed, 12 Jun 2024 11:52:11 +0300 Subject: [PATCH] refactoring(NetworkSelectItemDelegate): Remove backend dependency and clean the API This is the first step in refactoring the NetworkFilter, by cleaning the base component that handles the check states. This component supports multiple configurations: 1. Single selection with or without radio button 2. Multiple selection with or without checkbox 3. Automatic handling of the check state. The component will change the check state based on user clicks 4. Manual handling of the check state. The component will not change the check state, but offers a toggled signal and expects the user to change the check state based on external flows + Fix minor bugs --- storybook/pages/AccountViewPage.qml | 9 + storybook/pages/NetworkFilterPage.qml | 23 +- .../pages/NetworkSelectItemDelegatePage.qml | 97 +++++++ storybook/pages/NetworkSelectPopupPage.qml | 23 +- .../tests/tst_NetworkSelectItemDelegate.qml | 260 ++++++++++++++++++ .../Components/StatusSmartIdenticon.qml | 2 + .../Wallet/controls/NetworkFilter.qml | 14 +- .../controls/NetworkSelectItemDelegate.qml | 163 ++++++----- .../Wallet/popups/NetworkSelectPopup.qml | 2 +- .../Wallet/popups/swap/SwapModal.qml | 7 + .../Wallet/views/NetworkSelectionView.qml | 75 ++++- ui/app/AppLayouts/Wallet/views/qmldir | 1 + 12 files changed, 566 insertions(+), 110 deletions(-) create mode 100644 storybook/pages/NetworkSelectItemDelegatePage.qml create mode 100644 storybook/qmlTests/tests/tst_NetworkSelectItemDelegate.qml diff --git a/storybook/pages/AccountViewPage.qml b/storybook/pages/AccountViewPage.qml index 84f8a27e11..39d8516b3a 100644 --- a/storybook/pages/AccountViewPage.qml +++ b/storybook/pages/AccountViewPage.qml @@ -54,6 +54,8 @@ SplitView { sourceModel: NetworksModel.flatNetworks filters: ValueFilter { roleName: "isTest"; value: areTestNetworksEnabledCheckbox.checked } } + + property var filteredFlatModel: networks property bool areTestNetworksEnabled: areTestNetworksEnabledCheckbox.checked function toggleNetwork(chainId) { } @@ -72,6 +74,13 @@ SplitView { function processPreferredSharingNetworkToggle(preferredSharingNetworksArray, network) { console.warn("processPreferredSharingNetworkToggle :: preferredSharingNetworksArray ::", preferredSharingNetworksArray, "network :: ", network) + const chainId = network.chainId.toString() + if (preferredSharingNetworksArray.includes(chainId)) { + preferredSharingNetworksArray.splice(preferredSharingNetworksArray.indexOf(chainId), 1) + } else { + preferredSharingNetworksArray.push(chainId) + } + return [...preferredSharingNetworksArray] } } diff --git a/storybook/pages/NetworkFilterPage.qml b/storybook/pages/NetworkFilterPage.qml index 50958df554..927509a4a2 100644 --- a/storybook/pages/NetworkFilterPage.qml +++ b/storybook/pages/NetworkFilterPage.qml @@ -9,6 +9,7 @@ import SortFilterProxyModel 0.2 import AppLayouts.stores 1.0 import AppLayouts.Wallet.controls 1.0 +import AppLayouts.Wallet.views 1.0 SplitView { id: root @@ -32,10 +33,10 @@ SplitView { roles: ["chainId", "layer", "chainName", "isTest", "isEnabled", "iconUrl", "shortName", "chainColor"] rolesOverride: [{ role: "enabledState", transform: (mD) => { return simulatedNimModel.areAllEnabled(sourceModel) - ? NetworkSelectItemDelegate.UxEnabledState.AllEnabled + ? NetworkSelectionView.UxEnabledState.AllEnabled : mD.isEnabled - ? NetworkSelectItemDelegate.UxEnabledState.Enabled - : NetworkSelectItemDelegate.UxEnabledState.Disabled + ? NetworkSelectionView.UxEnabledState.Enabled + : NetworkSelectionView.UxEnabledState.Disabled } }] @@ -47,11 +48,11 @@ SplitView { let allEnabled = true for (let i = 0; i < simulatedNimModel.count; i++) { const item = simulatedNimModel.get(i) - if(item.enabledState === NetworkSelectItemDelegate.UxEnabledState.Enabled) { + if(item.enabledState === NetworkSelectionView.UxEnabledState.Enabled) { if(item.chainId !== chainId) { chainIdOnlyEnabled = false } - } else if(item.enabledState === NetworkSelectItemDelegate.UxEnabledState.Disabled) { + } else if(item.enabledState === NetworkSelectionView.UxEnabledState.Disabled) { if(item.chainId !== chainId) { chainIdOnlyDisabled = false } @@ -66,15 +67,15 @@ SplitView { for (let i = 0; i < simulatedNimModel.count; i++) { const item = simulatedNimModel.get(i) if(allEnabled) { - simulatedNimModel.setProperty(i, "enabledState", item.chainId === chainId ? NetworkSelectItemDelegate.UxEnabledState.Enabled : NetworkSelectItemDelegate.UxEnabledState.Disabled) + simulatedNimModel.setProperty(i, "enabledState", item.chainId === chainId ? NetworkSelectionView.UxEnabledState.Enabled : NetworkSelectionView.UxEnabledState.Disabled) } else if(chainIdOnlyEnabled || chainIdOnlyDisabled) { - simulatedNimModel.setProperty(i, "enabledState", NetworkSelectItemDelegate.UxEnabledState.AllEnabled) + simulatedNimModel.setProperty(i, "enabledState", NetworkSelectionView.UxEnabledState.AllEnabled) } else if(item.chainId === chainId) { - simulatedNimModel.setProperty(i, "enabledState", item.enabledState === NetworkSelectItemDelegate.UxEnabledState.Enabled - ? NetworkSelectItemDelegate.UxEnabledState.Disabled - : NetworkSelectItemDelegate.UxEnabledState.Enabled) + simulatedNimModel.setProperty(i, "enabledState", item.enabledState === NetworkSelectionView.UxEnabledState.Enabled + ? NetworkSelectionView.UxEnabledState.Disabled + : NetworkSelectionView.UxEnabledState.Enabled) } - const haveEnabled = item.enabledState !== NetworkSelectItemDelegate.UxEnabledState.Disabled + const haveEnabled = item.enabledState !== NetworkSelectionView.UxEnabledState.Disabled if(item.isEnabled !== haveEnabled) { simulatedNimModel.setProperty(i, "isEnabled", haveEnabled) } diff --git a/storybook/pages/NetworkSelectItemDelegatePage.qml b/storybook/pages/NetworkSelectItemDelegatePage.qml new file mode 100644 index 0000000000..8a10b1d0b9 --- /dev/null +++ b/storybook/pages/NetworkSelectItemDelegatePage.qml @@ -0,0 +1,97 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 +import QtQml 2.15 + +import StatusQ.Core 0.1 +import StatusQ.Core.Utils 0.1 +import StatusQ.Controls 0.1 +import StatusQ.Components 0.1 +import StatusQ.Core.Theme 0.1 + +import AppLayouts.Wallet.controls 1.0 + +import Models 1.0 +import Storybook 1.0 + +import SortFilterProxyModel 0.2 + +import utils 1.0 + +SplitView { + id: root + + Item { + implicitWidth: delegate.width + NetworkSelectItemDelegate { + id: delegate + title: "Ethereum" + iconUrl: Style.svg("network/Network=Ethereum") + showIndicator: true + multiSelection: true + checkState: checkStateSelector.checkState + nextCheckState: checkState === Qt.Unchecked ? Qt.PartiallyChecked : + checkState === Qt.PartiallyChecked ? Qt.Checked : Qt.Unchecked + + onCheckStateChanged: { + checkStateSelector.checkState = checkState + } + } + } + + Pane { + id: pane + SplitView.fillWidth: true + ColumnLayout { + CheckBox { + text: "showIndicator" + checked: delegate.showIndicator + onCheckedChanged: { + delegate.showIndicator = checked + } + } + + CheckBox { + text: "multiSelection" + checked: delegate.multiSelection + onCheckedChanged: { + delegate.multiSelection = checked + } + } + + Label { + text: "title" + } + TextField { + text: delegate.title + onTextChanged: { + delegate.title = text + } + } + + Label { + text: "iconUrl" + } + TextField { + text: delegate.iconUrl + onTextChanged: { + delegate.iconUrl = text + } + } + + CheckBox { + id: checkStateSelector + text: "checkedState" + tristate: true + checked: true + onCheckStateChanged: { + if(delegate.checkState !== checkState) { + delegate.checkState = checkState + } + } + } + } + } +} + +// category: Controls diff --git a/storybook/pages/NetworkSelectPopupPage.qml b/storybook/pages/NetworkSelectPopupPage.qml index 9273cfe62a..01239758be 100644 --- a/storybook/pages/NetworkSelectPopupPage.qml +++ b/storybook/pages/NetworkSelectPopupPage.qml @@ -10,6 +10,7 @@ import utils 1.0 import AppLayouts.Wallet.popups 1.0 import AppLayouts.Wallet.controls 1.0 +import AppLayouts.Wallet.views 1.0 import AppLayouts.stores 1.0 import Models 1.0 @@ -219,10 +220,10 @@ SplitView { roles: ["chainId", "layer", "chainName", "isTest", "isEnabled", "iconUrl", "shortName", "chainColor"] rolesOverride: [{ role: "enabledState", transform: (mD) => { return simulatedNimModel.areAllEnabled(sourceModel) - ? NetworkSelectItemDelegate.UxEnabledState.AllEnabled + ? NetworkSelectionView.UxEnabledState.AllEnabled : mD.isEnabled - ? NetworkSelectItemDelegate.UxEnabledState.Enabled - : NetworkSelectItemDelegate.UxEnabledState.Disabled + ? NetworkSelectionView.UxEnabledState.Enabled + : NetworkSelectionView.UxEnabledState.Disabled } }] @@ -234,11 +235,11 @@ SplitView { let allEnabled = true for (let i = 0; i < simulatedNimModel.count; i++) { const item = simulatedNimModel.get(i) - if(item.enabledState === NetworkSelectItemDelegate.UxEnabledState.Enabled) { + if(item.enabledState === NetworkSelectionView.UxEnabledState.Enabled) { if(item.chainId !== chainId) { chainIdOnlyEnabled = false } - } else if(item.enabledState === NetworkSelectItemDelegate.UxEnabledState.Disabled) { + } else if(item.enabledState === NetworkSelectionView.UxEnabledState.Disabled) { if(item.chainId !== chainId) { chainIdOnlyDisabled = false } @@ -253,15 +254,15 @@ SplitView { for (let i = 0; i < simulatedNimModel.count; i++) { const item = simulatedNimModel.get(i) if(allEnabled) { - simulatedNimModel.setProperty(i, "enabledState", item.chainId === chainId ? NetworkSelectItemDelegate.UxEnabledState.Enabled : NetworkSelectItemDelegate.UxEnabledState.Disabled) + simulatedNimModel.setProperty(i, "enabledState", item.chainId === chainId ? NetworkSelectionView.UxEnabledState.Enabled : NetworkSelectionView.UxEnabledState.Disabled) } else if(chainIdOnlyEnabled || chainIdOnlyDisabled) { - simulatedNimModel.setProperty(i, "enabledState", NetworkSelectItemDelegate.UxEnabledState.AllEnabled) + simulatedNimModel.setProperty(i, "enabledState", NetworkSelectionView.UxEnabledState.AllEnabled) } else if(item.chainId === chainId) { - simulatedNimModel.setProperty(i, "enabledState", item.enabledState === NetworkSelectItemDelegate.UxEnabledState.Enabled - ? NetworkSelectItemDelegate.UxEnabledState.Disabled - :NetworkSelectItemDelegate.UxEnabledState.Enabled) + simulatedNimModel.setProperty(i, "enabledState", item.enabledState === NetworkSelectionView.UxEnabledState.Enabled + ? NetworkSelectionView.UxEnabledState.Disabled + :NetworkSelectionView.UxEnabledState.Enabled) } - const haveEnabled = item.enabledState !== NetworkSelectItemDelegate.UxEnabledState.Disabled + const haveEnabled = item.enabledState !== NetworkSelectionView.UxEnabledState.Disabled if(item.isEnabled !== haveEnabled) { simulatedNimModel.setProperty(i, "isEnabled", haveEnabled) } diff --git a/storybook/qmlTests/tests/tst_NetworkSelectItemDelegate.qml b/storybook/qmlTests/tests/tst_NetworkSelectItemDelegate.qml new file mode 100644 index 0000000000..f6e8523c60 --- /dev/null +++ b/storybook/qmlTests/tests/tst_NetworkSelectItemDelegate.qml @@ -0,0 +1,260 @@ +import QtQuick 2.15 +import QtTest 1.15 + +import AppLayouts.Wallet.controls 1.0 + +import utils 1.0 + + +Item { + id: root + width: 600 + height: 400 + + Component { + id: componentUnderTest + NetworkSelectItemDelegate { + anchors.centerIn: parent + title: "Ethereum" + iconUrl: Style.svg("network/Network=Ethereum") + onToggled: root.onToggledHandler() + } + } + + SignalSpy { + id: toggledSpy + target: controlUnderTest + signalName: "toggled" + } + + SignalSpy { + id: checkStateChangedSpy + target: controlUnderTest + signalName: "checkStateChanged" + } + + property NetworkSelectItemDelegate controlUnderTest: null + property var onToggledHandler: function(){} + property int externalCheckState: Qt.Unchecked + + TestCase { + name: "NetworkSelectItemDelegate" + when: windowShown + + function init() { + controlUnderTest = createTemporaryObject(componentUnderTest, root) + toggledSpy.clear() + checkStateChangedSpy.clear() + onToggledHandler = function() {} + } + + function test_basicGeometry() { + verify(!!controlUnderTest) + verify(controlUnderTest.width > 0) + verify(controlUnderTest.height > 0) + } + + function test_title() { + verify(!!controlUnderTest) + compare(controlUnderTest.title, "Ethereum") + controlUnderTest.title = "Polygon" + compare(controlUnderTest.title, "Polygon") + controlUnderTest.title = "" + compare(controlUnderTest.title, "") + controlUnderTest.title = "Ethereum" + } + + function test_icon() { + verify(!!controlUnderTest) + compare(controlUnderTest.iconUrl, Style.svg("network/Network=Ethereum")) + compare(findChild(controlUnderTest, "statusRoundImage").image.source, Style.svg("network/Network=Ethereum")) + controlUnderTest.iconUrl = Style.svg("network/Network=Polygon") + compare(controlUnderTest.iconUrl, Style.svg("network/Network=Polygon")) + compare(findChild(controlUnderTest, "statusRoundImage").image.source, Style.svg("network/Network=Polygon")) + } + + function test_indicatorConfig() { + verify(!!controlUnderTest) + verify(!!findChild(controlUnderTest, "networkSelectionRadioButton_Ethereum")) + verify(!findChild(controlUnderTest, "networkSelectionCheckbox_Ethereum")) + compare(controlUnderTest.showIndicator, true) + compare(controlUnderTest.multiSelection, false) + + //changing to multiselect -> indicator switches to checkbox + controlUnderTest.multiSelection = true + waitForRendering(controlUnderTest) + waitForItemPolished(controlUnderTest) + verify(!!findChild(controlUnderTest, "networkSelectionCheckbox_Ethereum")) + verify(!findChild(controlUnderTest, "networkSelectionRadioButton_Ethereum")) + + //changing removing indicator + controlUnderTest.showIndicator = false + waitForRendering(controlUnderTest) + waitForItemPolished(controlUnderTest) + verify(!findChild(controlUnderTest, "networkSelectionCheckbox_Ethereum")) + verify(!findChild(controlUnderTest, "networkSelectionRadioButton_Ethereum")) + } + + function test_toggleByClick() { + verify(!!controlUnderTest) + mouseClick(controlUnderTest) + tryCompare(toggledSpy, "count", 1) + + const image = findChild(controlUnderTest, "statusRoundImage") + mouseClick(image) + tryCompare(toggledSpy, "count", 2) + + const radioButton = findChild(controlUnderTest, "networkSelectionRadioButton_Ethereum") + mouseClick(radioButton) + tryCompare(toggledSpy, "count", 3) + + controlUnderTest.multiSelection = true + waitForItemPolished(controlUnderTest) + const checkBox = findChild(controlUnderTest, "networkSelectionCheckbox_Ethereum") + mouseClick(checkBox) + tryCompare(toggledSpy, "count", 4) + } + + function test_autoCheckStateChanges() { + verify(!!controlUnderTest) + compare(controlUnderTest.checkState, Qt.Unchecked) + mouseClick(controlUnderTest) + compare(controlUnderTest.checkState, Qt.Checked) + mouseClick(controlUnderTest) + compare(controlUnderTest.checkState, Qt.Unchecked) + const radioButton = findChild(controlUnderTest, "networkSelectionRadioButton_Ethereum") + mouseClick(radioButton) + compare(controlUnderTest.checkState, Qt.Checked) + mouseClick(radioButton) + compare(controlUnderTest.checkState, Qt.Unchecked) + + controlUnderTest.multiSelection = true + waitForItemPolished(controlUnderTest) + compare(controlUnderTest.checkState, Qt.Unchecked) + const checkBox = findChild(controlUnderTest, "networkSelectionCheckbox_Ethereum") + mouseClick(checkBox) + waitForItemPolished(controlUnderTest) + compare(controlUnderTest.checkState, Qt.Checked) + mouseClick(checkBox) + compare(controlUnderTest.checkState, Qt.Unchecked) + mouseClick(controlUnderTest) + compare(controlUnderTest.checkState, Qt.Checked) + mouseClick(controlUnderTest) + compare(controlUnderTest.checkState, Qt.Unchecked) + } + + function test_manualCheckStateChanges() { + verify(!!controlUnderTest) + // checkState is not bound to nextCheckState => no automatic check changes + controlUnderTest.nextCheckState = Qt.binding(() => controlUnderTest.checkState) + compare(controlUnderTest.checkState, Qt.Unchecked) + + mouseClick(controlUnderTest) + compare(controlUnderTest.checkState, Qt.Unchecked) + let radioButton = findChild(controlUnderTest, "networkSelectionRadioButton_Ethereum") + mouseClick(radioButton) + compare(controlUnderTest.checkState, Qt.Unchecked) + + controlUnderTest.multiSelection = true + waitForItemPolished(controlUnderTest) + compare(controlUnderTest.checkState, Qt.Unchecked) + let checkBox = findChild(controlUnderTest, "networkSelectionCheckbox_Ethereum") + mouseClick(checkBox) + compare(controlUnderTest.checkState, Qt.Unchecked) + + controlUnderTest.multiSelection = false + waitForItemPolished(controlUnderTest) + compare(controlUnderTest.checkState, Qt.Unchecked) + + root.onToggledHandler = function() { + controlUnderTest.checkState = controlUnderTest.checkState === Qt.Checked ? Qt.Unchecked : Qt.Checked + } + + mouseClick(controlUnderTest) + compare(controlUnderTest.checkState, Qt.Checked) + + radioButton = findChild(controlUnderTest, "networkSelectionRadioButton_Ethereum") + mouseClick(radioButton) + compare(controlUnderTest.checkState, Qt.Unchecked) + + controlUnderTest.multiSelection = true + root.onToggledHandler = function() { + controlUnderTest.checkState = controlUnderTest.checkState === Qt.Unchecked ? Qt.PartiallyChecked : + controlUnderTest.checkState === Qt.Checked ? Qt.Unchecked : Qt.Checked + } + + mouseClick(controlUnderTest) + compare(controlUnderTest.checkState, Qt.PartiallyChecked) + mouseClick(controlUnderTest) + compare(controlUnderTest.checkState, Qt.Checked) + mouseClick(controlUnderTest) + compare(controlUnderTest.checkState, Qt.Unchecked) + + checkBox = findChild(controlUnderTest, "networkSelectionCheckbox_Ethereum") + mouseClick(checkBox) + compare(controlUnderTest.checkState, Qt.PartiallyChecked) + mouseClick(checkBox) + compare(controlUnderTest.checkState, Qt.Checked) + mouseClick(checkBox) + compare(controlUnderTest.checkState, Qt.Unchecked) + } + + function test_checkStateBindings() { + verify(!!controlUnderTest) + compare(controlUnderTest.checkState, Qt.Unchecked) + compare(root.externalCheckState, Qt.Unchecked) + + controlUnderTest.checkState = Qt.binding(() => root.externalCheckState) + compare(controlUnderTest.checkState, root.externalCheckState) + tryCompare(checkStateChangedSpy, "count", 0) + + root.externalCheckState = Qt.Checked + compare(controlUnderTest.checkState, Qt.Checked) + tryCompare(checkStateChangedSpy, "count", 1) + + root.externalCheckState = Qt.Unchecked + compare(controlUnderTest.checkState, Qt.Unchecked) + tryCompare(checkStateChangedSpy, "count", 2) + } + + function test_interactiveConfig() { + verify(!!controlUnderTest) + compare(controlUnderTest.interactive, true) + controlUnderTest.interactive = false + compare(controlUnderTest.checkState, Qt.Unchecked) + + mouseClick(controlUnderTest) + compare(controlUnderTest.checkState, Qt.Unchecked) + + let radioButton = findChild(controlUnderTest, "networkSelectionRadioButton_Ethereum") + mouseClick(radioButton) + compare(controlUnderTest.checkState, Qt.Unchecked) + + controlUnderTest.multiSelection = true + waitForItemPolished(controlUnderTest) + + mouseClick(controlUnderTest) + compare(controlUnderTest.checkState, Qt.Unchecked) + + let checkBox = findChild(controlUnderTest, "networkSelectionCheckbox_Ethereum") + mouseClick(checkBox) + compare(controlUnderTest.checkState, Qt.Unchecked) + + controlUnderTest.showIndicator = false + + mouseClick(controlUnderTest) + compare(controlUnderTest.checkState, Qt.Unchecked) + + mouseMove(controlUnderTest, controlUnderTest.width / 2, controlUnderTest.height / 2) + waitForRendering(controlUnderTest) + waitForItemPolished(controlUnderTest) + compare(controlUnderTest.sensor.containsMouse, true) + + // manual selection works + controlUnderTest.checkState = Qt.Checked + compare(controlUnderTest.checkState, Qt.Checked) + controlUnderTest.checkState = Qt.Unchecked + compare(controlUnderTest.checkState, Qt.Unchecked) + } + } +} \ No newline at end of file diff --git a/ui/StatusQ/src/StatusQ/Components/StatusSmartIdenticon.qml b/ui/StatusQ/src/StatusQ/Components/StatusSmartIdenticon.qml index 298d943ea5..3d03a7a508 100644 --- a/ui/StatusQ/src/StatusQ/Components/StatusSmartIdenticon.qml +++ b/ui/StatusQ/src/StatusQ/Components/StatusSmartIdenticon.qml @@ -50,6 +50,7 @@ Loader { StatusRoundedImage { id: statusRoundImage + objectName: "statusRoundImage" width: parent.width height: parent.height image.source: root.asset.isImage ? root.asset.name : "" @@ -80,6 +81,7 @@ Loader { id: roundedIcon StatusRoundIcon { + objectName: "statusRoundIcon" asset.bgRadius: root.asset.bgRadius asset.bgWidth: root.asset.bgWidth asset.bgHeight: root.asset.bgHeight diff --git a/ui/app/AppLayouts/Wallet/controls/NetworkFilter.qml b/ui/app/AppLayouts/Wallet/controls/NetworkFilter.qml index 479c83ddc1..e9c0955cfa 100644 --- a/ui/app/AppLayouts/Wallet/controls/NetworkFilter.qml +++ b/ui/app/AppLayouts/Wallet/controls/NetworkFilter.qml @@ -59,7 +59,8 @@ StatusComboBox { root.multiSelection NetworkModelHelpers.getChainIconUrl(root.flatNetworks, d.currentIndex) } - readonly property bool allSelected: enabledFlatNetworks.count === root.flatNetworks.count + readonly property bool allSelected: root.preferredNetworksMode ? root.preferredSharingNetworks.length === root.flatNetworks.count : + enabledFlatNetworks.count === root.flatNetworks.count readonly property bool noneSelected: enabledFlatNetworks.count === 0 // Persist selection between selectPopupLoader reloads @@ -67,7 +68,14 @@ StatusComboBox { property SortFilterProxyModel enabledFlatNetworks: SortFilterProxyModel { sourceModel: root.flatNetworks - filters: ValueFilter { roleName: "isEnabled"; value: true; enabled: !root.preferredNetworksMode } + filters: [ + ValueFilter { roleName: "isEnabled"; value: true; enabled: !root.preferredNetworksMode }, + FastExpressionFilter { + expression: root.preferredSharingNetworks.includes(chainId.toString()) + expectedRoles: ["chainId"] + enabled: root.preferredNetworksMode + } + ] } } @@ -128,7 +136,7 @@ StatusComboBox { visible: (!d.allSelected || !root.showAllSelectedText) && chainRepeater.count > 0 Repeater { id: chainRepeater - model: root.preferredNetworksMode ? root.flatNetworks: root.multiSelection ? d.enabledFlatNetworks: [] + model: root.multiSelection ? d.enabledFlatNetworks: [] delegate: StatusRoundedImage { id: delegateItem width: 24 diff --git a/ui/app/AppLayouts/Wallet/controls/NetworkSelectItemDelegate.qml b/ui/app/AppLayouts/Wallet/controls/NetworkSelectItemDelegate.qml index a7f0061ad7..ab5992679a 100644 --- a/ui/app/AppLayouts/Wallet/controls/NetworkSelectItemDelegate.qml +++ b/ui/app/AppLayouts/Wallet/controls/NetworkSelectItemDelegate.qml @@ -1,96 +1,119 @@ import QtQuick 2.15 +import QtQml 2.15 import QtQuick.Controls 2.15 +import QtGraphicalEffects 1.15 import StatusQ.Components 0.1 import StatusQ.Controls 0.1 +import StatusQ.Core.Theme 0.1 import utils 1.0 -import "../stores/NetworkSelectPopup" - StatusListItem { id: root - property var networkModel: null - property var singleSelection - property var radioButtonGroup - property bool useEnabledRole: true - property bool showCheckboxes: true - property bool showRadioButtons: true + // input/output property + property int checkState: Qt.Unchecked - // Needed for preferred sharing networks - property bool preferredNetworksMode: false - property var preferredSharingNetworks: [] - property bool allChecked: true + //input property + // Defines the next state of the checkbox when clicked + // By default, it toggles between checked and unchecked + property int nextCheckState: checkState === Qt.Checked ? Qt.Unchecked : Qt.Checked - signal toggleNetwork(var network, var model, int index) + required property string iconUrl + property bool showIndicator: true + property bool multiSelection: false + property bool interactive: true - /// Mirrors Nim's UxEnabledState enum from networks/item.nim - enum UxEnabledState { - Enabled, - AllEnabled, - Disabled - } + // output signal + // Emitted when the checkbox is clicked + // This signal is useful when the check state needs to change + // only after processing the toggle event E.g backend call + signal toggled - objectName: model.chainName - title: model.chainName + objectName: root.title asset.height: 24 asset.width: 24 asset.isImage: true - asset.name: Style.svg(model.iconUrl) + asset.name: root.iconUrl onClicked: { - if(!root.singleSelection.enabled) { - checkBox.nextCheckState() - } else if(!radioButton.checked) { // Don't allow uncheck - root.toggleNetwork(({chainId: model.chainId, chainName: model.chainName, iconUrl: model.iconUrl}), root.networkModel, model.index) + d.toggled() + } + + leftPadding: 16 + rightPadding: 16 + statusListItemTitleArea.anchors.leftMargin: 12 + highlighted: (d.checkState !== Qt.Unchecked && !showIndicator) + + Binding on bgColor { + when: highlighted && !root.sensor.containsMouse + value: root.interactive ? Theme.palette.baseColor4 : Theme.palette.primaryColor3 + restoreMode: Binding.RestoreBindingOrValue + } + + onCheckStateChanged: { + if (checkState !== d.checkState) { + d.checkState = checkState } } - leftPadding: 12 - rightPadding: 0 - statusListItemTitleArea.anchors.leftMargin: 12 - components: [ - StatusCheckBox { - id: checkBox - objectName: "networkSelectionCheckbox_" + model.chainName - tristate: true - visible: !root.singleSelection.enabled && root.showCheckboxes - - checkState: { - if(root.preferredNetworksMode) { - return root.allChecked ? Qt.PartiallyChecked : preferredSharingNetworks.includes(model.chainId.toString()) ? Qt.Checked : Qt.Unchecked - } - else if(root.useEnabledRole) { - return model.isEnabled ? Qt.Checked : Qt.Unchecked - } else if (model.enabledState === NetworkSelectItemDelegate.UxEnabledState.Enabled) { - return Qt.Checked - } else { - if( model.enabledState === NetworkSelectItemDelegate.UxEnabledState.AllEnabled) { - return Qt.PartiallyChecked - } else { - return Qt.Unchecked - } - } - } - - nextCheckState: () => { - Qt.callLater(root.toggleNetwork, model, root.networkModel, model.index) - return Qt.PartiallyChecked - } - }, - StatusRadioButton { - id: radioButton - visible: root.singleSelection.enabled && root.showRadioButtons - size: StatusRadioButton.Size.Large - ButtonGroup.group: root.radioButtonGroup - checked: root.singleSelection.currentModel === root.networkModel && root.singleSelection.currentIndex === model.index - - onToggled: { - if(checked) { - root.toggleNetwork(({chainId: model.chainId, chainName: model.chainName, iconUrl: model.iconUrl}), root.networkModel, model.index) - } - } + Loader { + id: indicatorLoader + sourceComponent: root.multiSelection ? checkBoxComponent : radioButtonComponent + active: root.showIndicator } ] + + + Component { + id: checkBoxComponent + StatusCheckBox { + id: checkBox + + objectName: "networkSelectionCheckbox_" + root.title + checkState: d.checkState + tristate: true + nextCheckState: () => d.checkState + enabled: root.interactive + + onClicked: { + d.toggled() + } + } + } + + Component { + id: radioButtonComponent + StatusRadioButton { + id: radioButton + objectName: "networkSelectionRadioButton_" + root.title + size: StatusRadioButton.Size.Large + checked: d.checkState !== Qt.Unchecked + enabled: root.interactive + + onClicked: { + d.toggled() + } + } + } + + QtObject { + id: d + property int checkState: root.checkState + + function toggled() { + if (!root.interactive) { + return + } + d.checkState = root.nextCheckState + root.toggled() + } + + onCheckStateChanged: { + if (checkState !== root.checkState) { + root.checkState = checkState + } + } + } } diff --git a/ui/app/AppLayouts/Wallet/popups/NetworkSelectPopup.qml b/ui/app/AppLayouts/Wallet/popups/NetworkSelectPopup.qml index 3f7f10687b..9cf89ea49e 100644 --- a/ui/app/AppLayouts/Wallet/popups/NetworkSelectPopup.qml +++ b/ui/app/AppLayouts/Wallet/popups/NetworkSelectPopup.qml @@ -75,7 +75,7 @@ StatusDialog { preferredSharingNetworks: root.preferredSharingNetworks useEnabledRole: root.useEnabledRole singleSelection: d.singleSelection - onToggleNetwork: { + onToggleNetwork: (network, index) => { root.toggleNetwork(network, index) if(d.singleSelection.enabled) close() diff --git a/ui/app/AppLayouts/Wallet/popups/swap/SwapModal.qml b/ui/app/AppLayouts/Wallet/popups/swap/SwapModal.qml index 7f384b6162..df012658ea 100644 --- a/ui/app/AppLayouts/Wallet/popups/swap/SwapModal.qml +++ b/ui/app/AppLayouts/Wallet/popups/swap/SwapModal.qml @@ -136,6 +136,13 @@ StatusDialog { networkFilter.setChain(root.swapInputParamsForm.selectedNetworkChainId) } } + + Connections { + target: root.swapInputParamsForm + function onSelectedNetworkChainIdChanged() { + networkFilter.setChain(root.swapInputParamsForm.selectedNetworkChainId) + } + } } } diff --git a/ui/app/AppLayouts/Wallet/views/NetworkSelectionView.qml b/ui/app/AppLayouts/Wallet/views/NetworkSelectionView.qml index 89c1136cd2..99a725b0d0 100644 --- a/ui/app/AppLayouts/Wallet/views/NetworkSelectionView.qml +++ b/ui/app/AppLayouts/Wallet/views/NetworkSelectionView.qml @@ -1,6 +1,7 @@ import QtQuick 2.15 import QtQuick.Controls 2.15 +import StatusQ 0.1 import StatusQ.Core 0.1 import StatusQ.Core.Theme 0.1 @@ -24,21 +25,71 @@ StatusListView { signal toggleNetwork(var network, int index) + /// Mirrors Nim's UxEnabledState enum from networks/item.nim + enum UxEnabledState { + Enabled, + AllEnabled, + Disabled + } + model: root.flatNetworks delegate: NetworkSelectItemDelegate { + id: delegateItem + + required property var model + readonly property int multiSelectCheckState: { + if(root.preferredNetworksMode) { + return root.preferredSharingNetworks.length === root.count ? + Qt.PartiallyChecked : + root.preferredSharingNetworks.includes(model.chainId.toString()) ? Qt.Checked : Qt.Unchecked + } + else if(root.useEnabledRole) { + return model.isEnabled ? Qt.Checked : Qt.Unchecked + } else if (model.enabledState === NetworkSelectionView.UxEnabledState.Enabled) { + return Qt.Checked + } else { + if( model.enabledState === NetworkSelectionView.UxEnabledState.AllEnabled) { + return Qt.PartiallyChecked + } else { + return Qt.Unchecked + } + } + } + + readonly property int singleSelectCheckState: { + if (root.singleSelection.currentModel === root.model && root.singleSelection.currentIndex === model.index) + return Qt.Checked + return Qt.Unchecked + } + + implicitHeight: 48 implicitWidth: root.width - radioButtonGroup: radioBtnGroup - networkModel: root.model - useEnabledRole: root.useEnabledRole - singleSelection: root.singleSelection - onToggleNetwork: (network, model, index) => root.toggleNetwork(network, index) - preferredNetworksMode: root.preferredNetworksMode - preferredSharingNetworks: root.preferredSharingNetworks - allChecked: root.preferredSharingNetworks.length === root.count - showCheckboxes: root.showCheckboxes - showRadioButtons: root.showRadioButtons + title: model.chainName + iconUrl: Style.svg(model.iconUrl) + showIndicator: (multiSelection && root.showCheckboxes) || (!multiSelection && root.showRadioButtons) + multiSelection: !root.singleSelection.enabled + + Binding on checkState { + when: root.singleSelection.enabled + value: singleSelectCheckState + } + + Binding on checkState { + when: !root.singleSelection.enabled + value: multiSelectCheckState + } + + nextCheckState: checkState + onToggled: { + if(!root.singleSelection.enabled) { + Qt.callLater(root.toggleNetwork, delegateItem.model, delegateItem.model.index) + } else if(!checkState !== Qt.Checked) { // Don't allow uncheck + checkState = checkState === Qt.Checked ? Qt.Unchecked : Qt.Checked + root.toggleNetwork(delegateItem.model, model.index) + } + } } section { @@ -62,8 +113,4 @@ StatusListView { } } } - - ButtonGroup { - id: radioBtnGroup - } } diff --git a/ui/app/AppLayouts/Wallet/views/qmldir b/ui/app/AppLayouts/Wallet/views/qmldir index f779a633f3..f64560e1e0 100644 --- a/ui/app/AppLayouts/Wallet/views/qmldir +++ b/ui/app/AppLayouts/Wallet/views/qmldir @@ -1,5 +1,6 @@ AssetsDetailView 1.0 AssetsDetailView.qml CollectiblesView 1.0 CollectiblesView.qml +NetworkSelectionView 1.0 NetworkSelectionView.qml SavedAddresses 1.0 SavedAddresses.qml TokenSelectorAssetDelegate 1.0 TokenSelectorAssetDelegate.qml TokenSelectorView 1.0 TokenSelectorView.qml