373 lines
13 KiB
QML
373 lines
13 KiB
QML
import QtQuick 2.13
|
|
import QtQuick.Controls 2.13
|
|
import QtQml.Models 2.14
|
|
import QtQuick.Layouts 1.14
|
|
|
|
import utils 1.0
|
|
import shared.controls 1.0
|
|
import shared.panels 1.0
|
|
|
|
import StatusQ.Core 0.1
|
|
import StatusQ.Core.Theme 0.1
|
|
import StatusQ.Controls 0.1
|
|
import StatusQ.Controls.Validators 0.1
|
|
import StatusQ.Popups.Dialog 0.1
|
|
import StatusQ.Components 0.1
|
|
|
|
import SortFilterProxyModel 0.2
|
|
|
|
import "../controls"
|
|
import "../stores"
|
|
import ".."
|
|
|
|
StatusDialog {
|
|
id: root
|
|
|
|
property bool edit: false
|
|
property bool addAddress: false
|
|
property string address: Constants.zeroAddress // Setting as zero address since we don't have the address yet
|
|
property string chainShortNames
|
|
property string ens
|
|
|
|
property alias name: nameInput.text
|
|
property bool favourite: false
|
|
property var contactsStore
|
|
property var store
|
|
|
|
signal save(string name, string address, string chainShortNames, string ens)
|
|
|
|
QtObject {
|
|
id: d
|
|
readonly property int validationMode: root.edit ?
|
|
StatusInput.ValidationMode.Always
|
|
: StatusInput.ValidationMode.OnlyWhenDirty
|
|
readonly property bool valid: addressInput.valid && nameInput.valid
|
|
property bool chainShortNamesDirty: false
|
|
readonly property bool dirty: nameInput.input.dirty || chainShortNamesDirty
|
|
|
|
readonly property var chainPrefixRegexPattern: /[^:]+\:?|:/g
|
|
readonly property string visibleAddress: root.address == Constants.zeroAddress ? "" : root.address
|
|
readonly property bool addressInputIsENS: !visibleAddress
|
|
|
|
function getPrefixArrayWithColumns(prefixStr) {
|
|
return prefixStr.match(d.chainPrefixRegexPattern)
|
|
}
|
|
|
|
function resetAddressValues() {
|
|
root.ens = ""
|
|
root.address = Constants.zeroAddress
|
|
root.chainShortNames = ""
|
|
allNetworksModelCopy.setEnabledNetworks([])
|
|
}
|
|
}
|
|
|
|
width: 574
|
|
height: 490
|
|
|
|
header: StatusDialogHeader {
|
|
headline.title: edit ? qsTr("Edit saved address") : qsTr("Add saved address")
|
|
headline.subtitle: edit ? name : ""
|
|
actions.closeButton.onClicked: root.close()
|
|
}
|
|
|
|
onOpened: {
|
|
if(edit || addAddress) {
|
|
if (root.ens)
|
|
addressInput.setPlainText(root.ens)
|
|
else
|
|
addressInput.setPlainText(root.chainShortNames + d.visibleAddress)
|
|
}
|
|
nameInput.input.edit.forceActiveFocus(Qt.MouseFocusReason)
|
|
}
|
|
|
|
Column {
|
|
width: parent.width
|
|
height: childrenRect.height
|
|
topPadding: Style.current.bigPadding
|
|
|
|
spacing: Style.current.bigPadding
|
|
|
|
StatusInput {
|
|
id: nameInput
|
|
implicitWidth: parent.width
|
|
input.edit.objectName: "savedAddressNameInput"
|
|
placeholderText: qsTr("Address owner")
|
|
label: qsTr("Name")
|
|
validators: [
|
|
StatusMinLengthValidator {
|
|
minLength: 1
|
|
errorMessage: qsTr("Name must not be blank")
|
|
},
|
|
StatusRegularExpressionValidator {
|
|
regularExpression: /^[^<>]+$/
|
|
errorMessage: qsTr("This is not a valid account name")
|
|
}
|
|
]
|
|
input.clearable: true
|
|
input.rightPadding: 16
|
|
validationMode: d.validationMode
|
|
}
|
|
|
|
StatusInput {
|
|
id: addressInput
|
|
implicitWidth: parent.width
|
|
label: qsTr("Address")
|
|
objectName: "savedAddressAddressInput"
|
|
input.edit.objectName: "savedAddressAddressInputEdit"
|
|
placeholderText: qsTr("Ethereum address")
|
|
maximumHeight: 66
|
|
input.implicitHeight: Math.min(Math.max(input.edit.contentHeight + topPadding + bottomPadding, minimumHeight), maximumHeight) // setting height instead does not work
|
|
enabled: !(root.edit || root.addAddress)
|
|
validators: [
|
|
StatusMinLengthValidator {
|
|
minLength: 1
|
|
errorMessage: qsTr("Address must not be blank")
|
|
},
|
|
StatusValidator {
|
|
errorMessage: addressInput.plainText ? qsTr("Please enter a valid address or ENS name.") : ""
|
|
validate: function (t) {
|
|
return Utils.isValidAddressWithChainPrefix(t) || Utils.isValidEns(t)
|
|
? true : { actual: t }
|
|
}
|
|
}
|
|
]
|
|
validationMode: d.validationMode
|
|
|
|
input.edit.textFormat: TextEdit.RichText
|
|
input.asset.name: addressInput.valid && !root.edit ? "checkbox" : ""
|
|
input.asset.color: enabled ? Theme.palette.primaryColor1 : Theme.palette.baseColor1
|
|
input.rightPadding: 16
|
|
input.leftIcon: false
|
|
|
|
multiline: true
|
|
|
|
property string plainText: input.edit.getText(0, text.length)
|
|
|
|
onTextChanged: {
|
|
if (skipTextUpdate)
|
|
return
|
|
|
|
plainText = input.edit.getText(0, text.length)
|
|
|
|
if (input.edit.previousText != plainText) {
|
|
let newText = plainText
|
|
const prefixAndAddress = Utils.splitToChainPrefixAndAddress(plainText)
|
|
|
|
if (!Utils.isLikelyEnsName(plainText)) {
|
|
newText = WalletUtils.colorizedChainPrefix(prefixAndAddress.prefix) +
|
|
prefixAndAddress.address
|
|
}
|
|
|
|
setRichText(newText)
|
|
|
|
// Reset
|
|
if (plainText.length == 0) {
|
|
d.resetAddressValues()
|
|
return
|
|
}
|
|
|
|
// Update root values
|
|
if (Utils.isLikelyEnsName(plainText)) {
|
|
root.ens = plainText
|
|
root.address = Constants.zeroAddress
|
|
root.chainShortNames = ""
|
|
}
|
|
else {
|
|
root.ens = ""
|
|
root.address = prefixAndAddress.address
|
|
root.chainShortNames = prefixAndAddress.prefix
|
|
|
|
let prefixArrWithColumn = d.getPrefixArrayWithColumns(prefixAndAddress.prefix)
|
|
if (!prefixArrWithColumn)
|
|
prefixArrWithColumn = []
|
|
|
|
allNetworksModelCopy.setEnabledNetworks(prefixArrWithColumn)
|
|
}
|
|
}
|
|
}
|
|
|
|
property bool skipTextUpdate: false
|
|
|
|
function setPlainText(newText) {
|
|
text = newText
|
|
}
|
|
|
|
function setRichText(val) {
|
|
skipTextUpdate = true
|
|
input.edit.previousText = plainText
|
|
const curPos = input.cursorPosition
|
|
setPlainText(val)
|
|
input.cursorPosition = curPos
|
|
skipTextUpdate = false
|
|
}
|
|
|
|
function getUnknownPrefixes(prefixes) {
|
|
let unknownPrefixes = prefixes.filter(e => {
|
|
for (let i = 0; i < allNetworksModelCopy.count; i++) {
|
|
if (e == allNetworksModelCopy.get(i).shortName)
|
|
return false
|
|
}
|
|
return true
|
|
})
|
|
|
|
return unknownPrefixes
|
|
}
|
|
|
|
// Add all chain short names from model, while keeping existing
|
|
function syncChainPrefixWithModel(prefix, model) {
|
|
let prefixes = prefix.split(":").filter(Boolean)
|
|
let prefixStr = ""
|
|
|
|
// Keep unknown prefixes from user input, the rest must be taken
|
|
// from the model
|
|
for (let i = 0; i < model.count; i++) {
|
|
const item = model.get(i)
|
|
prefixStr += item.shortName + ":"
|
|
// Remove all added prefixes from initial array
|
|
prefixes = prefixes.filter(e => e !== item.shortName)
|
|
}
|
|
|
|
const unknownPrefixes = getUnknownPrefixes(prefixes)
|
|
if (unknownPrefixes.length > 0) {
|
|
prefixStr += unknownPrefixes.join(":") + ":"
|
|
}
|
|
|
|
return prefixStr
|
|
}
|
|
}
|
|
|
|
StatusNetworkSelector {
|
|
id: networkSelector
|
|
|
|
title: "Network preference"
|
|
enabled: addressInput.valid && !d.addressInputIsENS
|
|
defaultItemText: "Add networks"
|
|
defaultItemImageSource: "add"
|
|
rightButtonVisible: true
|
|
|
|
property bool modelUpdateBlocked: false
|
|
|
|
function blockModelUpdate(value) {
|
|
modelUpdateBlocked = value
|
|
}
|
|
|
|
itemsModel: SortFilterProxyModel {
|
|
sourceModel: allNetworksModelCopy
|
|
filters: ValueFilter {
|
|
roleName: "isEnabled"
|
|
value: true
|
|
}
|
|
|
|
onCountChanged: {
|
|
if (!networkSelector.modelUpdateBlocked) {
|
|
// Initially source model is empty, filter proxy is also empty, but does
|
|
// extra work and mistakenly overwrites root.chainShortNames property
|
|
if (sourceModel.count != 0) {
|
|
const prefixAndAddress = Utils.splitToChainPrefixAndAddress(addressInput.plainText)
|
|
const syncedPrefix = addressInput.syncChainPrefixWithModel(prefixAndAddress.prefix, this)
|
|
root.chainShortNames = syncedPrefix
|
|
addressInput.setPlainText(syncedPrefix + prefixAndAddress.address)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
addButton.highlighted: networkSelectPopup.visible
|
|
addButton.onClicked: {
|
|
networkSelectPopup.openAtPosition(addButton.x, networkSelector.y + addButton.height + Style.current.xlPadding)
|
|
}
|
|
|
|
onItemClicked: function (item, index, mouse) {
|
|
// Append first item
|
|
if (index === 0 && defaultItem.visible)
|
|
networkSelectPopup.openAtPosition(defaultItem.x, networkSelector.y + defaultItem.height + Style.current.xlPadding)
|
|
}
|
|
|
|
onItemRightButtonClicked: function (item, index, mouse) {
|
|
item.modelRef.isEnabled = !item.modelRef.isEnabled
|
|
d.chainShortNamesDirty = true
|
|
}
|
|
}
|
|
}
|
|
|
|
NetworkSelectPopup {
|
|
id: networkSelectPopup
|
|
|
|
layer1Networks: SortFilterProxyModel {
|
|
sourceModel: allNetworksModelCopy
|
|
filters: ValueFilter {
|
|
roleName: "layer"
|
|
value: 1
|
|
}
|
|
}
|
|
layer2Networks: SortFilterProxyModel {
|
|
sourceModel: allNetworksModelCopy
|
|
filters: ValueFilter {
|
|
roleName: "layer"
|
|
value: 2
|
|
}
|
|
}
|
|
|
|
onToggleNetwork: {
|
|
network.isEnabled = !network.isEnabled
|
|
d.chainShortNamesDirty = true
|
|
}
|
|
|
|
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
|
|
|
|
function openAtPosition(xPos, yPos) {
|
|
x = xPos
|
|
y = yPos
|
|
open()
|
|
}
|
|
|
|
modal: true
|
|
dim: false
|
|
}
|
|
|
|
footer: StatusDialogFooter {
|
|
rightButtons: ObjectModel {
|
|
StatusButton {
|
|
text: root.edit ? qsTr("Save") : qsTr("Add address")
|
|
enabled: d.valid && d.dirty
|
|
onClicked: root.save(name, address, chainShortNames, ens)
|
|
objectName: "addSavedAddress"
|
|
}
|
|
}
|
|
}
|
|
|
|
ListModel {
|
|
id: allNetworksModelCopy
|
|
|
|
function setEnabledNetworks(prefixArr) {
|
|
networkSelector.blockModelUpdate(true)
|
|
for (let i = 0; i < count; i++) {
|
|
// Add only those chainShortNames to the model, that have column ":" at the end, making it a valid chain prefix
|
|
setProperty(i, "isEnabled", prefixArr.includes(get(i).shortName + ":"))
|
|
}
|
|
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)
|
|
}
|
|
}
|