2022-03-01 10:14:13 +00:00
import QtQuick 2.13
import QtQuick . Controls 2.13
2022-07-25 15:16:22 +00:00
import QtQml . Models 2.14
2023-02-20 10:57:45 +00:00
import QtQuick . Layouts 1.14
2022-03-01 10:14:13 +00:00
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
2022-07-25 15:16:22 +00:00
import StatusQ . Popups . Dialog 0.1
2023-02-20 10:57:45 +00:00
import StatusQ . Components 0.1
2022-03-01 10:14:13 +00:00
2023-02-20 10:57:45 +00:00
import SortFilterProxyModel 0.2
2023-04-05 11:10:44 +00:00
import AppLayouts . stores 1.0
2022-03-01 10:14:13 +00:00
import "../stores"
2023-04-05 11:10:44 +00:00
import "../controls"
2023-02-20 10:57:45 +00:00
import ".."
2022-03-01 10:14:13 +00:00
2022-07-25 15:16:22 +00:00
StatusDialog {
2022-03-01 10:14:13 +00:00
id: root
2023-03-08 10:57:15 +00:00
closePolicy: Popup . CloseOnEscape
2022-03-01 10:14:13 +00:00
property bool edit: false
2023-01-19 21:14:23 +00:00
property bool addAddress: false
2023-02-20 10:57:45 +00:00
property string address: Constants . zeroAddress // Setting as zero address since we don't have the address yet
property string chainShortNames
property string ens
2022-03-01 10:14:13 +00:00
property alias name: nameInput . text
2022-08-18 14:44:48 +00:00
property bool favourite: false
2022-03-01 10:14:13 +00:00
property var contactsStore
2023-02-20 10:57:45 +00:00
property var store
2022-03-01 10:14:13 +00:00
2023-02-20 10:57:45 +00:00
signal save ( string name , string address , string chainShortNames , string ens )
2022-03-01 10:14:13 +00:00
QtObject {
2022-08-08 18:21:56 +00:00
id: d
2023-02-20 10:57:45 +00:00
readonly property int validationMode: root . edit ?
2022-03-01 10:14:13 +00:00
StatusInput . ValidationMode . Always
: StatusInput . ValidationMode . OnlyWhenDirty
2023-05-14 10:21:19 +00:00
readonly property bool valid: addressInput . valid && nameInput . valid
2023-02-20 10:57:45 +00:00
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
2023-05-14 10:21:19 +00:00
readonly property bool addressInputIsENS: ! ! root . ens
2023-02-20 10:57:45 +00:00
2023-04-05 11:10:44 +00:00
/// Ensures that the \c root.address and \c root.chainShortNames are not reset when the initial text is set
property bool initialized: false
2023-02-20 10:57:45 +00:00
function getPrefixArrayWithColumns ( prefixStr ) {
return prefixStr . match ( d . chainPrefixRegexPattern )
}
function resetAddressValues ( ) {
root . ens = ""
root . address = Constants . zeroAddress
root . chainShortNames = ""
allNetworksModelCopy . setEnabledNetworks ( [ ] )
}
2022-03-01 10:14:13 +00:00
}
width: 574
height: 490
2022-07-25 15:16:22 +00:00
header: StatusDialogHeader {
headline.title: edit ? qsTr ( "Edit saved address" ) : qsTr ( "Add saved address" )
headline.subtitle: edit ? name : ""
2022-11-11 19:22:18 +00:00
actions.closeButton.onClicked: root . close ( )
2022-07-25 15:16:22 +00:00
}
2022-03-01 10:14:13 +00:00
onOpened: {
2023-04-05 11:10:44 +00:00
d . initialized = true
2023-01-19 21:14:23 +00:00
if ( edit || addAddress ) {
2023-02-20 10:57:45 +00:00
if ( root . ens )
addressInput . setPlainText ( root . ens )
else
addressInput . setPlainText ( root . chainShortNames + d . visibleAddress )
2022-03-01 10:14:13 +00:00
}
nameInput . input . edit . forceActiveFocus ( Qt . MouseFocusReason )
}
2022-07-25 15:16:22 +00:00
Column {
width: parent . width
2022-03-01 10:14:13 +00:00
height: childrenRect . height
2023-02-20 10:57:45 +00:00
topPadding: Style . current . bigPadding
2022-03-01 10:14:13 +00:00
spacing: Style . current . bigPadding
StatusInput {
id: nameInput
2022-07-25 15:16:22 +00:00
implicitWidth: parent . width
2022-08-03 10:08:46 +00:00
input.edit.objectName: "savedAddressNameInput"
2023-03-09 11:35:21 +00:00
placeholderText: qsTr ( "Address name" )
2022-03-01 10:14:13 +00:00
label: qsTr ( "Name" )
validators: [
StatusMinLengthValidator {
minLength: 1
errorMessage: qsTr ( "Name must not be blank" )
2022-03-24 15:31:38 +00:00
} ,
StatusRegularExpressionValidator {
regularExpression: /^[^<>]+$/
errorMessage: qsTr ( "This is not a valid account name" )
2022-03-01 10:14:13 +00:00
}
]
2023-02-20 10:57:45 +00:00
input.clearable: true
input.rightPadding: 16
2022-08-08 18:21:56 +00:00
validationMode: d . validationMode
2022-03-01 10:14:13 +00:00
}
2023-02-20 10:57:45 +00:00
StatusInput {
2022-07-25 15:16:22 +00:00
id: addressInput
implicitWidth: parent . width
label: qsTr ( "Address" )
2023-03-06 12:30:58 +00:00
objectName: "savedAddressAddressInput"
input.edit.objectName: "savedAddressAddressInputEdit"
placeholderText: qsTr ( "Ethereum address" )
2023-02-20 10:57:45 +00:00
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 ) {
2023-05-14 10:21:19 +00:00
return t !== Constants . zeroAddress && ( Utils . isValidAddressWithChainPrefix ( t ) || Utils . isValidEns ( t ) )
2023-02-20 10:57:45 +00:00
? 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: {
2023-04-05 11:10:44 +00:00
if ( skipTextUpdate || ! d . initialized )
2023-02-20 10:57:45 +00:00
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
2022-03-01 10:14:13 +00:00
}
}
2023-02-20 10:57:45 +00:00
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: {
2023-04-05 11:10:44 +00:00
if ( ! networkSelector . modelUpdateBlocked && d . initialized ) {
2023-02-20 10:57:45 +00:00
// 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
}
}
2023-04-05 11:10:44 +00:00
onToggleNetwork: ( network ) = > {
2023-02-20 10:57:45 +00:00
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
2022-03-01 10:14:13 +00:00
}
2022-07-25 15:16:22 +00:00
footer: StatusDialogFooter {
rightButtons: ObjectModel {
StatusButton {
text: root . edit ? qsTr ( "Save" ) : qsTr ( "Add address" )
2022-08-08 18:21:56 +00:00
enabled: d . valid && d . dirty
2023-02-20 10:57:45 +00:00
onClicked: root . save ( name , address , chainShortNames , ens )
2022-08-03 10:08:46 +00:00
objectName: "addSavedAddress"
2022-07-25 15:16:22 +00:00
}
2022-03-01 10:14:13 +00:00
}
2022-07-25 15:16:22 +00:00
}
2023-02-20 10:57:45 +00:00
2023-04-05 11:10:44 +00:00
CloneModel {
2023-02-20 10:57:45 +00:00
id: allNetworksModelCopy
2023-04-05 11:10:44 +00:00
sourceModel: store . allNetworks
roles: [ "layer" , "chainId" , "chainColor" , "chainName" , "shortName" , "iconUrl" ]
rolesOverride: [ { role: "isEnabled" , transform: ( modelData ) = > Boolean ( false ) } ]
2023-02-20 10:57:45 +00:00
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 )
}
}
2022-03-01 10:14:13 +00:00
}