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] =
|
proc getNetworks*(self: Controller): seq[NetworkDto] =
|
||||||
return self.networkService.getNetworks()
|
return self.networkService.getNetworks()
|
||||||
|
|
||||||
proc toggleNetwork*(self: Controller, chainId: int) =
|
proc setNetworksState*(self: Controller, chainIds: seq[int], enabled: bool) =
|
||||||
self.walletAccountService.toggleNetworkEnabled(chainId)
|
self.walletAccountService.setNetworksState(chainIds, enabled)
|
||||||
|
|
||||||
proc areTestNetworksEnabled*(self: Controller): bool =
|
proc areTestNetworksEnabled*(self: Controller): bool =
|
||||||
return self.settingsService.areTestNetworksEnabled()
|
return self.settingsService.areTestNetworksEnabled()
|
||||||
|
|
|
@ -19,7 +19,7 @@ method isLoaded*(self: AccessInterface): bool {.base.} =
|
||||||
method viewDidLoad*(self: AccessInterface) {.base.} =
|
method viewDidLoad*(self: AccessInterface) {.base.} =
|
||||||
raise newException(ValueError, "No implementation available")
|
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")
|
raise newException(ValueError, "No implementation available")
|
||||||
|
|
||||||
method refreshNetworks*(self: AccessInterface) {.base.} =
|
method refreshNetworks*(self: AccessInterface) {.base.} =
|
||||||
|
|
|
@ -1,5 +1,11 @@
|
||||||
import strformat
|
import strformat
|
||||||
|
|
||||||
|
type
|
||||||
|
UxEnabledState* {.pure.} = enum
|
||||||
|
Enabled
|
||||||
|
AllEnabled
|
||||||
|
Disabled
|
||||||
|
|
||||||
type
|
type
|
||||||
Item* = object
|
Item* = object
|
||||||
chainId: int
|
chainId: int
|
||||||
|
@ -16,6 +22,7 @@ type
|
||||||
chainColor: string
|
chainColor: string
|
||||||
shortName: string
|
shortName: string
|
||||||
balance: float64
|
balance: float64
|
||||||
|
enabledState: UxEnabledState
|
||||||
|
|
||||||
proc initItem*(
|
proc initItem*(
|
||||||
chainId: int,
|
chainId: int,
|
||||||
|
@ -32,6 +39,7 @@ proc initItem*(
|
||||||
chainColor: string,
|
chainColor: string,
|
||||||
shortName: string,
|
shortName: string,
|
||||||
balance: float64,
|
balance: float64,
|
||||||
|
enabledState: UxEnabledState,
|
||||||
): Item =
|
): Item =
|
||||||
result.chainId = chainId
|
result.chainId = chainId
|
||||||
result.nativeCurrencyDecimals = nativeCurrencyDecimals
|
result.nativeCurrencyDecimals = nativeCurrencyDecimals
|
||||||
|
@ -47,6 +55,7 @@ proc initItem*(
|
||||||
result.chainColor = chainColor
|
result.chainColor = chainColor
|
||||||
result.shortName = shortName
|
result.shortName = shortName
|
||||||
result.balance = balance
|
result.balance = balance
|
||||||
|
result.enabledState = enabledState
|
||||||
|
|
||||||
proc `$`*(self: Item): string =
|
proc `$`*(self: Item): string =
|
||||||
result = fmt"""NetworkItem(
|
result = fmt"""NetworkItem(
|
||||||
|
@ -64,6 +73,7 @@ proc `$`*(self: Item): string =
|
||||||
shortName: {self.shortName},
|
shortName: {self.shortName},
|
||||||
chainColor: {self.chainColor},
|
chainColor: {self.chainColor},
|
||||||
balance: {self.balance},
|
balance: {self.balance},
|
||||||
|
enabledState: {self.enabledState}
|
||||||
]"""
|
]"""
|
||||||
|
|
||||||
proc getChainId*(self: Item): int =
|
proc getChainId*(self: Item): int =
|
||||||
|
@ -94,7 +104,7 @@ proc getIsTest*(self: Item): bool =
|
||||||
return self.isTest
|
return self.isTest
|
||||||
|
|
||||||
proc getIsEnabled*(self: Item): bool =
|
proc getIsEnabled*(self: Item): bool =
|
||||||
return self.isEnabled
|
return self.isEnabled
|
||||||
|
|
||||||
proc getIconURL*(self: Item): string =
|
proc getIconURL*(self: Item): string =
|
||||||
return self.iconUrl
|
return self.iconUrl
|
||||||
|
@ -107,3 +117,6 @@ proc getChainColor*(self: Item): string =
|
||||||
|
|
||||||
proc getBalance*(self: Item): float64 =
|
proc getBalance*(self: Item): float64 =
|
||||||
return self.balance
|
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
|
import ./item
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@ type
|
||||||
ChainColor
|
ChainColor
|
||||||
ShortName
|
ShortName
|
||||||
Balance
|
Balance
|
||||||
|
EnabledState
|
||||||
|
|
||||||
QtObject:
|
QtObject:
|
||||||
type
|
type
|
||||||
|
@ -69,6 +70,7 @@ QtObject:
|
||||||
ModelRole.ShortName.int: "shortName",
|
ModelRole.ShortName.int: "shortName",
|
||||||
ModelRole.ChainColor.int: "chainColor",
|
ModelRole.ChainColor.int: "chainColor",
|
||||||
ModelRole.Balance.int: "balance",
|
ModelRole.Balance.int: "balance",
|
||||||
|
ModelRole.EnabledState.int: "enabledState",
|
||||||
}.toTable
|
}.toTable
|
||||||
|
|
||||||
method data(self: Model, index: QModelIndex, role: int): QVariant =
|
method data(self: Model, index: QModelIndex, role: int): QVariant =
|
||||||
|
@ -110,6 +112,8 @@ QtObject:
|
||||||
result = newQVariant(item.getChainColor())
|
result = newQVariant(item.getChainColor())
|
||||||
of ModelRole.Balance:
|
of ModelRole.Balance:
|
||||||
result = newQVariant(item.getBalance())
|
result = newQVariant(item.getBalance())
|
||||||
|
of ModelRole.EnabledState:
|
||||||
|
result = newQVariant(item.getEnabledState().int)
|
||||||
|
|
||||||
proc rowData*(self: Model, index: int, column: string): string {.slot.} =
|
proc rowData*(self: Model, index: int, column: string): string {.slot.} =
|
||||||
if (index >= self.items.len):
|
if (index >= self.items.len):
|
||||||
|
@ -130,6 +134,7 @@ QtObject:
|
||||||
of "chainColor": result = $item.getChainColor()
|
of "chainColor": result = $item.getChainColor()
|
||||||
of "shortName": result = $item.getShortName()
|
of "shortName": result = $item.getShortName()
|
||||||
of "balance": result = $item.getBalance()
|
of "balance": result = $item.getBalance()
|
||||||
|
of "enabledState": result = $item.getEnabledState().int
|
||||||
|
|
||||||
proc setItems*(self: Model, items: seq[Item]) =
|
proc setItems*(self: Model, items: seq[Item]) =
|
||||||
self.beginResetModel()
|
self.beginResetModel()
|
||||||
|
@ -171,7 +176,7 @@ QtObject:
|
||||||
for item in self.items:
|
for item in self.items:
|
||||||
if cmpIgnoreCase(item.getShortName(), shortName) == 0:
|
if cmpIgnoreCase(item.getShortName(), shortName) == 0:
|
||||||
return item.getChainName()
|
return item.getChainName()
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
proc getNetworkColor*(self: Model, shortName: string): string {.slot.} =
|
proc getNetworkColor*(self: Model, shortName: string): string {.slot.} =
|
||||||
for item in self.items:
|
for item in self.items:
|
||||||
|
@ -196,3 +201,41 @@ QtObject:
|
||||||
if(item.getChainId() == chainId):
|
if(item.getChainId() == chainId):
|
||||||
return item.getBlockExplorerURL() & EXPLORER_TX_PREFIX
|
return item.getBlockExplorerURL() & EXPLORER_TX_PREFIX
|
||||||
return ""
|
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,12 +60,12 @@ proc checkIfModuleDidLoad(self: Module) =
|
||||||
method viewDidLoad*(self: Module) =
|
method viewDidLoad*(self: Module) =
|
||||||
self.checkIfModuleDidLoad()
|
self.checkIfModuleDidLoad()
|
||||||
|
|
||||||
method toggleNetwork*(self: Module, chainId: int) =
|
method setNetworksState*(self: Module, chainIds: seq[int], enabled: bool) =
|
||||||
self.controller.toggleNetwork(chainId)
|
self.controller.setNetworksState(chainIds, enabled)
|
||||||
|
|
||||||
method areTestNetworksEnabled*(self: Module): bool =
|
method areTestNetworksEnabled*(self: Module): bool =
|
||||||
return self.controller.areTestNetworksEnabled()
|
return self.controller.areTestNetworksEnabled()
|
||||||
|
|
||||||
method toggleTestNetworksEnabled*(self: Module) =
|
method toggleTestNetworksEnabled*(self: Module) =
|
||||||
self.controller.toggleTestNetworksEnabled()
|
self.controller.toggleTestNetworksEnabled()
|
||||||
self.refreshNetworks()
|
self.refreshNetworks()
|
|
@ -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 ../../../../app_service/service/network/dto
|
||||||
import ./io_interface
|
import ./io_interface
|
||||||
import ./model
|
import ./model
|
||||||
import ./networks_extra_store_proxy
|
|
||||||
import ./item
|
import ./item
|
||||||
|
|
||||||
|
proc networkEnabledToUxEnabledState(enabled: bool, allEnabled: bool): UxEnabledState
|
||||||
|
proc areAllEnabled(networks: seq[NetworkDto]): bool
|
||||||
|
|
||||||
QtObject:
|
QtObject:
|
||||||
type
|
type
|
||||||
View* = ref object of QObject
|
View* = ref object of QObject
|
||||||
|
@ -15,9 +17,6 @@ QtObject:
|
||||||
layer1: Model
|
layer1: Model
|
||||||
layer2: Model
|
layer2: Model
|
||||||
areTestNetworksEnabled: bool
|
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) =
|
proc setup(self: View) =
|
||||||
self.QObject.setup
|
self.QObject.setup
|
||||||
|
@ -32,8 +31,6 @@ QtObject:
|
||||||
result.layer1 = newModel()
|
result.layer1 = newModel()
|
||||||
result.layer2 = newModel()
|
result.layer2 = newModel()
|
||||||
result.enabled = newModel()
|
result.enabled = newModel()
|
||||||
result.layer1Proxy = nil
|
|
||||||
result.layer2Proxy = nil
|
|
||||||
result.setup()
|
result.setup()
|
||||||
|
|
||||||
proc areTestNetworksEnabledChanged*(self: View) {.signal.}
|
proc areTestNetworksEnabledChanged*(self: View) {.signal.}
|
||||||
|
@ -87,6 +84,7 @@ QtObject:
|
||||||
|
|
||||||
proc load*(self: View, networks: TableRef[NetworkDto, float64]) =
|
proc load*(self: View, networks: TableRef[NetworkDto, float64]) =
|
||||||
var items: seq[Item] = @[]
|
var items: seq[Item] = @[]
|
||||||
|
let allEnabled = areAllEnabled(toSeq(networks.keys))
|
||||||
for n, balance in networks.pairs:
|
for n, balance in networks.pairs:
|
||||||
items.add(initItem(
|
items.add(initItem(
|
||||||
n.chainId,
|
n.chainId,
|
||||||
|
@ -103,6 +101,8 @@ QtObject:
|
||||||
n.chainColor,
|
n.chainColor,
|
||||||
n.shortName,
|
n.shortName,
|
||||||
balance,
|
balance,
|
||||||
|
# Ensure we mark all as enabled if all are enabled
|
||||||
|
networkEnabledToUxEnabledState(n.enabled, allEnabled)
|
||||||
))
|
))
|
||||||
|
|
||||||
self.all.setItems(items)
|
self.all.setItems(items)
|
||||||
|
@ -118,34 +118,24 @@ QtObject:
|
||||||
self.delegate.viewDidLoad()
|
self.delegate.viewDidLoad()
|
||||||
|
|
||||||
proc toggleNetwork*(self: View, chainId: int) {.slot.} =
|
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.} =
|
proc toggleTestNetworksEnabled*(self: View) {.slot.} =
|
||||||
self.delegate.toggleTestNetworksEnabled()
|
self.delegate.toggleTestNetworksEnabled()
|
||||||
self.areTestNetworksEnabled = not self.areTestNetworksEnabled
|
self.areTestNetworksEnabled = not self.areTestNetworksEnabled
|
||||||
self.areTestNetworksEnabledChanged()
|
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.} =
|
proc getMainnetChainId*(self: View): int {.slot.} =
|
||||||
return self.layer1.getLayer1Network(self.areTestNetworksEnabled)
|
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,11 +91,15 @@ proc getNetwork*(self: Service, networkType: NetworkType): NetworkDto =
|
||||||
# Will be removed, this is used in case of legacy chain Id
|
# Will be removed, this is used in case of legacy chain Id
|
||||||
return NetworkDto(chainId: networkType.toChainId())
|
return NetworkDto(chainId: networkType.toChainId())
|
||||||
|
|
||||||
proc toggleNetwork*(self: Service, chainId: int) =
|
proc setNetworksState*(self: Service, chainIds: seq[int], enabled: bool) =
|
||||||
let network = self.getNetwork(chainId)
|
for chainId in chainIds:
|
||||||
|
let network = self.getNetwork(chainId)
|
||||||
|
|
||||||
network.enabled = not network.enabled
|
if network.enabled == enabled:
|
||||||
self.upsertNetwork(network)
|
continue
|
||||||
|
|
||||||
|
network.enabled = enabled
|
||||||
|
self.upsertNetwork(network)
|
||||||
|
|
||||||
proc getChainIdForEns*(self: Service): int =
|
proc getChainIdForEns*(self: Service): int =
|
||||||
if self.settingsService.areTestNetworksEnabled():
|
if self.settingsService.areTestNetworksEnabled():
|
||||||
|
|
|
@ -442,8 +442,8 @@ QtObject:
|
||||||
self.buildAllTokens(self.getAddresses(), store = true)
|
self.buildAllTokens(self.getAddresses(), store = true)
|
||||||
self.events.emit(SIGNAL_WALLET_ACCOUNT_CURRENCY_UPDATED, CurrencyUpdated())
|
self.events.emit(SIGNAL_WALLET_ACCOUNT_CURRENCY_UPDATED, CurrencyUpdated())
|
||||||
|
|
||||||
proc toggleNetworkEnabled*(self: Service, chainId: int) =
|
proc setNetworksState*(self: Service, chainIds: seq[int], enabled: bool) =
|
||||||
self.networkService.toggleNetwork(chainId)
|
self.networkService.setNetworksState(chainIds, enabled)
|
||||||
self.events.emit(SIGNAL_WALLET_ACCOUNT_NETWORK_ENABLED_UPDATED, NetwordkEnabledToggled())
|
self.events.emit(SIGNAL_WALLET_ACCOUNT_NETWORK_ENABLED_UPDATED, NetwordkEnabledToggled())
|
||||||
|
|
||||||
method toggleTestNetworksEnabled*(self: Service) =
|
method toggleTestNetworksEnabled*(self: Service) =
|
||||||
|
|
|
@ -95,7 +95,7 @@ target_compile_definitions(QmlTests PRIVATE
|
||||||
QML_IMPORT_ROOT="${CMAKE_CURRENT_LIST_DIR}"
|
QML_IMPORT_ROOT="${CMAKE_CURRENT_LIST_DIR}"
|
||||||
STATUSQ_MODULE_IMPORT_PATH="${STATUSQ_MODULE_IMPORT_PATH}"
|
STATUSQ_MODULE_IMPORT_PATH="${STATUSQ_MODULE_IMPORT_PATH}"
|
||||||
QUICK_TEST_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}/qmlTests")
|
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)
|
add_test(NAME QmlTests COMMAND QmlTests)
|
||||||
|
|
||||||
list(APPEND QML_DIRS "${CMAKE_SOURCE_DIR}/../ui/app")
|
list(APPEND QML_DIRS "${CMAKE_SOURCE_DIR}/../ui/app")
|
||||||
|
|
|
@ -161,6 +161,10 @@ ListModel {
|
||||||
title: "SelfDestructAlertPopup"
|
title: "SelfDestructAlertPopup"
|
||||||
section: "Popups"
|
section: "Popups"
|
||||||
}
|
}
|
||||||
|
ListElement {
|
||||||
|
title: "NetworkSelectPopup"
|
||||||
|
section: "Popups"
|
||||||
|
}
|
||||||
ListElement {
|
ListElement {
|
||||||
title: "MembersSelector"
|
title: "MembersSelector"
|
||||||
section: "Components"
|
section: "Components"
|
||||||
|
@ -237,4 +241,8 @@ ListModel {
|
||||||
title: "LanguageCurrencySettings"
|
title: "LanguageCurrencySettings"
|
||||||
section: "Settings"
|
section: "Settings"
|
||||||
}
|
}
|
||||||
|
ListElement {
|
||||||
|
title: "ProfileSocialLinksPanel"
|
||||||
|
section: "Panels"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -119,6 +119,11 @@
|
||||||
"LoginView": [
|
"LoginView": [
|
||||||
"https://www.figma.com/file/17fc13UBFvInrLgNUKJJg5/Kuba%E2%8E%9CDesktop?node-id=1080%3A313192"
|
"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": [
|
"PermissionConflictWarningPanel": [
|
||||||
"https://www.figma.com/file/17fc13UBFvInrLgNUKJJg5/Kuba%E2%8E%9CDesktop?node-id=22253%3A486103&t=JrCIfks1zVzsk3vn-0"
|
"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 {
|
QtObject {
|
||||||
|
|
||||||
readonly property var layer1Networks: ListModel {
|
readonly property var layer1Networks: ListModel {
|
||||||
|
function rowData(index, propName) {
|
||||||
|
return get(index)[propName]
|
||||||
|
}
|
||||||
|
|
||||||
Component.onCompleted:
|
Component.onCompleted:
|
||||||
append([
|
append([
|
||||||
{
|
{
|
||||||
|
@ -14,7 +18,8 @@ QtObject {
|
||||||
isActive: true,
|
isActive: true,
|
||||||
isEnabled: true,
|
isEnabled: true,
|
||||||
shortName: "ETH",
|
shortName: "ETH",
|
||||||
chainColor: "blue"
|
chainColor: "blue",
|
||||||
|
isTest: false
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
@ -29,7 +34,8 @@ QtObject {
|
||||||
isActive: false,
|
isActive: false,
|
||||||
isEnabled: true,
|
isEnabled: true,
|
||||||
shortName: "OPT",
|
shortName: "OPT",
|
||||||
chainColor: "red"
|
chainColor: "red",
|
||||||
|
isTest: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
chainId: 3,
|
chainId: 3,
|
||||||
|
@ -38,7 +44,8 @@ QtObject {
|
||||||
isActive: false,
|
isActive: false,
|
||||||
isEnabled: true,
|
isEnabled: true,
|
||||||
shortName: "ARB",
|
shortName: "ARB",
|
||||||
chainColor: "purple"
|
chainColor: "purple",
|
||||||
|
isTest: false
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
@ -53,7 +60,8 @@ QtObject {
|
||||||
isActive: false,
|
isActive: false,
|
||||||
isEnabled: true,
|
isEnabled: true,
|
||||||
shortName: "HEZ",
|
shortName: "HEZ",
|
||||||
chainColor: "orange"
|
chainColor: "orange",
|
||||||
|
isTest: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
chainId: 5,
|
chainId: 5,
|
||||||
|
@ -62,7 +70,8 @@ QtObject {
|
||||||
isActive: false,
|
isActive: false,
|
||||||
isEnabled: true,
|
isEnabled: true,
|
||||||
shortName: "TNET",
|
shortName: "TNET",
|
||||||
chainColor: "lightblue"
|
chainColor: "lightblue",
|
||||||
|
isTest: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
chainId: 6,
|
chainId: 6,
|
||||||
|
@ -71,68 +80,180 @@ QtObject {
|
||||||
isActive: false,
|
isActive: false,
|
||||||
isEnabled: true,
|
isEnabled: true,
|
||||||
shortName: "CUSTOM",
|
shortName: "CUSTOM",
|
||||||
chainColor: "orange"
|
chainColor: "orange",
|
||||||
|
isTest: true
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly property var enabledNetworks: ListModel {
|
readonly property var enabledNetworks: ListModel {
|
||||||
|
// Simulate Nim's way of providing access to data
|
||||||
|
function rowData(index, propName) {
|
||||||
|
return get(index)[propName]
|
||||||
|
}
|
||||||
|
|
||||||
Component.onCompleted:
|
Component.onCompleted:
|
||||||
append([
|
append([
|
||||||
{
|
{
|
||||||
chainId: 1,
|
chainId: 1,
|
||||||
chainName: "Ethereum Mainnet",
|
layer: 1,
|
||||||
iconUrl: ModelsData.networks.ethereum,
|
chainName: "Ethereum Mainnet",
|
||||||
isActive: true,
|
iconUrl: ModelsData.networks.ethereum,
|
||||||
isEnabled: true,
|
isActive: true,
|
||||||
shortName: "ETH",
|
isEnabled: false,
|
||||||
chainColor: "blue"
|
shortName: "ETH",
|
||||||
|
chainColor: "blue",
|
||||||
|
isTest: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
chainId: 2,
|
chainId: 2,
|
||||||
chainName: "Optimism",
|
layer: 2,
|
||||||
iconUrl: ModelsData.networks.optimism,
|
chainName: "Optimism",
|
||||||
isActive: false,
|
iconUrl: ModelsData.networks.optimism,
|
||||||
isEnabled: true,
|
isActive: false,
|
||||||
shortName: "OPT",
|
isEnabled: true,
|
||||||
chainColor: "red"
|
shortName: "OPT",
|
||||||
|
chainColor: "red",
|
||||||
|
isTest: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
chainId: 3,
|
chainId: 3,
|
||||||
chainName: "Arbitrum",
|
layer: 2,
|
||||||
iconUrl: ModelsData.networks.arbitrum,
|
chainName: "Arbitrum",
|
||||||
isActive: false,
|
iconUrl: ModelsData.networks.arbitrum,
|
||||||
isEnabled: true,
|
isActive: false,
|
||||||
shortName: "ARB",
|
isEnabled: true,
|
||||||
chainColor: "purple"
|
shortName: "ARB",
|
||||||
|
chainColor: "purple",
|
||||||
|
isTest: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
chainId: 4,
|
chainId: 4,
|
||||||
chainName: "Hermez",
|
layer: 2,
|
||||||
iconUrl: ModelsData.networks.hermez,
|
chainName: "Hermez",
|
||||||
isActive: false,
|
iconUrl: ModelsData.networks.hermez,
|
||||||
isEnabled: true,
|
isActive: false,
|
||||||
shortName: "HEZ",
|
isEnabled: true,
|
||||||
chainColor: "orange"
|
shortName: "HEZ",
|
||||||
|
chainColor: "orange",
|
||||||
|
isTest: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
chainId: 5,
|
chainId: 5,
|
||||||
chainName: "Testnet",
|
layer: 1,
|
||||||
iconUrl: ModelsData.networks.testnet,
|
chainName: "Testnet",
|
||||||
isActive: false,
|
iconUrl: ModelsData.networks.testnet,
|
||||||
isEnabled: true,
|
isActive: false,
|
||||||
shortName: "TNET",
|
isEnabled: true,
|
||||||
chainColor: "lightblue"
|
shortName: "TNET",
|
||||||
|
chainColor: "lightblue",
|
||||||
|
isTest: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
chainId: 6,
|
chainId: 6,
|
||||||
chainName: "Custom",
|
layer: 1,
|
||||||
iconUrl: ModelsData.networks.custom,
|
chainName: "Custom",
|
||||||
isActive: false,
|
iconUrl: ModelsData.networks.custom,
|
||||||
isEnabled: true,
|
isActive: false,
|
||||||
shortName: "CUSTOM",
|
isEnabled: true,
|
||||||
chainColor: "orange"
|
shortName: "CUSTOM",
|
||||||
|
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)
|
self._find_saved_address_and_open_menu(name)
|
||||||
|
|
||||||
click_obj_by_name(SavedAddressesScreen.EDIT.value)
|
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)
|
type_text(AddSavedAddressPopup.NAME_INPUT.value, new_name)
|
||||||
click_obj_by_name(AddSavedAddressPopup.ADD_BUTTON.value)
|
click_obj_by_name(AddSavedAddressPopup.ADD_BUTTON.value)
|
||||||
|
|
||||||
|
@ -467,19 +472,19 @@ class StatusWalletScreen:
|
||||||
return
|
return
|
||||||
|
|
||||||
assert False, "network name not found"
|
assert False, "network name not found"
|
||||||
|
|
||||||
def click_default_wallet_account(self):
|
def click_default_wallet_account(self):
|
||||||
accounts = get_obj(MainWalletScreen.WALLET_ACCOUNTS_LIST.value)
|
accounts = get_obj(MainWalletScreen.WALLET_ACCOUNTS_LIST.value)
|
||||||
click_obj(accounts.itemAtIndex(0))
|
click_obj(accounts.itemAtIndex(0))
|
||||||
|
|
||||||
def click_wallet_account(self, account_name: str):
|
def click_wallet_account(self, account_name: str):
|
||||||
accounts = get_obj(MainWalletScreen.WALLET_ACCOUNTS_LIST.value)
|
accounts = get_obj(MainWalletScreen.WALLET_ACCOUNTS_LIST.value)
|
||||||
for index in range(accounts.count):
|
for index in range(accounts.count):
|
||||||
if(accounts.itemAtIndex(index).objectName == "walletAccount-" + account_name):
|
if(accounts.itemAtIndex(index).objectName == "walletAccount-" + account_name):
|
||||||
click_obj(accounts.itemAtIndex(index))
|
click_obj(accounts.itemAtIndex(index))
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
#####################################
|
#####################################
|
||||||
### Verifications region:
|
### Verifications region:
|
||||||
#####################################
|
#####################################
|
||||||
|
|
|
@ -17,10 +17,10 @@ Feature: Status Desktop Wallet
|
||||||
Scenario Outline: The user can manage a saved address
|
Scenario Outline: The user can manage a saved address
|
||||||
When the user adds a saved address named "<name>" and address "<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>"
|
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>"
|
When the user deletes the saved address with name "<new_name>"
|
||||||
Then the name "<new_name><name>" is not in the list of saved addresses
|
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
|
# Test for toggling favourite button is disabled until favourite functionality is enabled
|
||||||
# When the user adds a saved address named "<name>" and address "<address>"
|
# When the user adds a saved address named "<name>" and address "<address>"
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import QtQuick 2.14
|
import QtQuick 2.15
|
||||||
import QtQuick.Controls 2.14
|
import QtQuick.Controls 2.15
|
||||||
|
|
||||||
import StatusQ.Core 0.1
|
import StatusQ.Core 0.1
|
||||||
import StatusQ.Core.Theme 0.1
|
import StatusQ.Core.Theme 0.1
|
||||||
|
@ -15,6 +15,7 @@ CheckBox {
|
||||||
- Regular (default size)
|
- Regular (default size)
|
||||||
*/
|
*/
|
||||||
property int size: StatusCheckBox.Size.Regular
|
property int size: StatusCheckBox.Size.Regular
|
||||||
|
property bool changeCursor: true
|
||||||
|
|
||||||
enum Size {
|
enum Size {
|
||||||
Small,
|
Small,
|
||||||
|
@ -49,8 +50,9 @@ CheckBox {
|
||||||
x: !root.leftSide? root.rightPadding : root.leftPadding
|
x: !root.leftSide? root.rightPadding : root.leftPadding
|
||||||
y: parent.height / 2 - height / 2
|
y: parent.height / 2 - height / 2
|
||||||
radius: 2
|
radius: 2
|
||||||
color: (root.down || root.checked) ? Theme.palette.primaryColor1
|
color: root.down || checkState !== Qt.Checked
|
||||||
: Theme.palette.directColor8
|
? Theme.palette.directColor8
|
||||||
|
: Theme.palette.primaryColor1
|
||||||
|
|
||||||
StatusIcon {
|
StatusIcon {
|
||||||
icon: "checkbox"
|
icon: "checkbox"
|
||||||
|
@ -60,8 +62,8 @@ CheckBox {
|
||||||
? d.indicatorIconHeightRegular : d.indicatorIconHeightSmall
|
? d.indicatorIconHeightRegular : d.indicatorIconHeightSmall
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
anchors.horizontalCenterOffset: 1
|
anchors.horizontalCenterOffset: 1
|
||||||
color: Theme.palette.white
|
color: checkState === Qt.PartiallyChecked ? Theme.palette.directColor9 : Theme.palette.white
|
||||||
visible: root.down || root.checked
|
visible: root.down || checkState !== Qt.Unchecked
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,4 +80,9 @@ CheckBox {
|
||||||
rightPadding: !root.leftSide? (!!root.text ? root.indicator.width + root.spacing
|
rightPadding: !root.leftSide? (!!root.text ? root.indicator.width + root.spacing
|
||||||
: root.indicator.width) : 0
|
: root.indicator.width) : 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
HoverHandler {
|
||||||
|
acceptedDevices: PointerDevice.Mouse
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -270,18 +270,20 @@ StatusScrollView {
|
||||||
|
|
||||||
NetworkFilter {
|
NetworkFilter {
|
||||||
Layout.preferredWidth: 160
|
Layout.preferredWidth: 160
|
||||||
|
|
||||||
|
allNetworks: root.allNetworks
|
||||||
layer1Networks: root.layer1Networks
|
layer1Networks: root.layer1Networks
|
||||||
layer2Networks: root.layer2Networks
|
layer2Networks: root.layer2Networks
|
||||||
testNetworks: root.testNetworks
|
testNetworks: root.testNetworks
|
||||||
enabledNetworks: root.enabledNetworks
|
enabledNetworks: root.enabledNetworks
|
||||||
allNetworks: root.allNetworks
|
|
||||||
isChainVisible: false
|
isChainVisible: false
|
||||||
multiSelection: false
|
multiSelection: false
|
||||||
|
|
||||||
onSingleNetworkSelected: {
|
onToggleNetwork: (network) => {
|
||||||
root.chainId = chainId
|
root.chainId = network.chainId
|
||||||
root.chainName = chainName
|
root.chainName = network.chainName
|
||||||
root.chainIcon = chainIcon
|
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 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;
|
/// \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
|
/// also fix code duplication in parseDerivationPath generating static level definitions and iterate through it
|
||||||
|
/// \note using Item to support embedded sub-components
|
||||||
Item {
|
Item {
|
||||||
id: root
|
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 0.1
|
||||||
import StatusQ.Core.Theme 0.1
|
import StatusQ.Core.Theme 0.1
|
||||||
|
@ -16,28 +17,44 @@ Item {
|
||||||
implicitWidth: 130
|
implicitWidth: 130
|
||||||
implicitHeight: parent.height
|
implicitHeight: parent.height
|
||||||
|
|
||||||
property var layer1Networks
|
required property var allNetworks
|
||||||
property var layer2Networks
|
required property var layer1Networks
|
||||||
property var testNetworks
|
required property var layer2Networks
|
||||||
property var enabledNetworks
|
required property var testNetworks
|
||||||
property var allNetworks
|
required property var enabledNetworks
|
||||||
|
|
||||||
property bool isChainVisible: true
|
property bool isChainVisible: true
|
||||||
property bool multiSelection: true
|
property bool multiSelection: true
|
||||||
|
|
||||||
signal toggleNetwork(int chainId)
|
/// \c network is a network.model.nim entry
|
||||||
signal singleNetworkSelected(int chainId, string chainName, string chainIcon)
|
/// 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 {
|
QtObject {
|
||||||
id: d
|
id: d
|
||||||
|
|
||||||
property string selectedChainName: ""
|
property string selectedChainName: ""
|
||||||
property string selectedIconUrl: ""
|
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 {
|
Item {
|
||||||
id: selectRectangleItem
|
id: selectRectangleItem
|
||||||
|
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: 56
|
height: 56
|
||||||
|
|
||||||
// FIXME this should be a (styled) ComboBox
|
// FIXME this should be a (styled) ComboBox
|
||||||
StatusListItem {
|
StatusListItem {
|
||||||
implicitWidth: parent.width
|
implicitWidth: parent.width
|
||||||
|
@ -52,8 +69,11 @@ Item {
|
||||||
statusListItemTitle.font.pixelSize: 13
|
statusListItemTitle.font.pixelSize: 13
|
||||||
statusListItemTitle.font.weight: Font.Medium
|
statusListItemTitle.font.weight: Font.Medium
|
||||||
statusListItemTitle.color: Theme.palette.baseColor1
|
statusListItemTitle.color: Theme.palette.baseColor1
|
||||||
title: root.multiSelection ? (root.enabledNetworks.count === root.allNetworks.count ? qsTr("All networks") : qsTr("%n network(s)", "", root.enabledNetworks.count)) :
|
title: root.multiSelection
|
||||||
d.selectedChainName
|
? (root.enabledNetworks.count === root.allNetworks.count
|
||||||
|
? qsTr("All networks")
|
||||||
|
: qsTr("%n network(s)", "", root.enabledNetworks.count))
|
||||||
|
: d.selectedChainName
|
||||||
asset.height: 24
|
asset.height: 24
|
||||||
asset.width: asset.height
|
asset.width: asset.height
|
||||||
asset.isImage: !root.multiSelection
|
asset.isImage: !root.multiSelection
|
||||||
|
@ -66,12 +86,9 @@ Item {
|
||||||
color: Theme.palette.baseColor1
|
color: Theme.palette.baseColor1
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (selectPopup.opened) {
|
selectPopupLoader.active = !selectPopupLoader.active
|
||||||
selectPopup.close();
|
|
||||||
} else {
|
|
||||||
selectPopup.open();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -94,23 +111,41 @@ Item {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
NetworkSelectPopup {
|
Loader {
|
||||||
id: selectPopup
|
id: selectPopupLoader
|
||||||
x: (parent.width - width + 5)
|
|
||||||
y: (selectRectangleItem.height + 5)
|
|
||||||
layer1Networks: root.layer1Networks
|
|
||||||
layer2Networks: root.layer2Networks
|
|
||||||
testNetworks: root.testNetworks
|
|
||||||
multiSelection: root.multiSelection
|
|
||||||
|
|
||||||
onToggleNetwork: {
|
active: false
|
||||||
root.toggleNetwork(network.chainId)
|
|
||||||
|
sourceComponent: NetworkSelectPopup {
|
||||||
|
id: selectPopup
|
||||||
|
|
||||||
|
x: -width + selectRectangleItem.width + 5
|
||||||
|
y: selectRectangleItem.height + 5
|
||||||
|
|
||||||
|
layer1Networks: root.layer1Networks
|
||||||
|
layer2Networks: root.layer2Networks
|
||||||
|
testNetworks: root.testNetworks
|
||||||
|
|
||||||
|
singleSelection {
|
||||||
|
enabled: !root.multiSelection
|
||||||
|
currentModel: d.currentModel
|
||||||
|
currentIndex: d.currentIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
onSingleNetworkSelected: {
|
onLoaded: item.open()
|
||||||
d.selectedChainName = chainName
|
|
||||||
d.selectedIconUrl = iconUrl
|
|
||||||
root.singleNetworkSelected(chainId, chainName, iconUrl)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
NetworkFilter 1.0 NetworkFilter.qml
|
|
@ -54,15 +54,19 @@ Item {
|
||||||
// network filter
|
// network filter
|
||||||
NetworkFilter {
|
NetworkFilter {
|
||||||
id: networkFilter
|
id: networkFilter
|
||||||
|
|
||||||
Layout.alignment: Qt.AlignTrailing
|
Layout.alignment: Qt.AlignTrailing
|
||||||
Layout.rowSpan: 2
|
Layout.rowSpan: 2
|
||||||
|
|
||||||
|
allNetworks: walletStore.allNetworks
|
||||||
layer1Networks: walletStore.layer1Networks
|
layer1Networks: walletStore.layer1Networks
|
||||||
layer2Networks: walletStore.layer2Networks
|
layer2Networks: walletStore.layer2Networks
|
||||||
testNetworks: walletStore.testNetworks
|
testNetworks: walletStore.testNetworks
|
||||||
enabledNetworks: walletStore.enabledNetworks
|
enabledNetworks: walletStore.enabledNetworks
|
||||||
allNetworks: walletStore.allNetworks
|
|
||||||
|
|
||||||
onToggleNetwork: walletStore.toggleNetwork(chainId)
|
onToggleNetwork: (network) => {
|
||||||
|
walletStore.toggleNetwork(network.chainId)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
StatusAddressPanel {
|
StatusAddressPanel {
|
||||||
|
|
|
@ -16,8 +16,10 @@ import StatusQ.Components 0.1
|
||||||
|
|
||||||
import SortFilterProxyModel 0.2
|
import SortFilterProxyModel 0.2
|
||||||
|
|
||||||
import "../controls"
|
import AppLayouts.stores 1.0
|
||||||
|
|
||||||
import "../stores"
|
import "../stores"
|
||||||
|
import "../controls"
|
||||||
import ".."
|
import ".."
|
||||||
|
|
||||||
StatusDialog {
|
StatusDialog {
|
||||||
|
@ -43,7 +45,7 @@ StatusDialog {
|
||||||
readonly property int validationMode: root.edit ?
|
readonly property int validationMode: root.edit ?
|
||||||
StatusInput.ValidationMode.Always
|
StatusInput.ValidationMode.Always
|
||||||
: StatusInput.ValidationMode.OnlyWhenDirty
|
: 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
|
property bool chainShortNamesDirty: false
|
||||||
readonly property bool dirty: nameInput.input.dirty || chainShortNamesDirty
|
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 string visibleAddress: root.address == Constants.zeroAddress ? "" : root.address
|
||||||
readonly property bool addressInputIsENS: !visibleAddress
|
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) {
|
function getPrefixArrayWithColumns(prefixStr) {
|
||||||
return prefixStr.match(d.chainPrefixRegexPattern)
|
return prefixStr.match(d.chainPrefixRegexPattern)
|
||||||
}
|
}
|
||||||
|
@ -73,6 +78,8 @@ StatusDialog {
|
||||||
}
|
}
|
||||||
|
|
||||||
onOpened: {
|
onOpened: {
|
||||||
|
d.initialized = true
|
||||||
|
|
||||||
if(edit || addAddress) {
|
if(edit || addAddress) {
|
||||||
if (root.ens)
|
if (root.ens)
|
||||||
addressInput.setPlainText(root.ens)
|
addressInput.setPlainText(root.ens)
|
||||||
|
@ -146,7 +153,7 @@ StatusDialog {
|
||||||
property string plainText: input.edit.getText(0, text.length)
|
property string plainText: input.edit.getText(0, text.length)
|
||||||
|
|
||||||
onTextChanged: {
|
onTextChanged: {
|
||||||
if (skipTextUpdate)
|
if (skipTextUpdate || !d.initialized)
|
||||||
return
|
return
|
||||||
|
|
||||||
plainText = input.edit.getText(0, text.length)
|
plainText = input.edit.getText(0, text.length)
|
||||||
|
@ -261,7 +268,7 @@ StatusDialog {
|
||||||
}
|
}
|
||||||
|
|
||||||
onCountChanged: {
|
onCountChanged: {
|
||||||
if (!networkSelector.modelUpdateBlocked) {
|
if (!networkSelector.modelUpdateBlocked && d.initialized) {
|
||||||
// Initially source model is empty, filter proxy is also empty, but does
|
// Initially source model is empty, filter proxy is also empty, but does
|
||||||
// extra work and mistakenly overwrites root.chainShortNames property
|
// extra work and mistakenly overwrites root.chainShortNames property
|
||||||
if (sourceModel.count != 0) {
|
if (sourceModel.count != 0) {
|
||||||
|
@ -310,7 +317,7 @@ StatusDialog {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onToggleNetwork: {
|
onToggleNetwork: (network) => {
|
||||||
network.isEnabled = !network.isEnabled
|
network.isEnabled = !network.isEnabled
|
||||||
d.chainShortNamesDirty = true
|
d.chainShortNamesDirty = true
|
||||||
}
|
}
|
||||||
|
@ -338,9 +345,13 @@ StatusDialog {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ListModel {
|
CloneModel {
|
||||||
id: allNetworksModelCopy
|
id: allNetworksModelCopy
|
||||||
|
|
||||||
|
sourceModel: store.allNetworks
|
||||||
|
roles: ["layer", "chainId", "chainColor", "chainName","shortName", "iconUrl"]
|
||||||
|
rolesOverride: [{ role: "isEnabled", transform: (modelData) => Boolean(false) }]
|
||||||
|
|
||||||
function setEnabledNetworks(prefixArr) {
|
function setEnabledNetworks(prefixArr) {
|
||||||
networkSelector.blockModelUpdate(true)
|
networkSelector.blockModelUpdate(true)
|
||||||
for (let i = 0; i < count; i++) {
|
for (let i = 0; i < count; i++) {
|
||||||
|
@ -349,26 +360,5 @@ StatusDialog {
|
||||||
}
|
}
|
||||||
networkSelector.blockModelUpdate(false)
|
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 2.15
|
||||||
import QtQuick.Controls 2.13
|
import QtQuick.Controls 2.15
|
||||||
import QtQuick.Layouts 1.3
|
import QtQuick.Layouts 1.3
|
||||||
import QtGraphicalEffects 1.0
|
import QtGraphicalEffects 1.0
|
||||||
|
|
||||||
|
@ -7,31 +7,58 @@ import StatusQ.Core 0.1
|
||||||
import StatusQ.Core.Theme 0.1
|
import StatusQ.Core.Theme 0.1
|
||||||
import StatusQ.Components 0.1
|
import StatusQ.Components 0.1
|
||||||
import StatusQ.Controls 0.1
|
import StatusQ.Controls 0.1
|
||||||
|
import StatusQ.Popups.Dialog 0.1
|
||||||
|
|
||||||
import utils 1.0
|
import utils 1.0
|
||||||
|
|
||||||
// TODO: replace with StatusModal
|
import SortFilterProxyModel 0.2
|
||||||
Popup {
|
|
||||||
|
import "./NetworkSelectPopup"
|
||||||
|
|
||||||
|
StatusDialog {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
modal: false
|
modal: false
|
||||||
|
standardButtons: Dialog.NoButton
|
||||||
|
|
||||||
|
anchors.centerIn: undefined
|
||||||
|
|
||||||
|
padding: 4
|
||||||
width: 360
|
width: 360
|
||||||
height: Math.min(432, scrollView.contentHeight + root.padding)
|
implicitHeight: Math.min(432, scrollView.contentHeight + root.padding * 2)
|
||||||
|
|
||||||
horizontalPadding: 5
|
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
|
||||||
verticalPadding: 5
|
|
||||||
|
|
||||||
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent
|
required property var layer1Networks
|
||||||
property var layer1Networks
|
required property var layer2Networks
|
||||||
property var layer2Networks
|
property var testNetworks: null
|
||||||
property var testNetworks
|
|
||||||
|
|
||||||
// If true NetworksExtraStoreProxy expected for layer1Networks and layer2Networks properties
|
/// Grouped properties for single selection state. \c singleSelection.enabled is \c false by default
|
||||||
property bool useNetworksExtraStoreProxy: false
|
/// \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 {
|
background: Rectangle {
|
||||||
radius: Style.current.radius
|
radius: Style.current.radius
|
||||||
|
@ -50,6 +77,7 @@ Popup {
|
||||||
|
|
||||||
contentItem: StatusScrollView {
|
contentItem: StatusScrollView {
|
||||||
id: scrollView
|
id: scrollView
|
||||||
|
|
||||||
width: root.width
|
width: root.width
|
||||||
height: root.height
|
height: root.height
|
||||||
contentHeight: content.height
|
contentHeight: content.height
|
||||||
|
@ -65,12 +93,16 @@ Popup {
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
id: chainRepeater1
|
id: chainRepeater1
|
||||||
|
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: parent.height
|
height: parent.height
|
||||||
|
|
||||||
objectName: "networkSelectPopupChainRepeaterLayer1"
|
objectName: "networkSelectPopupChainRepeaterLayer1"
|
||||||
model: root.layer1Networks
|
model: root.layer1Networks
|
||||||
|
|
||||||
delegate: chainItem
|
delegate: ChainItemDelegate {
|
||||||
|
networkModel: chainRepeater1.model
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
StatusBaseText {
|
StatusBaseText {
|
||||||
|
@ -87,72 +119,95 @@ Popup {
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
id: chainRepeater2
|
id: chainRepeater2
|
||||||
model: root.layer2Networks
|
|
||||||
|
|
||||||
delegate: chainItem
|
model: root.layer2Networks
|
||||||
|
delegate: ChainItemDelegate {
|
||||||
|
networkModel: chainRepeater2.model
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
id: chainRepeater3
|
id: chainRepeater3
|
||||||
model: root.testNetworks
|
model: root.testNetworks
|
||||||
|
delegate: ChainItemDelegate {
|
||||||
delegate: chainItem
|
networkModel: chainRepeater3.model
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Component {
|
component ChainItemDelegate: StatusListItem {
|
||||||
id: chainItem
|
id: chainItemDelegate
|
||||||
StatusListItem {
|
|
||||||
objectName: model.chainName
|
|
||||||
implicitHeight: 48
|
|
||||||
implicitWidth: scrollView.width
|
|
||||||
title: model.chainName
|
|
||||||
asset.height: 24
|
|
||||||
asset.width: 24
|
|
||||||
asset.isImage: true
|
|
||||||
asset.name: Style.svg(model.iconUrl)
|
|
||||||
onClicked: {
|
|
||||||
if(root.multiSelection)
|
|
||||||
toggleModelIsActive()
|
|
||||||
else {
|
|
||||||
// Don't allow uncheck
|
|
||||||
if(!radioButton.checked) radioButton.toggle()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleModelIsActive() {
|
property var networkModel: null
|
||||||
model.isActive = !model.isActive
|
|
||||||
}
|
|
||||||
|
|
||||||
components: [
|
objectName: model.chainName
|
||||||
StatusCheckBox {
|
implicitHeight: 48
|
||||||
id: checkBox
|
implicitWidth: scrollView.width
|
||||||
visible: root.multiSelection
|
title: model.chainName
|
||||||
checked: root.useNetworksExtraStoreProxy ? model.isActive : model.isEnabled
|
asset.height: 24
|
||||||
onToggled: {
|
asset.width: 24
|
||||||
if (root.useNetworksExtraStoreProxy) {
|
asset.isImage: true
|
||||||
toggleModelIsActive()
|
asset.name: Style.svg(model.iconUrl)
|
||||||
} else {
|
onClicked: {
|
||||||
root.toggleNetwork(model)
|
if(!d.singleSelection.enabled) {
|
||||||
}
|
checkBox.nextCheckState()
|
||||||
}
|
} else if(!radioButton.checked) { // Don't allow uncheck
|
||||||
},
|
radioButton.toggle()
|
||||||
StatusRadioButton {
|
}
|
||||||
id: radioButton
|
|
||||||
visible: !root.multiSelection
|
|
||||||
size: StatusRadioButton.Size.Large
|
|
||||||
ButtonGroup.group: radioBtnGroup
|
|
||||||
checked: model.index === 0
|
|
||||||
onCheckedChanged: {
|
|
||||||
if(checked && !root.multiSelection) {
|
|
||||||
root.singleNetworkSelected(model.chainId, model.chainName, model.iconUrl)
|
|
||||||
close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
components: [
|
||||||
|
StatusCheckBox {
|
||||||
|
id: checkBox
|
||||||
|
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 {
|
||||||
|
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: d.singleSelection.enabled
|
||||||
|
size: StatusRadioButton.Size.Large
|
||||||
|
ButtonGroup.group: radioBtnGroup
|
||||||
|
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 && (d.singleSelection.currentModel !== chainItemDelegate.networkModel || d.singleSelection.currentIndex !== model.index)) {
|
||||||
|
setNewInfo(chainItemDelegate.networkModel, model.index)
|
||||||
|
root.toggleNetwork(model, chainItemDelegate.networkModel, model.index)
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
ButtonGroup {
|
ButtonGroup {
|
||||||
|
|
|
@ -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.controls 1.0
|
||||||
import shared.popups 1.0
|
import shared.popups 1.0
|
||||||
|
|
||||||
|
|
||||||
|
import AppLayouts.stores 1.0
|
||||||
import "../stores"
|
import "../stores"
|
||||||
|
|
||||||
StatusModal {
|
StatusModal {
|
||||||
|
@ -42,15 +44,6 @@ StatusModal {
|
||||||
showHeader: false
|
showHeader: false
|
||||||
showAdvancedHeader: true
|
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
|
hasFloatingButtons: true
|
||||||
advancedHeaderComponent: AccountsModalHeader {
|
advancedHeaderComponent: AccountsModalHeader {
|
||||||
model: RootStore.accounts
|
model: RootStore.accounts
|
||||||
|
@ -97,7 +90,7 @@ StatusModal {
|
||||||
flow: Grid.TopToBottom
|
flow: Grid.TopToBottom
|
||||||
columns: need2Columns ? 2 : 1
|
columns: need2Columns ? 2 : 1
|
||||||
spacing: 5
|
spacing: 5
|
||||||
property var networkProxies: [RootStore.layer1NetworksProxy, RootStore.layer2NetworksProxy]
|
property var networkProxies: [layer1NetworksClone, layer2NetworksClone]
|
||||||
Repeater {
|
Repeater {
|
||||||
model: multiChainList.networkProxies.length
|
model: multiChainList.networkProxies.length
|
||||||
delegate: Repeater {
|
delegate: Repeater {
|
||||||
|
@ -106,7 +99,7 @@ StatusModal {
|
||||||
tagPrimaryLabel.text: model.shortName
|
tagPrimaryLabel.text: model.shortName
|
||||||
tagPrimaryLabel.color: model.chainColor
|
tagPrimaryLabel.color: model.chainColor
|
||||||
image.source: Style.svg("tiny/" + model.iconUrl)
|
image.source: Style.svg("tiny/" + model.iconUrl)
|
||||||
visible: model.isActive
|
visible: model.isEnabled
|
||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
@ -116,6 +109,7 @@ StatusModal {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
StatusRoundButton {
|
StatusRoundButton {
|
||||||
|
id: editButton
|
||||||
width: 32
|
width: 32
|
||||||
height: 32
|
height: 32
|
||||||
icon.name: "edit_pencil"
|
icon.name: "edit_pencil"
|
||||||
|
@ -212,7 +206,7 @@ StatusModal {
|
||||||
font.pixelSize: 15
|
font.pixelSize: 15
|
||||||
color: chainColor
|
color: chainColor
|
||||||
text: shortName + ":"
|
text: shortName + ":"
|
||||||
visible: model.isActive
|
visible: model.isEnabled
|
||||||
onVisibleChanged: {
|
onVisibleChanged: {
|
||||||
if (visible) {
|
if (visible) {
|
||||||
networkPrefix += text
|
networkPrefix += text
|
||||||
|
@ -253,15 +247,37 @@ StatusModal {
|
||||||
NetworkSelectPopup {
|
NetworkSelectPopup {
|
||||||
id: selectPopup
|
id: selectPopup
|
||||||
|
|
||||||
x: multiChainList.x + Style.current.xlPadding + Style.current.halfPadding
|
x: multiChainList.x + editButton.width + 9
|
||||||
y: centralLayout.y
|
y: tabBar.y + tabBar.height
|
||||||
|
|
||||||
layer1Networks: RootStore.layer1NetworksProxy
|
layer1Networks: layer1NetworksClone
|
||||||
layer2Networks: RootStore.layer2NetworksProxy
|
layer2Networks: layer2NetworksClone
|
||||||
testNetworks: RootStore.testNetworks
|
|
||||||
useNetworksExtraStoreProxy: true
|
|
||||||
|
|
||||||
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
|
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: [
|
states: [
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
NetworkSelectPopup 1.0 NetworkSelectPopup.qml
|
|
@ -65,8 +65,6 @@ QtObject {
|
||||||
onAllNetworksChanged: {
|
onAllNetworksChanged: {
|
||||||
d.initChainColors(allNetworks)
|
d.initChainColors(allNetworks)
|
||||||
}
|
}
|
||||||
property var layer1NetworksProxy: networksModule.layer1Proxy
|
|
||||||
property var layer2NetworksProxy: networksModule.layer2Proxy
|
|
||||||
|
|
||||||
property var cryptoRampServicesModel: walletSectionBuySellCrypto.model
|
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
|
RootStore 1.0 RootStore.qml
|
||||||
|
CloneModel 1.0 CloneModel.qml
|
||||||
|
|
Loading…
Reference in New Issue