fix(Wallet) network selection and unify network implementations
Major changes: - Don't allow empty network selection. End up using the nim model directly instead because of individual row changes issues encountered with nim models - Made the clone model a generic implementation to be used in other places where we need to clone a model: ReceiveModal, AddEditSavedAddressPopup - Use cloned model as alternative to NetworksExtraStoreProxy in ReceiveModal - Added tristate support to our generic checkbox control - UX improvements as per design - Fix save address tests naming and zero address issue - Various fixes Notes: - Failed to make NetworkSelectPopup follow ground-truth: show partially checked as user intention until the network is selected in the source model. Got stuck on nim models not being stable models and report wrong entry change when reset. Tried sorting and only updating changes without reset but it didn't work. - Moved grouped property SingleSelectionInfo to its own file from an inline component after finding out that it fails to load on Linux with error "Cannot assign to property of unknown type: "*".". It works on MacOS as expected Closes: #10119
This commit is contained in:
parent
4bd81e8a9a
commit
691de11211
|
@ -40,8 +40,8 @@ proc init*(self: Controller) =
|
|||
proc getNetworks*(self: Controller): seq[NetworkDto] =
|
||||
return self.networkService.getNetworks()
|
||||
|
||||
proc toggleNetwork*(self: Controller, chainId: int) =
|
||||
self.walletAccountService.toggleNetworkEnabled(chainId)
|
||||
proc setNetworksState*(self: Controller, chainIds: seq[int], enabled: bool) =
|
||||
self.walletAccountService.setNetworksState(chainIds, enabled)
|
||||
|
||||
proc areTestNetworksEnabled*(self: Controller): bool =
|
||||
return self.settingsService.areTestNetworksEnabled()
|
||||
|
|
|
@ -19,7 +19,7 @@ method isLoaded*(self: AccessInterface): bool {.base.} =
|
|||
method viewDidLoad*(self: AccessInterface) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method toggleNetwork*(self: AccessInterface, chainId: int) {.base.} =
|
||||
method setNetworksState*(self: AccessInterface, chainIds: seq[int], enable: bool) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method refreshNetworks*(self: AccessInterface) {.base.} =
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
import strformat
|
||||
|
||||
type
|
||||
UxEnabledState* {.pure.} = enum
|
||||
Enabled
|
||||
AllEnabled
|
||||
Disabled
|
||||
|
||||
type
|
||||
Item* = object
|
||||
chainId: int
|
||||
|
@ -16,6 +22,7 @@ type
|
|||
chainColor: string
|
||||
shortName: string
|
||||
balance: float64
|
||||
enabledState: UxEnabledState
|
||||
|
||||
proc initItem*(
|
||||
chainId: int,
|
||||
|
@ -32,6 +39,7 @@ proc initItem*(
|
|||
chainColor: string,
|
||||
shortName: string,
|
||||
balance: float64,
|
||||
enabledState: UxEnabledState,
|
||||
): Item =
|
||||
result.chainId = chainId
|
||||
result.nativeCurrencyDecimals = nativeCurrencyDecimals
|
||||
|
@ -47,6 +55,7 @@ proc initItem*(
|
|||
result.chainColor = chainColor
|
||||
result.shortName = shortName
|
||||
result.balance = balance
|
||||
result.enabledState = enabledState
|
||||
|
||||
proc `$`*(self: Item): string =
|
||||
result = fmt"""NetworkItem(
|
||||
|
@ -64,6 +73,7 @@ proc `$`*(self: Item): string =
|
|||
shortName: {self.shortName},
|
||||
chainColor: {self.chainColor},
|
||||
balance: {self.balance},
|
||||
enabledState: {self.enabledState}
|
||||
]"""
|
||||
|
||||
proc getChainId*(self: Item): int =
|
||||
|
@ -107,3 +117,6 @@ proc getChainColor*(self: Item): string =
|
|||
|
||||
proc getBalance*(self: Item): float64 =
|
||||
return self.balance
|
||||
|
||||
proc getEnabledState*(self: Item): UxEnabledState =
|
||||
return self.enabledState
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import NimQml, Tables, strutils, strformat
|
||||
import NimQml, Tables, strutils, strformat, sequtils
|
||||
|
||||
import ./item
|
||||
|
||||
|
@ -20,6 +20,7 @@ type
|
|||
ChainColor
|
||||
ShortName
|
||||
Balance
|
||||
EnabledState
|
||||
|
||||
QtObject:
|
||||
type
|
||||
|
@ -69,6 +70,7 @@ QtObject:
|
|||
ModelRole.ShortName.int: "shortName",
|
||||
ModelRole.ChainColor.int: "chainColor",
|
||||
ModelRole.Balance.int: "balance",
|
||||
ModelRole.EnabledState.int: "enabledState",
|
||||
}.toTable
|
||||
|
||||
method data(self: Model, index: QModelIndex, role: int): QVariant =
|
||||
|
@ -110,6 +112,8 @@ QtObject:
|
|||
result = newQVariant(item.getChainColor())
|
||||
of ModelRole.Balance:
|
||||
result = newQVariant(item.getBalance())
|
||||
of ModelRole.EnabledState:
|
||||
result = newQVariant(item.getEnabledState().int)
|
||||
|
||||
proc rowData*(self: Model, index: int, column: string): string {.slot.} =
|
||||
if (index >= self.items.len):
|
||||
|
@ -130,6 +134,7 @@ QtObject:
|
|||
of "chainColor": result = $item.getChainColor()
|
||||
of "shortName": result = $item.getShortName()
|
||||
of "balance": result = $item.getBalance()
|
||||
of "enabledState": result = $item.getEnabledState().int
|
||||
|
||||
proc setItems*(self: Model, items: seq[Item]) =
|
||||
self.beginResetModel()
|
||||
|
@ -196,3 +201,41 @@ QtObject:
|
|||
if(item.getChainId() == chainId):
|
||||
return item.getBlockExplorerURL() & EXPLORER_TX_PREFIX
|
||||
return ""
|
||||
|
||||
proc getEnabledState*(self: Model, chainId: int): UxEnabledState =
|
||||
for item in self.items:
|
||||
if(item.getChainId() == chainId):
|
||||
return item.getEnabledState()
|
||||
return UxEnabledState.Disabled
|
||||
|
||||
# Returns the chains that need to be enabled or disabled (the second return value)
|
||||
# to satisty the transitions: all enabled to only chainId enabled and
|
||||
# only chainId enabled to all enabled
|
||||
proc networksToChangeStateOnUserActionFor*(self: Model, chainId: int): (seq[int], bool) =
|
||||
var chainIds: seq[int] = @[]
|
||||
var enable = false
|
||||
case self.getEnabledState(chainId):
|
||||
of UxEnabledState.Enabled:
|
||||
# Iterate to check for the only chainId enabled case ...
|
||||
for item in self.items:
|
||||
if item.getEnabledState() == UxEnabledState.Enabled and item.getChainId() != chainId:
|
||||
# ... as soon as we find another enabled chain mark this by adding it to the list
|
||||
chainIds.add(chainId)
|
||||
break
|
||||
|
||||
# ... if no other chains are enabled, then it's a transition from only chainId enabled to all enabled
|
||||
if chainIds.len == 0:
|
||||
for item in self.items:
|
||||
if item.getChainId() != chainId:
|
||||
chainIds.add(item.getChainId())
|
||||
enable = true
|
||||
of UxEnabledState.Disabled:
|
||||
chainIds.add(chainId)
|
||||
enable = true
|
||||
of UxEnabledState.AllEnabled:
|
||||
# disable all but chainId
|
||||
for item in self.items:
|
||||
if item.getChainId() != chainId:
|
||||
chainIds.add(item.getChainId())
|
||||
|
||||
return (chainIds, enable)
|
|
@ -60,8 +60,8 @@ proc checkIfModuleDidLoad(self: Module) =
|
|||
method viewDidLoad*(self: Module) =
|
||||
self.checkIfModuleDidLoad()
|
||||
|
||||
method toggleNetwork*(self: Module, chainId: int) =
|
||||
self.controller.toggleNetwork(chainId)
|
||||
method setNetworksState*(self: Module, chainIds: seq[int], enabled: bool) =
|
||||
self.controller.setNetworksState(chainIds, enabled)
|
||||
|
||||
method areTestNetworksEnabled*(self: Module): bool =
|
||||
return self.controller.areTestNetworksEnabled()
|
||||
|
|
|
@ -1,89 +0,0 @@
|
|||
import NimQml, Tables, strutils
|
||||
|
||||
import ./model
|
||||
|
||||
# Proxy data model for the Networks data model with additional role; see isActiveRoleName
|
||||
# isEnabled values are copied from the original model into isActiveRoleName values
|
||||
const isActiveRoleName = "isActive"
|
||||
|
||||
QtObject:
|
||||
type
|
||||
NetworksExtraStoreProxy* = ref object of QAbstractListModel
|
||||
sourceModel: Model
|
||||
activeNetworks: seq[bool]
|
||||
extraRole: tuple[roleId: int, roleName: string]
|
||||
|
||||
proc delete(self: NetworksExtraStoreProxy) =
|
||||
self.sourceModel = nil
|
||||
self.activeNetworks = @[]
|
||||
self.QAbstractListModel.delete
|
||||
|
||||
proc setup(self: NetworksExtraStoreProxy) =
|
||||
self.QAbstractListModel.setup
|
||||
|
||||
proc updateActiveNetworks(self: NetworksExtraStoreProxy, sourceModel: Model) =
|
||||
var tmpSeq = newSeq[bool](sourceModel.rowCount())
|
||||
for i in 0 ..< sourceModel.rowCount():
|
||||
tmpSeq[i] = sourceModel.data(sourceModel.index(i, 0, newQModelIndex()), ModelRole.IsEnabled.int).boolVal()
|
||||
self.activeNetworks = tmpSeq
|
||||
|
||||
proc newNetworksExtraStoreProxy*(sourceModel: Model): NetworksExtraStoreProxy =
|
||||
new(result, delete)
|
||||
|
||||
result.sourceModel = sourceModel
|
||||
# assign past last role element
|
||||
result.extraRole = (0, isActiveRoleName)
|
||||
for k in sourceModel.roleNames().keys:
|
||||
if k > result.extraRole.roleId:
|
||||
result.extraRole.roleId = k
|
||||
result.extraRole.roleId += 1
|
||||
|
||||
result.updateactiveNetworks(sourceModel)
|
||||
result.setup
|
||||
|
||||
signalConnect(result.sourceModel, "countChanged()", result, "onCountChanged()")
|
||||
|
||||
proc countChanged(self: NetworksExtraStoreProxy) {.signal.}
|
||||
|
||||
# Nimqml doesn't support connecting signals to other signals
|
||||
proc onCountChanged(self: NetworksExtraStoreProxy) {.slot.} =
|
||||
self.updateActiveNetworks(self.sourceModel)
|
||||
self.countChanged()
|
||||
|
||||
proc getCount(self: NetworksExtraStoreProxy): int {.slot.} =
|
||||
return self.sourceModel.rowCount()
|
||||
|
||||
QtProperty[int] count:
|
||||
read = getCount
|
||||
notify = countChanged
|
||||
|
||||
method rowCount(self: NetworksExtraStoreProxy, index: QModelIndex = nil): int =
|
||||
return self.sourceModel.rowCount()
|
||||
|
||||
method roleNames(self: NetworksExtraStoreProxy): Table[int, string] =
|
||||
var srcRoles = self.sourceModel.roleNames()
|
||||
srcRoles.add(self.extraRole.roleId, self.extraRole.roleName)
|
||||
return srcRoles
|
||||
|
||||
method data(self: NetworksExtraStoreProxy, index: QModelIndex, role: int): QVariant =
|
||||
if role == self.extraRole.roleId:
|
||||
if index.row() < 0 or index.row() >= self.activeNetworks.len:
|
||||
return QVariant()
|
||||
return newQVariant(self.activeNetworks[index.row()])
|
||||
return self.sourceModel.data(index, role)
|
||||
|
||||
method setData*(self: NetworksExtraStoreProxy, index: QModelIndex, value: QVariant, role: int): bool =
|
||||
if role == self.extraRole.roleId:
|
||||
if index.row() < 0 or index.row() >= self.activeNetworks.len:
|
||||
return false
|
||||
self.activeNetworks[index.row()] = value.boolVal()
|
||||
self.dataChanged(index, index, [self.extraRole.roleId])
|
||||
return true
|
||||
return self.sourceModel.setData(index, value, role)
|
||||
|
||||
proc rowData(self: NetworksExtraStoreProxy, index: int, column: string): string {.slot.} =
|
||||
if column == isActiveRoleName:
|
||||
if index < 0 or index >= self.activeNetworks.len:
|
||||
return ""
|
||||
return $self.activeNetworks[index]
|
||||
return self.sourceModel.rowData(index, column)
|
|
@ -3,9 +3,11 @@ import Tables, NimQml, sequtils, sugar
|
|||
import ../../../../app_service/service/network/dto
|
||||
import ./io_interface
|
||||
import ./model
|
||||
import ./networks_extra_store_proxy
|
||||
import ./item
|
||||
|
||||
proc networkEnabledToUxEnabledState(enabled: bool, allEnabled: bool): UxEnabledState
|
||||
proc areAllEnabled(networks: seq[NetworkDto]): bool
|
||||
|
||||
QtObject:
|
||||
type
|
||||
View* = ref object of QObject
|
||||
|
@ -15,9 +17,6 @@ QtObject:
|
|||
layer1: Model
|
||||
layer2: Model
|
||||
areTestNetworksEnabled: bool
|
||||
# Lazy initized but here to keep a reference to the object not to be GCed
|
||||
layer1Proxy: NetworksExtraStoreProxy
|
||||
layer2Proxy: NetworksExtraStoreProxy
|
||||
|
||||
proc setup(self: View) =
|
||||
self.QObject.setup
|
||||
|
@ -32,8 +31,6 @@ QtObject:
|
|||
result.layer1 = newModel()
|
||||
result.layer2 = newModel()
|
||||
result.enabled = newModel()
|
||||
result.layer1Proxy = nil
|
||||
result.layer2Proxy = nil
|
||||
result.setup()
|
||||
|
||||
proc areTestNetworksEnabledChanged*(self: View) {.signal.}
|
||||
|
@ -87,6 +84,7 @@ QtObject:
|
|||
|
||||
proc load*(self: View, networks: TableRef[NetworkDto, float64]) =
|
||||
var items: seq[Item] = @[]
|
||||
let allEnabled = areAllEnabled(toSeq(networks.keys))
|
||||
for n, balance in networks.pairs:
|
||||
items.add(initItem(
|
||||
n.chainId,
|
||||
|
@ -103,6 +101,8 @@ QtObject:
|
|||
n.chainColor,
|
||||
n.shortName,
|
||||
balance,
|
||||
# Ensure we mark all as enabled if all are enabled
|
||||
networkEnabledToUxEnabledState(n.enabled, allEnabled)
|
||||
))
|
||||
|
||||
self.all.setItems(items)
|
||||
|
@ -118,34 +118,24 @@ QtObject:
|
|||
self.delegate.viewDidLoad()
|
||||
|
||||
proc toggleNetwork*(self: View, chainId: int) {.slot.} =
|
||||
self.delegate.toggleNetwork(chainId)
|
||||
let (chainIds, enable) = self.all.networksToChangeStateOnUserActionFor(chainId)
|
||||
self.delegate.setNetworksState(chainIds, enable)
|
||||
|
||||
proc toggleTestNetworksEnabled*(self: View) {.slot.} =
|
||||
self.delegate.toggleTestNetworksEnabled()
|
||||
self.areTestNetworksEnabled = not self.areTestNetworksEnabled
|
||||
self.areTestNetworksEnabledChanged()
|
||||
|
||||
proc layer1ProxyChanged*(self: View) {.signal.}
|
||||
|
||||
proc getLayer1Proxy(self: View): QVariant {.slot.} =
|
||||
if self.layer1Proxy.isNil:
|
||||
self.layer1Proxy = newNetworksExtraStoreProxy(self.layer1)
|
||||
return newQVariant(self.layer1Proxy)
|
||||
|
||||
QtProperty[QVariant] layer1Proxy:
|
||||
read = getLayer1Proxy
|
||||
notify = layer1ProxyChanged
|
||||
|
||||
proc layer2ProxyChanged*(self: View) {.signal.}
|
||||
|
||||
proc getLayer2Proxy(self: View): QVariant {.slot.} =
|
||||
if self.layer2Proxy.isNil:
|
||||
self.layer2Proxy = newNetworksExtraStoreProxy(self.layer2)
|
||||
return newQVariant(self.layer2Proxy)
|
||||
|
||||
QtProperty[QVariant] layer2Proxy:
|
||||
read = getLayer2Proxy
|
||||
notify = layer2ProxyChanged
|
||||
|
||||
proc getMainnetChainId*(self: View): int {.slot.} =
|
||||
return self.layer1.getLayer1Network(self.areTestNetworksEnabled)
|
||||
|
||||
proc networkEnabledToUxEnabledState(enabled: bool, allEnabled: bool): UxEnabledState =
|
||||
return if allEnabled:
|
||||
UxEnabledState.AllEnabled
|
||||
elif enabled:
|
||||
UxEnabledState.Enabled
|
||||
else:
|
||||
UxEnabledState.Disabled
|
||||
|
||||
proc areAllEnabled(networks: seq[NetworkDto]): bool =
|
||||
return networks.allIt(it.enabled)
|
||||
|
|
|
@ -91,10 +91,14 @@ proc getNetwork*(self: Service, networkType: NetworkType): NetworkDto =
|
|||
# Will be removed, this is used in case of legacy chain Id
|
||||
return NetworkDto(chainId: networkType.toChainId())
|
||||
|
||||
proc toggleNetwork*(self: Service, chainId: int) =
|
||||
proc setNetworksState*(self: Service, chainIds: seq[int], enabled: bool) =
|
||||
for chainId in chainIds:
|
||||
let network = self.getNetwork(chainId)
|
||||
|
||||
network.enabled = not network.enabled
|
||||
if network.enabled == enabled:
|
||||
continue
|
||||
|
||||
network.enabled = enabled
|
||||
self.upsertNetwork(network)
|
||||
|
||||
proc getChainIdForEns*(self: Service): int =
|
||||
|
|
|
@ -442,8 +442,8 @@ QtObject:
|
|||
self.buildAllTokens(self.getAddresses(), store = true)
|
||||
self.events.emit(SIGNAL_WALLET_ACCOUNT_CURRENCY_UPDATED, CurrencyUpdated())
|
||||
|
||||
proc toggleNetworkEnabled*(self: Service, chainId: int) =
|
||||
self.networkService.toggleNetwork(chainId)
|
||||
proc setNetworksState*(self: Service, chainIds: seq[int], enabled: bool) =
|
||||
self.networkService.setNetworksState(chainIds, enabled)
|
||||
self.events.emit(SIGNAL_WALLET_ACCOUNT_NETWORK_ENABLED_UPDATED, NetwordkEnabledToggled())
|
||||
|
||||
method toggleTestNetworksEnabled*(self: Service) =
|
||||
|
|
|
@ -95,7 +95,7 @@ target_compile_definitions(QmlTests PRIVATE
|
|||
QML_IMPORT_ROOT="${CMAKE_CURRENT_LIST_DIR}"
|
||||
STATUSQ_MODULE_IMPORT_PATH="${STATUSQ_MODULE_IMPORT_PATH}"
|
||||
QUICK_TEST_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}/qmlTests")
|
||||
target_link_libraries(QmlTests PRIVATE Qt5::QuickTest Qt5::Qml ${PROJECT_LIB})
|
||||
target_link_libraries(QmlTests PRIVATE Qt5::QuickTest Qt5::Qml ${PROJECT_LIB} SortFilterProxyModel)
|
||||
add_test(NAME QmlTests COMMAND QmlTests)
|
||||
|
||||
list(APPEND QML_DIRS "${CMAKE_SOURCE_DIR}/../ui/app")
|
||||
|
|
|
@ -161,6 +161,10 @@ ListModel {
|
|||
title: "SelfDestructAlertPopup"
|
||||
section: "Popups"
|
||||
}
|
||||
ListElement {
|
||||
title: "NetworkSelectPopup"
|
||||
section: "Popups"
|
||||
}
|
||||
ListElement {
|
||||
title: "MembersSelector"
|
||||
section: "Components"
|
||||
|
@ -237,4 +241,8 @@ ListModel {
|
|||
title: "LanguageCurrencySettings"
|
||||
section: "Settings"
|
||||
}
|
||||
ListElement {
|
||||
title: "ProfileSocialLinksPanel"
|
||||
section: "Panels"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -119,6 +119,11 @@
|
|||
"LoginView": [
|
||||
"https://www.figma.com/file/17fc13UBFvInrLgNUKJJg5/Kuba%E2%8E%9CDesktop?node-id=1080%3A313192"
|
||||
],
|
||||
"NetworkSelectPopup": [
|
||||
"https://www.figma.com/file/FkFClTCYKf83RJWoifWgoX/Wallet-v2?node-id=13200-352357&t=jKciSCy3BVlrZmBs-0",
|
||||
"https://www.figma.com/file/FkFClTCYKf83RJWoifWgoX/Wallet-v2?node-id=13185-350333&t=b2AclcJgxjXDL6Wl-0",
|
||||
"https://www.figma.com/file/FkFClTCYKf83RJWoifWgoX/Wallet-v2?node-id=13187-359097&t=b2AclcJgxjXDL6Wl-0"
|
||||
],
|
||||
"PermissionConflictWarningPanel": [
|
||||
"https://www.figma.com/file/17fc13UBFvInrLgNUKJJg5/Kuba%E2%8E%9CDesktop?node-id=22253%3A486103&t=JrCIfks1zVzsk3vn-0"
|
||||
],
|
||||
|
|
|
@ -0,0 +1,323 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import StatusQ.Core 0.1
|
||||
import StatusQ.Core.Theme 0.1
|
||||
import StatusQ.Popups 0.1
|
||||
|
||||
import utils 1.0
|
||||
|
||||
import AppLayouts.Wallet.popups 1.0
|
||||
import AppLayouts.Wallet.controls 1.0
|
||||
import AppLayouts.stores 1.0
|
||||
|
||||
import Models 1.0
|
||||
|
||||
import SortFilterProxyModel 0.2
|
||||
|
||||
SplitView {
|
||||
id: root
|
||||
|
||||
Pane {
|
||||
SplitView.fillWidth: true
|
||||
SplitView.fillHeight: true
|
||||
|
||||
ColumnLayout {
|
||||
id: controlLayout
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
// Leave some space so that the popup will be opened without accounting for Layer
|
||||
ColumnLayout {
|
||||
Layout.maximumHeight: 50
|
||||
}
|
||||
|
||||
NetworkFilter {
|
||||
id: networkFilter
|
||||
|
||||
Layout.preferredWidth: 200
|
||||
Layout.preferredHeight: 100
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
||||
allNetworks: simulatedNimModel
|
||||
layer1Networks: SortFilterProxyModel {
|
||||
function rowData(index, propName) {
|
||||
return get(index)[propName]
|
||||
}
|
||||
sourceModel: simulatedNimModel
|
||||
filters: ValueFilter { roleName: "layer"; value: 1; }
|
||||
}
|
||||
layer2Networks: SortFilterProxyModel {
|
||||
sourceModel: simulatedNimModel
|
||||
filters: [ValueFilter { roleName: "layer"; value: 2; },
|
||||
ValueFilter { roleName: "isTest"; value: false; }]
|
||||
}
|
||||
testNetworks: SortFilterProxyModel {
|
||||
sourceModel: simulatedNimModel
|
||||
filters: [ValueFilter { roleName: "layer"; value: 2; },
|
||||
ValueFilter { roleName: "isTest"; value: true; }]
|
||||
}
|
||||
enabledNetworks: SortFilterProxyModel {
|
||||
sourceModel: simulatedNimModel
|
||||
filters: ValueFilter { roleName: "isEnabled"; value: true; }
|
||||
}
|
||||
|
||||
onToggleNetwork: (network) => {
|
||||
if(multiSelection) {
|
||||
simulatedNimModel.toggleNetwork(network)
|
||||
} else {
|
||||
lastSingleSelectionLabel.text = `[${network.chainName}] (NL) - ID: ${network.chainId}, Icon: ${network.iconUrl}`
|
||||
}
|
||||
}
|
||||
|
||||
multiSelection: multiSelectionCheckbox.checked
|
||||
isChainVisible: isChainVisibleCheckbox.checked
|
||||
}
|
||||
|
||||
// Dummy item to make space for popup
|
||||
Item {
|
||||
id: popupPlaceholder
|
||||
|
||||
Layout.preferredWidth: networkSelectPopup.width
|
||||
Layout.preferredHeight: networkSelectPopup.height
|
||||
|
||||
NetworkSelectPopup {
|
||||
id: networkSelectPopup
|
||||
|
||||
layer1Networks: networkFilter.layer1Networks
|
||||
layer2Networks: networkFilter.layer2Networks
|
||||
testNetworks: networkFilter.testNetworks
|
||||
|
||||
useEnabledRole: false
|
||||
|
||||
visible: true
|
||||
closePolicy: Popup.NoAutoClose
|
||||
|
||||
// Simulates a network toggle
|
||||
onToggleNetwork: (network, networkModel, index) => simulatedNimModel.toggleNetwork(network)
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.preferredHeight: 30
|
||||
Layout.maximumHeight: 30
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Button {
|
||||
text: "Single Selection Popup"
|
||||
onClicked: selectPopupLoader.active = true
|
||||
}
|
||||
Label {
|
||||
id: lastSingleSelectionLabel
|
||||
text: "-"
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: singleSelectionPopupPlaceholder
|
||||
|
||||
Layout.preferredWidth: selectPopupLoader.item ? selectPopupLoader.item.width : 0
|
||||
Layout.preferredHeight: selectPopupLoader.item ? selectPopupLoader.item.height : 0
|
||||
|
||||
property var currentModel: networkFilter.layer2Networks
|
||||
property int currentIndex: 0
|
||||
|
||||
Loader {
|
||||
id: selectPopupLoader
|
||||
|
||||
active: false
|
||||
|
||||
sourceComponent: NetworkSelectPopup {
|
||||
layer1Networks: networkFilter.layer1Networks
|
||||
layer2Networks: networkFilter.layer2Networks
|
||||
testNetworks: networkFilter.testNetworks
|
||||
|
||||
singleSelection {
|
||||
enabled: true
|
||||
currentModel: singleSelectionPopupPlaceholder.currentModel
|
||||
currentIndex: singleSelectionPopupPlaceholder.currentIndex
|
||||
}
|
||||
|
||||
onToggleNetwork: (network, networkModel, index) => {
|
||||
lastSingleSelectionLabel.text = `[${network.chainName}] - ID: ${network.chainId}, Icon: ${network.iconUrl}`
|
||||
singleSelectionPopupPlaceholder.currentModel = networkModel
|
||||
singleSelectionPopupPlaceholder.currentIndex = index
|
||||
}
|
||||
|
||||
onClosed: selectPopupLoader.active = false
|
||||
}
|
||||
|
||||
onLoaded: item.open()
|
||||
}
|
||||
}
|
||||
|
||||
// Vertical separator
|
||||
ColumnLayout {}
|
||||
}
|
||||
}
|
||||
Pane {
|
||||
SplitView.minimumWidth: 300
|
||||
SplitView.fillWidth: true
|
||||
SplitView.minimumHeight: 300
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
|
||||
ListView {
|
||||
id: allNetworksListView
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
model: simulatedNimModel
|
||||
|
||||
delegate: ItemDelegate {
|
||||
width: allNetworksListView.width
|
||||
implicitHeight: delegateRowLayout.implicitHeight
|
||||
|
||||
highlighted: ListView.isCurrentItem
|
||||
|
||||
RowLayout {
|
||||
id: delegateRowLayout
|
||||
anchors.fill: parent
|
||||
|
||||
Column {
|
||||
Layout.margins: 5
|
||||
|
||||
spacing: 3
|
||||
|
||||
Label { text: model.chainName }
|
||||
|
||||
Row {
|
||||
spacing: 5
|
||||
Label { text: `<b>${model.shortName}</b>` }
|
||||
Label { text: `ID <b>${model.chainId}</b>` }
|
||||
CheckBox {
|
||||
checkState: model.isEnabled ? Qt.Checked : Qt.Unchecked
|
||||
tristate: true
|
||||
nextCheckState: () => {
|
||||
const nextEnabled = (checkState !== Qt.Checked)
|
||||
availableNetworks.sourceModel.setProperty(availableNetworks.mapToSource(index), "isEnabled", nextEnabled)
|
||||
Qt.callLater(() => { simulatedNimModel.cloneModel(availableNetworks) })
|
||||
return nextEnabled ? Qt.Checked : Qt.Unchecked
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: allNetworksListView.currentIndex = index
|
||||
}
|
||||
}
|
||||
CheckBox {
|
||||
id: multiSelectionCheckbox
|
||||
|
||||
Layout.margins: 5
|
||||
|
||||
text: "Multi Selection"
|
||||
checked: true
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
id: testModeCheckbox
|
||||
|
||||
Layout.margins: 5
|
||||
|
||||
text: "Test Networks Mode"
|
||||
checked: false
|
||||
onCheckedChanged: Qt.callLater(simulatedNimModel.cloneModel, availableNetworks)
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
id: isChainVisibleCheckbox
|
||||
|
||||
Layout.margins: 5
|
||||
|
||||
text: "Is chain visible"
|
||||
checked: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SortFilterProxyModel {
|
||||
id: availableNetworks
|
||||
|
||||
// Simulate Nim's way of providing access to data
|
||||
function rowData(index, propName) {
|
||||
return get(index)[propName]
|
||||
}
|
||||
|
||||
sourceModel: NetworksModel.allNetworks
|
||||
filters: ValueFilter { roleName: "isTest"; value: testModeCheckbox.checked; }
|
||||
}
|
||||
|
||||
// Keep a clone so that the UX can be modified without affecting the original model
|
||||
CloneModel {
|
||||
id: simulatedNimModel
|
||||
|
||||
sourceModel: availableNetworks
|
||||
|
||||
roles: ["chainId", "layer", "chainName", "isTest", "isEnabled", "iconUrl", "shortName", "chainColor"]
|
||||
rolesOverride: [{ role: "enabledState", transform: (mD) => {
|
||||
return simulatedNimModel.areAllEnabled(sourceModel)
|
||||
? NetworkSelectPopup.UxEnabledState.AllEnabled
|
||||
: mD.isEnabled
|
||||
? NetworkSelectPopup.UxEnabledState.Enabled
|
||||
: NetworkSelectPopup.UxEnabledState.Disabled
|
||||
}
|
||||
}]
|
||||
|
||||
/// Simulate the Nim model
|
||||
function toggleNetwork(network) {
|
||||
const chainId = network.chainId
|
||||
let chainIdOnlyEnabled = true
|
||||
let chainIdOnlyDisabled = true
|
||||
let allEnabled = true
|
||||
for (let i = 0; i < simulatedNimModel.count; i++) {
|
||||
const item = simulatedNimModel.get(i)
|
||||
if(item.enabledState === NetworkSelectPopup.UxEnabledState.Enabled) {
|
||||
if(item.chainId !== chainId) {
|
||||
chainIdOnlyEnabled = false
|
||||
}
|
||||
} else if(item.enabledState === NetworkSelectPopup.UxEnabledState.Disabled) {
|
||||
if(item.chainId !== chainId) {
|
||||
chainIdOnlyDisabled = false
|
||||
}
|
||||
allEnabled = false
|
||||
} else {
|
||||
if(item.chainId === chainId) {
|
||||
chainIdOnlyDisabled = false
|
||||
chainIdOnlyEnabled = false
|
||||
}
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < simulatedNimModel.count; i++) {
|
||||
const item = simulatedNimModel.get(i)
|
||||
if(allEnabled) {
|
||||
simulatedNimModel.setProperty(i, "enabledState", item.chainId === chainId ? NetworkSelectPopup.UxEnabledState.Enabled : NetworkSelectPopup.UxEnabledState.Disabled)
|
||||
} else if(chainIdOnlyEnabled || chainIdOnlyDisabled) {
|
||||
simulatedNimModel.setProperty(i, "enabledState", NetworkSelectPopup.UxEnabledState.AllEnabled)
|
||||
} else if(item.chainId === chainId) {
|
||||
simulatedNimModel.setProperty(i, "enabledState", item.enabledState === NetworkSelectPopup.UxEnabledState.Enabled
|
||||
? NetworkSelectPopup.UxEnabledState.Disabled
|
||||
:NetworkSelectPopup.UxEnabledState.Enabled)
|
||||
}
|
||||
const haveEnabled = item.enabledState !== NetworkSelectPopup.UxEnabledState.Disabled
|
||||
if(item.isEnabled !== haveEnabled) {
|
||||
simulatedNimModel.setProperty(i, "isEnabled", haveEnabled)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function areAllEnabled(modelToCheck) {
|
||||
for (let i = 0; i < modelToCheck.count; i++) {
|
||||
if(!(modelToCheck.get(i).isEnabled)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,6 +5,10 @@ import QtQuick 2.15
|
|||
QtObject {
|
||||
|
||||
readonly property var layer1Networks: ListModel {
|
||||
function rowData(index, propName) {
|
||||
return get(index)[propName]
|
||||
}
|
||||
|
||||
Component.onCompleted:
|
||||
append([
|
||||
{
|
||||
|
@ -14,7 +18,8 @@ QtObject {
|
|||
isActive: true,
|
||||
isEnabled: true,
|
||||
shortName: "ETH",
|
||||
chainColor: "blue"
|
||||
chainColor: "blue",
|
||||
isTest: false
|
||||
}
|
||||
])
|
||||
}
|
||||
|
@ -29,7 +34,8 @@ QtObject {
|
|||
isActive: false,
|
||||
isEnabled: true,
|
||||
shortName: "OPT",
|
||||
chainColor: "red"
|
||||
chainColor: "red",
|
||||
isTest: false
|
||||
},
|
||||
{
|
||||
chainId: 3,
|
||||
|
@ -38,7 +44,8 @@ QtObject {
|
|||
isActive: false,
|
||||
isEnabled: true,
|
||||
shortName: "ARB",
|
||||
chainColor: "purple"
|
||||
chainColor: "purple",
|
||||
isTest: false
|
||||
}
|
||||
])
|
||||
}
|
||||
|
@ -53,7 +60,8 @@ QtObject {
|
|||
isActive: false,
|
||||
isEnabled: true,
|
||||
shortName: "HEZ",
|
||||
chainColor: "orange"
|
||||
chainColor: "orange",
|
||||
isTest: true
|
||||
},
|
||||
{
|
||||
chainId: 5,
|
||||
|
@ -62,7 +70,8 @@ QtObject {
|
|||
isActive: false,
|
||||
isEnabled: true,
|
||||
shortName: "TNET",
|
||||
chainColor: "lightblue"
|
||||
chainColor: "lightblue",
|
||||
isTest: true
|
||||
},
|
||||
{
|
||||
chainId: 6,
|
||||
|
@ -71,68 +80,180 @@ QtObject {
|
|||
isActive: false,
|
||||
isEnabled: true,
|
||||
shortName: "CUSTOM",
|
||||
chainColor: "orange"
|
||||
chainColor: "orange",
|
||||
isTest: true
|
||||
}
|
||||
])
|
||||
}
|
||||
|
||||
readonly property var enabledNetworks: ListModel {
|
||||
// Simulate Nim's way of providing access to data
|
||||
function rowData(index, propName) {
|
||||
return get(index)[propName]
|
||||
}
|
||||
|
||||
Component.onCompleted:
|
||||
append([
|
||||
{
|
||||
chainId: 1,
|
||||
layer: 1,
|
||||
chainName: "Ethereum Mainnet",
|
||||
iconUrl: ModelsData.networks.ethereum,
|
||||
isActive: true,
|
||||
isEnabled: true,
|
||||
isEnabled: false,
|
||||
shortName: "ETH",
|
||||
chainColor: "blue"
|
||||
chainColor: "blue",
|
||||
isTest: false
|
||||
},
|
||||
{
|
||||
chainId: 2,
|
||||
layer: 2,
|
||||
chainName: "Optimism",
|
||||
iconUrl: ModelsData.networks.optimism,
|
||||
isActive: false,
|
||||
isEnabled: true,
|
||||
shortName: "OPT",
|
||||
chainColor: "red"
|
||||
chainColor: "red",
|
||||
isTest: false
|
||||
},
|
||||
{
|
||||
chainId: 3,
|
||||
layer: 2,
|
||||
chainName: "Arbitrum",
|
||||
iconUrl: ModelsData.networks.arbitrum,
|
||||
isActive: false,
|
||||
isEnabled: true,
|
||||
shortName: "ARB",
|
||||
chainColor: "purple"
|
||||
chainColor: "purple",
|
||||
isTest: false
|
||||
},
|
||||
{
|
||||
chainId: 4,
|
||||
layer: 2,
|
||||
chainName: "Hermez",
|
||||
iconUrl: ModelsData.networks.hermez,
|
||||
isActive: false,
|
||||
isEnabled: true,
|
||||
shortName: "HEZ",
|
||||
chainColor: "orange"
|
||||
chainColor: "orange",
|
||||
isTest: false
|
||||
},
|
||||
{
|
||||
chainId: 5,
|
||||
layer: 1,
|
||||
chainName: "Testnet",
|
||||
iconUrl: ModelsData.networks.testnet,
|
||||
isActive: false,
|
||||
isEnabled: true,
|
||||
shortName: "TNET",
|
||||
chainColor: "lightblue"
|
||||
chainColor: "lightblue",
|
||||
isTest: true
|
||||
},
|
||||
{
|
||||
chainId: 6,
|
||||
layer: 1,
|
||||
chainName: "Custom",
|
||||
iconUrl: ModelsData.networks.custom,
|
||||
isActive: false,
|
||||
isEnabled: true,
|
||||
shortName: "CUSTOM",
|
||||
chainColor: "orange"
|
||||
chainColor: "orange",
|
||||
isTest: false
|
||||
}
|
||||
])
|
||||
}
|
||||
|
||||
readonly property var allNetworks: ListModel {
|
||||
// Simulate Nim's way of providing access to data
|
||||
function rowData(index, propName) {
|
||||
return get(index)[propName]
|
||||
}
|
||||
|
||||
Component.onCompleted: append([
|
||||
{
|
||||
chainId: 1,
|
||||
chainName: "Ethereum Mainnet",
|
||||
blockExplorerUrl: "https://etherscan.io/",
|
||||
iconUrl: "network/Network=Ethereum",
|
||||
chainColor: "#627EEA",
|
||||
shortName: "eth",
|
||||
nativeCurrencyName: "Ether",
|
||||
nativeCurrencySymbol: "ETH",
|
||||
nativeCurrencyDecimals: 18,
|
||||
isTest: false,
|
||||
layer: 1,
|
||||
isEnabled: true,
|
||||
},
|
||||
{
|
||||
chainId: 5,
|
||||
chainName: "Goerli",
|
||||
blockExplorerUrl: "https://goerli.etherscan.io/",
|
||||
iconUrl: "network/Network=Testnet",
|
||||
chainColor: "#939BA1",
|
||||
shortName: "goEth",
|
||||
nativeCurrencyName: "Ether",
|
||||
nativeCurrencySymbol: "ETH",
|
||||
nativeCurrencyDecimals: 18,
|
||||
isTest: true,
|
||||
layer: 1,
|
||||
isEnabled: true,
|
||||
},
|
||||
{
|
||||
chainId: 10,
|
||||
chainName: "Optimism",
|
||||
blockExplorerUrl: "https://optimistic.etherscan.io",
|
||||
iconUrl: "network/Network=Optimism",
|
||||
chainColor: "#E90101",
|
||||
shortName: "opt",
|
||||
nativeCurrencyName: "Ether",
|
||||
nativeCurrencySymbol: "ETH",
|
||||
nativeCurrencyDecimals: 18,
|
||||
isTest: false,
|
||||
layer: 2,
|
||||
isEnabled: true,
|
||||
},
|
||||
{
|
||||
chainId: 420,
|
||||
chainName: "Optimism Goerli Testnet",
|
||||
blockExplorerUrl: "https://goerli-optimism.etherscan.io/",
|
||||
iconUrl: "network/Network=Testnet",
|
||||
chainColor: "#939BA1",
|
||||
shortName: "goOpt",
|
||||
nativeCurrencyName: "Ether",
|
||||
nativeCurrencySymbol: "ETH",
|
||||
nativeCurrencyDecimals: 18,
|
||||
isTest: true,
|
||||
layer: 2,
|
||||
isEnabled: true,
|
||||
},
|
||||
{
|
||||
chainId: 42161,
|
||||
chainName: "Arbitrum",
|
||||
blockExplorerUrl: "https://arbiscan.io/",
|
||||
iconUrl: "network/Network=Arbitrum",
|
||||
chainColor: "#51D0F0",
|
||||
shortName: "arb",
|
||||
nativeCurrencyName: "Ether",
|
||||
nativeCurrencySymbol: "ETH",
|
||||
nativeCurrencyDecimals: 18,
|
||||
isTest: false,
|
||||
layer: 2,
|
||||
isEnabled: true,
|
||||
},
|
||||
{
|
||||
chainId: 421613,
|
||||
chainName: "Arbitrum Goerli",
|
||||
blockExplorerUrl: "https://goerli.arbiscan.io/",
|
||||
iconUrl: "network/Network=Testnet",
|
||||
chainColor: "#939BA1",
|
||||
shortName: "goArb",
|
||||
nativeCurrencyName: "Ether",
|
||||
nativeCurrencySymbol: "ETH",
|
||||
nativeCurrencyDecimals: 18,
|
||||
isTest: true,
|
||||
layer: 2,
|
||||
isEnabled: false,
|
||||
}]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -431,6 +431,11 @@ class StatusWalletScreen:
|
|||
self._find_saved_address_and_open_menu(name)
|
||||
|
||||
click_obj_by_name(SavedAddressesScreen.EDIT.value)
|
||||
|
||||
# Delete existing text
|
||||
type_text(AddSavedAddressPopup.NAME_INPUT.value, "<Ctrl+A>")
|
||||
type_text(AddSavedAddressPopup.NAME_INPUT.value, "<Del>")
|
||||
|
||||
type_text(AddSavedAddressPopup.NAME_INPUT.value, new_name)
|
||||
click_obj_by_name(AddSavedAddressPopup.ADD_BUTTON.value)
|
||||
|
||||
|
|
|
@ -17,10 +17,10 @@ Feature: Status Desktop Wallet
|
|||
Scenario Outline: The user can manage a saved address
|
||||
When the user adds a saved address named "<name>" and address "<address>"
|
||||
And the user edits a saved address with name "<name>" to "<new_name>"
|
||||
Then the name "<new_name><name>" is in the list of saved addresses
|
||||
Then the name "<new_name>" is in the list of saved addresses
|
||||
|
||||
When the user deletes the saved address with name "<new_name><name>"
|
||||
Then the name "<new_name><name>" is not in the list of saved addresses
|
||||
When the user deletes the saved address with name "<new_name>"
|
||||
Then the name "<new_name>" is not in the list of saved addresses
|
||||
|
||||
# Test for toggling favourite button is disabled until favourite functionality is enabled
|
||||
# When the user adds a saved address named "<name>" and address "<address>"
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import QtQuick 2.14
|
||||
import QtQuick.Controls 2.14
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
|
||||
import StatusQ.Core 0.1
|
||||
import StatusQ.Core.Theme 0.1
|
||||
|
@ -15,6 +15,7 @@ CheckBox {
|
|||
- Regular (default size)
|
||||
*/
|
||||
property int size: StatusCheckBox.Size.Regular
|
||||
property bool changeCursor: true
|
||||
|
||||
enum Size {
|
||||
Small,
|
||||
|
@ -49,8 +50,9 @@ CheckBox {
|
|||
x: !root.leftSide? root.rightPadding : root.leftPadding
|
||||
y: parent.height / 2 - height / 2
|
||||
radius: 2
|
||||
color: (root.down || root.checked) ? Theme.palette.primaryColor1
|
||||
: Theme.palette.directColor8
|
||||
color: root.down || checkState !== Qt.Checked
|
||||
? Theme.palette.directColor8
|
||||
: Theme.palette.primaryColor1
|
||||
|
||||
StatusIcon {
|
||||
icon: "checkbox"
|
||||
|
@ -60,8 +62,8 @@ CheckBox {
|
|||
? d.indicatorIconHeightRegular : d.indicatorIconHeightSmall
|
||||
anchors.centerIn: parent
|
||||
anchors.horizontalCenterOffset: 1
|
||||
color: Theme.palette.white
|
||||
visible: root.down || root.checked
|
||||
color: checkState === Qt.PartiallyChecked ? Theme.palette.directColor9 : Theme.palette.white
|
||||
visible: root.down || checkState !== Qt.Unchecked
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -78,4 +80,9 @@ CheckBox {
|
|||
rightPadding: !root.leftSide? (!!root.text ? root.indicator.width + root.spacing
|
||||
: root.indicator.width) : 0
|
||||
}
|
||||
|
||||
HoverHandler {
|
||||
acceptedDevices: PointerDevice.Mouse
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
}
|
||||
}
|
||||
|
|
|
@ -270,18 +270,20 @@ StatusScrollView {
|
|||
|
||||
NetworkFilter {
|
||||
Layout.preferredWidth: 160
|
||||
|
||||
allNetworks: root.allNetworks
|
||||
layer1Networks: root.layer1Networks
|
||||
layer2Networks: root.layer2Networks
|
||||
testNetworks: root.testNetworks
|
||||
enabledNetworks: root.enabledNetworks
|
||||
allNetworks: root.allNetworks
|
||||
|
||||
isChainVisible: false
|
||||
multiSelection: false
|
||||
|
||||
onSingleNetworkSelected: {
|
||||
root.chainId = chainId
|
||||
root.chainName = chainName
|
||||
root.chainIcon = chainIcon
|
||||
onToggleNetwork: (network) => {
|
||||
root.chainId = network.chainId
|
||||
root.chainName = network.chainName
|
||||
root.chainIcon = network.iconUrl
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ import QtQuick 2.15
|
|||
/// \note ensures data model always has consecutive Separator and Number after Base without duplicates except current element
|
||||
/// \note for future work: split deleteInContent in deleteInContent and deleteElements and move data model to a DataModel object;
|
||||
/// also fix code duplication in parseDerivationPath generating static level definitions and iterate through it
|
||||
/// \note using Item to support embedded sub-components
|
||||
Item {
|
||||
id: root
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import QtQuick 2.13
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
|
||||
import StatusQ.Core 0.1
|
||||
import StatusQ.Core.Theme 0.1
|
||||
|
@ -16,28 +17,44 @@ Item {
|
|||
implicitWidth: 130
|
||||
implicitHeight: parent.height
|
||||
|
||||
property var layer1Networks
|
||||
property var layer2Networks
|
||||
property var testNetworks
|
||||
property var enabledNetworks
|
||||
property var allNetworks
|
||||
required property var allNetworks
|
||||
required property var layer1Networks
|
||||
required property var layer2Networks
|
||||
required property var testNetworks
|
||||
required property var enabledNetworks
|
||||
|
||||
property bool isChainVisible: true
|
||||
property bool multiSelection: true
|
||||
|
||||
signal toggleNetwork(int chainId)
|
||||
signal singleNetworkSelected(int chainId, string chainName, string chainIcon)
|
||||
/// \c network is a network.model.nim entry
|
||||
/// It is called for every toggled network if \c multiSelection is \c true
|
||||
/// If \c multiSelection is \c false, it is called only for the selected network when the selection changes
|
||||
signal toggleNetwork(var network)
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
|
||||
property string selectedChainName: ""
|
||||
property string selectedIconUrl: ""
|
||||
|
||||
// Persist selection between selectPopupLoader reloads
|
||||
property var currentModel: layer1Networks
|
||||
property int currentIndex: 0
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
if (d.currentModel.count > 0) {
|
||||
d.selectedChainName = d.currentModel.rowData(d.currentIndex, "chainName")
|
||||
d.selectedIconUrl = d.currentModel.rowData(d.currentIndex, "iconUrl")
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: selectRectangleItem
|
||||
|
||||
width: parent.width
|
||||
height: 56
|
||||
|
||||
// FIXME this should be a (styled) ComboBox
|
||||
StatusListItem {
|
||||
implicitWidth: parent.width
|
||||
|
@ -52,8 +69,11 @@ Item {
|
|||
statusListItemTitle.font.pixelSize: 13
|
||||
statusListItemTitle.font.weight: Font.Medium
|
||||
statusListItemTitle.color: Theme.palette.baseColor1
|
||||
title: root.multiSelection ? (root.enabledNetworks.count === root.allNetworks.count ? qsTr("All networks") : qsTr("%n network(s)", "", root.enabledNetworks.count)) :
|
||||
d.selectedChainName
|
||||
title: root.multiSelection
|
||||
? (root.enabledNetworks.count === root.allNetworks.count
|
||||
? qsTr("All networks")
|
||||
: qsTr("%n network(s)", "", root.enabledNetworks.count))
|
||||
: d.selectedChainName
|
||||
asset.height: 24
|
||||
asset.width: asset.height
|
||||
asset.isImage: !root.multiSelection
|
||||
|
@ -66,12 +86,9 @@ Item {
|
|||
color: Theme.palette.baseColor1
|
||||
}
|
||||
]
|
||||
|
||||
onClicked: {
|
||||
if (selectPopup.opened) {
|
||||
selectPopup.close();
|
||||
} else {
|
||||
selectPopup.open();
|
||||
}
|
||||
selectPopupLoader.active = !selectPopupLoader.active
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -94,23 +111,41 @@ Item {
|
|||
}
|
||||
}
|
||||
|
||||
NetworkSelectPopup {
|
||||
Loader {
|
||||
id: selectPopupLoader
|
||||
|
||||
active: false
|
||||
|
||||
sourceComponent: NetworkSelectPopup {
|
||||
id: selectPopup
|
||||
x: (parent.width - width + 5)
|
||||
y: (selectRectangleItem.height + 5)
|
||||
|
||||
x: -width + selectRectangleItem.width + 5
|
||||
y: selectRectangleItem.height + 5
|
||||
|
||||
layer1Networks: root.layer1Networks
|
||||
layer2Networks: root.layer2Networks
|
||||
testNetworks: root.testNetworks
|
||||
multiSelection: root.multiSelection
|
||||
|
||||
onToggleNetwork: {
|
||||
root.toggleNetwork(network.chainId)
|
||||
singleSelection {
|
||||
enabled: !root.multiSelection
|
||||
currentModel: d.currentModel
|
||||
currentIndex: d.currentIndex
|
||||
}
|
||||
|
||||
onSingleNetworkSelected: {
|
||||
d.selectedChainName = chainName
|
||||
d.selectedIconUrl = iconUrl
|
||||
root.singleNetworkSelected(chainId, chainName, iconUrl)
|
||||
useEnabledRole: false
|
||||
|
||||
onToggleNetwork: (network, networkModel, index) => {
|
||||
d.selectedChainName = network.chainName
|
||||
d.selectedIconUrl = network.iconUrl
|
||||
d.currentModel = networkModel
|
||||
d.currentIndex = index
|
||||
root.toggleNetwork(network)
|
||||
}
|
||||
|
||||
|
||||
onClosed: selectPopupLoader.active = false
|
||||
}
|
||||
|
||||
onLoaded: item.open()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
NetworkFilter 1.0 NetworkFilter.qml
|
|
@ -54,15 +54,19 @@ Item {
|
|||
// network filter
|
||||
NetworkFilter {
|
||||
id: networkFilter
|
||||
|
||||
Layout.alignment: Qt.AlignTrailing
|
||||
Layout.rowSpan: 2
|
||||
|
||||
allNetworks: walletStore.allNetworks
|
||||
layer1Networks: walletStore.layer1Networks
|
||||
layer2Networks: walletStore.layer2Networks
|
||||
testNetworks: walletStore.testNetworks
|
||||
enabledNetworks: walletStore.enabledNetworks
|
||||
allNetworks: walletStore.allNetworks
|
||||
|
||||
onToggleNetwork: walletStore.toggleNetwork(chainId)
|
||||
onToggleNetwork: (network) => {
|
||||
walletStore.toggleNetwork(network.chainId)
|
||||
}
|
||||
}
|
||||
|
||||
StatusAddressPanel {
|
||||
|
|
|
@ -16,8 +16,10 @@ import StatusQ.Components 0.1
|
|||
|
||||
import SortFilterProxyModel 0.2
|
||||
|
||||
import "../controls"
|
||||
import AppLayouts.stores 1.0
|
||||
|
||||
import "../stores"
|
||||
import "../controls"
|
||||
import ".."
|
||||
|
||||
StatusDialog {
|
||||
|
@ -43,7 +45,7 @@ StatusDialog {
|
|||
readonly property int validationMode: root.edit ?
|
||||
StatusInput.ValidationMode.Always
|
||||
: StatusInput.ValidationMode.OnlyWhenDirty
|
||||
readonly property bool valid: addressInput.valid && nameInput.valid
|
||||
readonly property bool valid: addressInput.valid && nameInput.valid && root.address !== Constants.zeroAddress
|
||||
property bool chainShortNamesDirty: false
|
||||
readonly property bool dirty: nameInput.input.dirty || chainShortNamesDirty
|
||||
|
||||
|
@ -51,6 +53,9 @@ StatusDialog {
|
|||
readonly property string visibleAddress: root.address == Constants.zeroAddress ? "" : root.address
|
||||
readonly property bool addressInputIsENS: !visibleAddress
|
||||
|
||||
/// Ensures that the \c root.address and \c root.chainShortNames are not reset when the initial text is set
|
||||
property bool initialized: false
|
||||
|
||||
function getPrefixArrayWithColumns(prefixStr) {
|
||||
return prefixStr.match(d.chainPrefixRegexPattern)
|
||||
}
|
||||
|
@ -73,6 +78,8 @@ StatusDialog {
|
|||
}
|
||||
|
||||
onOpened: {
|
||||
d.initialized = true
|
||||
|
||||
if(edit || addAddress) {
|
||||
if (root.ens)
|
||||
addressInput.setPlainText(root.ens)
|
||||
|
@ -146,7 +153,7 @@ StatusDialog {
|
|||
property string plainText: input.edit.getText(0, text.length)
|
||||
|
||||
onTextChanged: {
|
||||
if (skipTextUpdate)
|
||||
if (skipTextUpdate || !d.initialized)
|
||||
return
|
||||
|
||||
plainText = input.edit.getText(0, text.length)
|
||||
|
@ -261,7 +268,7 @@ StatusDialog {
|
|||
}
|
||||
|
||||
onCountChanged: {
|
||||
if (!networkSelector.modelUpdateBlocked) {
|
||||
if (!networkSelector.modelUpdateBlocked && d.initialized) {
|
||||
// Initially source model is empty, filter proxy is also empty, but does
|
||||
// extra work and mistakenly overwrites root.chainShortNames property
|
||||
if (sourceModel.count != 0) {
|
||||
|
@ -310,7 +317,7 @@ StatusDialog {
|
|||
}
|
||||
}
|
||||
|
||||
onToggleNetwork: {
|
||||
onToggleNetwork: (network) => {
|
||||
network.isEnabled = !network.isEnabled
|
||||
d.chainShortNamesDirty = true
|
||||
}
|
||||
|
@ -338,9 +345,13 @@ StatusDialog {
|
|||
}
|
||||
}
|
||||
|
||||
ListModel {
|
||||
CloneModel {
|
||||
id: allNetworksModelCopy
|
||||
|
||||
sourceModel: store.allNetworks
|
||||
roles: ["layer", "chainId", "chainColor", "chainName","shortName", "iconUrl"]
|
||||
rolesOverride: [{ role: "isEnabled", transform: (modelData) => Boolean(false) }]
|
||||
|
||||
function setEnabledNetworks(prefixArr) {
|
||||
networkSelector.blockModelUpdate(true)
|
||||
for (let i = 0; i < count; i++) {
|
||||
|
@ -349,26 +360,5 @@ StatusDialog {
|
|||
}
|
||||
networkSelector.blockModelUpdate(false)
|
||||
}
|
||||
|
||||
function init(model, address) {
|
||||
const prefixStr = Utils.getChainsPrefix(address)
|
||||
for (let i = 0; i < model.count; i++) {
|
||||
const clonedItem = {
|
||||
layer: model.rowData(i, "layer"),
|
||||
chainId: model.rowData(i, "chainId"),
|
||||
chainColor: model.rowData(i, "chainColor"),
|
||||
chainName: model.rowData(i, "chainName"),
|
||||
shortName: model.rowData(i, "shortName"),
|
||||
iconUrl: model.rowData(i, "iconUrl"),
|
||||
isEnabled: Boolean(prefixStr.length > 0 && prefixStr.includes(shortName))
|
||||
}
|
||||
|
||||
append(clonedItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
allNetworksModelCopy.init(store.allNetworks, root.address)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import QtQuick 2.13
|
||||
import QtQuick.Controls 2.13
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.3
|
||||
import QtGraphicalEffects 1.0
|
||||
|
||||
|
@ -7,31 +7,58 @@ import StatusQ.Core 0.1
|
|||
import StatusQ.Core.Theme 0.1
|
||||
import StatusQ.Components 0.1
|
||||
import StatusQ.Controls 0.1
|
||||
import StatusQ.Popups.Dialog 0.1
|
||||
|
||||
import utils 1.0
|
||||
|
||||
// TODO: replace with StatusModal
|
||||
Popup {
|
||||
import SortFilterProxyModel 0.2
|
||||
|
||||
import "./NetworkSelectPopup"
|
||||
|
||||
StatusDialog {
|
||||
id: root
|
||||
|
||||
modal: false
|
||||
standardButtons: Dialog.NoButton
|
||||
|
||||
anchors.centerIn: undefined
|
||||
|
||||
padding: 4
|
||||
width: 360
|
||||
height: Math.min(432, scrollView.contentHeight + root.padding)
|
||||
implicitHeight: Math.min(432, scrollView.contentHeight + root.padding * 2)
|
||||
|
||||
horizontalPadding: 5
|
||||
verticalPadding: 5
|
||||
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
|
||||
|
||||
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent
|
||||
property var layer1Networks
|
||||
property var layer2Networks
|
||||
property var testNetworks
|
||||
required property var layer1Networks
|
||||
required property var layer2Networks
|
||||
property var testNetworks: null
|
||||
|
||||
// If true NetworksExtraStoreProxy expected for layer1Networks and layer2Networks properties
|
||||
property bool useNetworksExtraStoreProxy: false
|
||||
/// Grouped properties for single selection state. \c singleSelection.enabled is \c false by default
|
||||
/// \see SingleSelectionInfo
|
||||
property alias singleSelection: d.singleSelection
|
||||
|
||||
property bool multiSelection: true
|
||||
property bool useEnabledRole: true
|
||||
|
||||
/// \c network is a network.model.nim entry. \c model and \c index for the current selection
|
||||
/// It is called for every toggled network if \c singleSelection.enabled is \c false
|
||||
/// If \c singleSelection.enabled is \c true, it is called only for the selected network when the selection changes
|
||||
/// \see SingleSelectionInfo
|
||||
signal toggleNetwork(var network, var model, int index)
|
||||
|
||||
/// Mirrors Nim's UxEnabledState enum from networks/item.nim
|
||||
enum UxEnabledState {
|
||||
Enabled,
|
||||
AllEnabled,
|
||||
Disabled
|
||||
}
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
|
||||
property SingleSelectionInfo singleSelection: SingleSelectionInfo {}
|
||||
property SingleSelectionInfo tmpObject: SingleSelectionInfo { enabled: true }
|
||||
}
|
||||
|
||||
signal toggleNetwork(var network)
|
||||
signal singleNetworkSelected(int chainId, string chainName, string iconUrl)
|
||||
|
||||
background: Rectangle {
|
||||
radius: Style.current.radius
|
||||
|
@ -50,6 +77,7 @@ Popup {
|
|||
|
||||
contentItem: StatusScrollView {
|
||||
id: scrollView
|
||||
|
||||
width: root.width
|
||||
height: root.height
|
||||
contentHeight: content.height
|
||||
|
@ -65,12 +93,16 @@ Popup {
|
|||
|
||||
Repeater {
|
||||
id: chainRepeater1
|
||||
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
|
||||
objectName: "networkSelectPopupChainRepeaterLayer1"
|
||||
model: root.layer1Networks
|
||||
|
||||
delegate: chainItem
|
||||
delegate: ChainItemDelegate {
|
||||
networkModel: chainRepeater1.model
|
||||
}
|
||||
}
|
||||
|
||||
StatusBaseText {
|
||||
|
@ -87,23 +119,28 @@ Popup {
|
|||
|
||||
Repeater {
|
||||
id: chainRepeater2
|
||||
model: root.layer2Networks
|
||||
|
||||
delegate: chainItem
|
||||
model: root.layer2Networks
|
||||
delegate: ChainItemDelegate {
|
||||
networkModel: chainRepeater2.model
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
id: chainRepeater3
|
||||
model: root.testNetworks
|
||||
|
||||
delegate: chainItem
|
||||
delegate: ChainItemDelegate {
|
||||
networkModel: chainRepeater3.model
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: chainItem
|
||||
StatusListItem {
|
||||
component ChainItemDelegate: StatusListItem {
|
||||
id: chainItemDelegate
|
||||
|
||||
property var networkModel: null
|
||||
|
||||
objectName: model.chainName
|
||||
implicitHeight: 48
|
||||
implicitWidth: scrollView.width
|
||||
|
@ -113,47 +150,65 @@ Popup {
|
|||
asset.isImage: true
|
||||
asset.name: Style.svg(model.iconUrl)
|
||||
onClicked: {
|
||||
if(root.multiSelection)
|
||||
toggleModelIsActive()
|
||||
else {
|
||||
// Don't allow uncheck
|
||||
if(!radioButton.checked) radioButton.toggle()
|
||||
if(!d.singleSelection.enabled) {
|
||||
checkBox.nextCheckState()
|
||||
} else if(!radioButton.checked) { // Don't allow uncheck
|
||||
radioButton.toggle()
|
||||
}
|
||||
}
|
||||
|
||||
function toggleModelIsActive() {
|
||||
model.isActive = !model.isActive
|
||||
}
|
||||
|
||||
components: [
|
||||
StatusCheckBox {
|
||||
id: checkBox
|
||||
visible: root.multiSelection
|
||||
checked: root.useNetworksExtraStoreProxy ? model.isActive : model.isEnabled
|
||||
onToggled: {
|
||||
if (root.useNetworksExtraStoreProxy) {
|
||||
toggleModelIsActive()
|
||||
tristate: true
|
||||
visible: !d.singleSelection.enabled
|
||||
|
||||
checkState: {
|
||||
if(root.useEnabledRole) {
|
||||
return model.isEnabled ? Qt.Checked : Qt.Unchecked
|
||||
} else if(model.enabledState === NetworkSelectPopup.Enabled) {
|
||||
return Qt.Checked
|
||||
} else {
|
||||
root.toggleNetwork(model)
|
||||
if( model.enabledState === NetworkSelectPopup.AllEnabled) {
|
||||
return Qt.PartiallyChecked
|
||||
} else {
|
||||
return Qt.Unchecked
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nextCheckState: () => {
|
||||
Qt.callLater(root.toggleNetwork, model, chainItemDelegate.networkModel, model.index)
|
||||
return Qt.PartiallyChecked
|
||||
}
|
||||
},
|
||||
StatusRadioButton {
|
||||
id: radioButton
|
||||
visible: !root.multiSelection
|
||||
visible: d.singleSelection.enabled
|
||||
size: StatusRadioButton.Size.Large
|
||||
ButtonGroup.group: radioBtnGroup
|
||||
checked: model.index === 0
|
||||
checked: d.singleSelection.currentModel === chainItemDelegate.networkModel && d.singleSelection.currentIndex === model.index
|
||||
|
||||
property SingleSelectionInfo exchangeObject: null
|
||||
function setNewInfo(networkModel, index) {
|
||||
d.tmpObject.currentModel = networkModel
|
||||
d.tmpObject.currentIndex = index
|
||||
exchangeObject = d.tmpObject
|
||||
d.tmpObject = d.singleSelection
|
||||
d.singleSelection = exchangeObject
|
||||
exchangeObject = null
|
||||
}
|
||||
|
||||
onCheckedChanged: {
|
||||
if(checked && !root.multiSelection) {
|
||||
root.singleNetworkSelected(model.chainId, model.chainName, model.iconUrl)
|
||||
if(checked && (d.singleSelection.currentModel !== chainItemDelegate.networkModel || d.singleSelection.currentIndex !== model.index)) {
|
||||
setNewInfo(chainItemDelegate.networkModel, model.index)
|
||||
root.toggleNetwork(model, chainItemDelegate.networkModel, model.index)
|
||||
close()
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
ButtonGroup {
|
||||
id: radioBtnGroup
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
import QtQml 2.15
|
||||
|
||||
/// Inline component was failing on Linux with "Cannot assign to property of unknown type" so we need to use a separate file for it.
|
||||
QtObject {
|
||||
property bool enabled: false
|
||||
property var currentModel: root.layer1Networks
|
||||
property int currentIndex: 0
|
||||
}
|
|
@ -16,6 +16,8 @@ import utils 1.0
|
|||
import shared.controls 1.0
|
||||
import shared.popups 1.0
|
||||
|
||||
|
||||
import AppLayouts.stores 1.0
|
||||
import "../stores"
|
||||
|
||||
StatusModal {
|
||||
|
@ -42,15 +44,6 @@ StatusModal {
|
|||
showHeader: false
|
||||
showAdvancedHeader: true
|
||||
|
||||
// When no network is selected reset the prefix to empty string
|
||||
Connections {
|
||||
target: RootStore.enabledNetworks
|
||||
function onModelReset() {
|
||||
if(RootStore.enabledNetworks.count === 0)
|
||||
root.networkPrefix = ""
|
||||
}
|
||||
}
|
||||
|
||||
hasFloatingButtons: true
|
||||
advancedHeaderComponent: AccountsModalHeader {
|
||||
model: RootStore.accounts
|
||||
|
@ -97,7 +90,7 @@ StatusModal {
|
|||
flow: Grid.TopToBottom
|
||||
columns: need2Columns ? 2 : 1
|
||||
spacing: 5
|
||||
property var networkProxies: [RootStore.layer1NetworksProxy, RootStore.layer2NetworksProxy]
|
||||
property var networkProxies: [layer1NetworksClone, layer2NetworksClone]
|
||||
Repeater {
|
||||
model: multiChainList.networkProxies.length
|
||||
delegate: Repeater {
|
||||
|
@ -106,7 +99,7 @@ StatusModal {
|
|||
tagPrimaryLabel.text: model.shortName
|
||||
tagPrimaryLabel.color: model.chainColor
|
||||
image.source: Style.svg("tiny/" + model.iconUrl)
|
||||
visible: model.isActive
|
||||
visible: model.isEnabled
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
@ -116,6 +109,7 @@ StatusModal {
|
|||
}
|
||||
}
|
||||
StatusRoundButton {
|
||||
id: editButton
|
||||
width: 32
|
||||
height: 32
|
||||
icon.name: "edit_pencil"
|
||||
|
@ -212,7 +206,7 @@ StatusModal {
|
|||
font.pixelSize: 15
|
||||
color: chainColor
|
||||
text: shortName + ":"
|
||||
visible: model.isActive
|
||||
visible: model.isEnabled
|
||||
onVisibleChanged: {
|
||||
if (visible) {
|
||||
networkPrefix += text
|
||||
|
@ -253,15 +247,37 @@ StatusModal {
|
|||
NetworkSelectPopup {
|
||||
id: selectPopup
|
||||
|
||||
x: multiChainList.x + Style.current.xlPadding + Style.current.halfPadding
|
||||
y: centralLayout.y
|
||||
x: multiChainList.x + editButton.width + 9
|
||||
y: tabBar.y + tabBar.height
|
||||
|
||||
layer1Networks: RootStore.layer1NetworksProxy
|
||||
layer2Networks: RootStore.layer2NetworksProxy
|
||||
testNetworks: RootStore.testNetworks
|
||||
useNetworksExtraStoreProxy: true
|
||||
layer1Networks: layer1NetworksClone
|
||||
layer2Networks: layer2NetworksClone
|
||||
|
||||
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
|
||||
|
||||
onToggleNetwork: (network, networkModel, index) => {
|
||||
network.isEnabled = !network.isEnabled
|
||||
}
|
||||
|
||||
CloneModel {
|
||||
id: layer1NetworksClone
|
||||
|
||||
sourceModel: RootStore.layer1Networks
|
||||
roles: ["layer", "chainId", "chainColor", "chainName","shortName", "iconUrl", "isEnabled"]
|
||||
// rowData used to clone returns string. Convert it to bool for bool arithmetics
|
||||
rolesOverride: [{
|
||||
role: "isEnabled",
|
||||
transform: (modelData) => Boolean(modelData.isEnabled)
|
||||
}]
|
||||
}
|
||||
|
||||
CloneModel {
|
||||
id: layer2NetworksClone
|
||||
|
||||
sourceModel: RootStore.layer2Networks
|
||||
roles: layer1NetworksClone.roles
|
||||
rolesOverride: layer1NetworksClone.rolesOverride
|
||||
}
|
||||
}
|
||||
|
||||
states: [
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
NetworkSelectPopup 1.0 NetworkSelectPopup.qml
|
|
@ -65,8 +65,6 @@ QtObject {
|
|||
onAllNetworksChanged: {
|
||||
d.initChainColors(allNetworks)
|
||||
}
|
||||
property var layer1NetworksProxy: networksModule.layer1Proxy
|
||||
property var layer2NetworksProxy: networksModule.layer2Proxy
|
||||
|
||||
property var cryptoRampServicesModel: walletSectionBuySellCrypto.model
|
||||
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
import QtQuick 2.15
|
||||
|
||||
/// Helper item to clone a model and alter its data without affecting the original model
|
||||
/// \beware this is not a proxy model. It clones the initial state
|
||||
/// and every time the instance changes and doesn't adapt when the data
|
||||
/// in the source model \c allNetworksModel changes
|
||||
/// \beware use it with small models and in temporary views (e.g. popups)
|
||||
/// \note requires `rowData` to be implemented in the model
|
||||
/// \note tried to use SortFilterProxyModel with but it complicates implementation too much
|
||||
ListModel {
|
||||
id: root
|
||||
|
||||
required property var sourceModel
|
||||
|
||||
/// Roles to clone
|
||||
required property var roles
|
||||
|
||||
/// Roles to override or add of the form { role: "roleName", transform: function(modelData) { return newValue } }
|
||||
property var rolesOverride: []
|
||||
|
||||
Component.onCompleted: cloneModel(sourceModel)
|
||||
onSourceModelChanged: cloneModel(sourceModel)
|
||||
|
||||
function rowData(index, roleName) {
|
||||
return get(index)[roleName]
|
||||
}
|
||||
|
||||
function findIndexForRole(roleName, value) {
|
||||
for (let i = 0; i < count; i++) {
|
||||
if(get(i)[roleName] === value) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
function cloneModel(model) {
|
||||
clear()
|
||||
if (!model) {
|
||||
console.warn("Missing valid data model to clone. The CloneModel is useless")
|
||||
return
|
||||
}
|
||||
|
||||
for (let i = 0; i < model.count; i++) {
|
||||
const clonedItem = new Object()
|
||||
for (var propName of roles) {
|
||||
clonedItem[propName] = model.rowData(i, propName)
|
||||
}
|
||||
for (var newProp of rolesOverride) {
|
||||
clonedItem[newProp.role] = newProp.transform(clonedItem)
|
||||
}
|
||||
append(clonedItem)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1 +1,2 @@
|
|||
RootStore 1.0 RootStore.qml
|
||||
CloneModel 1.0 CloneModel.qml
|
||||
|
|
Loading…
Reference in New Issue