2024-03-02 10:48:11 +00:00
import QtQuick 2.15
import QtQml 2.15
import QtQuick . Controls 2.15
import QtQml . Models 2.15
import QtQuick . Layouts 1.15
2022-03-01 10:14:13 +00:00
import utils 1.0
import shared . controls 1.0
import shared . panels 1.0
2024-09-25 17:02:35 +00:00
import shared . stores 1.0 as SharedStores
2022-03-01 10:14:13 +00:00
2024-06-15 21:33:12 +00:00
import StatusQ 0.1
2024-07-12 09:46:43 +00:00
import StatusQ . Components 0.1
import StatusQ . Controls 0.1
import StatusQ . Controls . Validators 0.1
2022-03-01 10:14:13 +00:00
import StatusQ . Core 0.1
2024-07-12 09:46:43 +00:00
import StatusQ . Core . Backpressure 0.1
2022-03-01 10:14:13 +00:00
import StatusQ . Core . Theme 0.1
2024-06-15 21:33:12 +00:00
import StatusQ . Core . Utils 0.1 as StatusQUtils
2024-01-10 13:31:44 +00:00
import StatusQ . Popups 0.1
2024-08-05 08:18:17 +00:00
import StatusQ . Popups . Dialog 0.1
2022-03-01 10:14:13 +00:00
2023-02-20 10:57:45 +00:00
import SortFilterProxyModel 0.2
2024-05-22 08:13:39 +00:00
import AppLayouts . Wallet . stores 1.0 as WalletStores
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
2024-01-10 13:31:44 +00:00
StatusModal {
2022-03-01 10:14:13 +00:00
id: root
2024-09-25 17:02:35 +00:00
required property WalletStores . RootStore store
required property SharedStores . RootStore sharedRootStore
2024-01-10 13:31:44 +00:00
closePolicy: Popup . CloseOnEscape | Popup . CloseOnPressOutside
2023-02-20 10:57:45 +00:00
2023-12-29 13:10:55 +00:00
width: 477
2022-03-01 10:14:13 +00:00
2024-09-25 17:02:35 +00:00
headerSettings.title: d . editMode ? qsTr ( "Edit saved address" ) : qsTr ( "Add new saved address" )
2024-01-10 13:31:44 +00:00
headerSettings.subTitle: d . editMode ? d.name : ""
2023-12-29 13:10:55 +00:00
function initWithParams ( params = { } ) {
d . storedName = params . name ? ? ""
d . storedColorId = params . colorId ? ? ""
d . editMode = params . edit ? ? false
d . addAddress = params . addAddress ? ? false
d . name = d . storedName
nameInput . input . dirty = false
d . address = params . address ? ? Constants . zeroAddress
d . ens = params . ens ? ? ""
d . colorId = d . storedColorId
d . initialized = true
if ( d . colorId === "" ) {
colorSelection . selectedColorIndex = Math . floor ( Math . random ( ) * colorSelection . model . length )
}
else {
let ind = Utils . getColorIndexForId ( d . colorId )
colorSelection . selectedColorIndex = ind
}
2024-01-26 15:28:49 +00:00
if ( d . addressInputIsENS )
2023-12-29 13:10:55 +00:00
addressInput . setPlainText ( d . ens )
else
2024-10-03 20:13:01 +00:00
addressInput . setPlainText ( "%1" . arg ( d . address == Constants . zeroAddress ? "" : d . address ) )
2023-12-29 13:10:55 +00:00
2024-05-27 21:29:31 +00:00
nameInput . input . edit . forceActiveFocus ( )
2023-12-26 10:19:41 +00:00
}
2022-03-01 10:14:13 +00:00
2024-01-26 15:28:49 +00:00
enum CardType {
Contact ,
WalletAccount ,
SavedAddress
}
2022-03-01 10:14:13 +00:00
QtObject {
2022-08-08 18:21:56 +00:00
id: d
2023-12-29 13:10:55 +00:00
2024-01-10 13:31:44 +00:00
readonly property int componentWidth: 445
2023-12-29 13:10:55 +00:00
property bool editMode: false
property bool addAddress: false
property alias name: nameInput . text
property string address: Constants . zeroAddress // Setting as zero address since we don't have the address yet
property string ens: ""
property string colorId: ""
property string storedName: ""
property string storedColorId: ""
2024-06-15 21:33:12 +00:00
2024-01-26 15:28:49 +00:00
property bool addressInputValid: d . editMode ||
addressInput . input . dirty &&
d . addressInputIsAddress &&
! d . minAddressLengthRequestError &&
! d . addressAlreadyAddedToWalletError &&
! d . addressAlreadyAddedToSavedAddressesError
readonly property bool valid: d . addressInputValid && nameInput . valid
2023-12-29 13:10:55 +00:00
readonly property bool dirty: nameInput . input . dirty && ( ! d . editMode || d . storedName !== d . name )
2024-10-03 20:13:01 +00:00
|| ! d . editMode
2023-12-29 13:10:55 +00:00
|| d . colorId . toUpperCase ( ) !== d . storedColorId . toUpperCase ( )
2024-07-30 13:00:20 +00:00
property bool incorrectChecksum: false
2023-02-20 10:57:45 +00:00
2024-01-26 15:28:49 +00:00
readonly property bool addressInputIsENS: ! ! d . ens &&
Utils . isValidEns ( d . ens )
readonly property bool addressInputIsAddress: ! ! d . address &&
d . address != Constants . zeroAddress &&
2024-10-03 20:13:01 +00:00
Utils . isAddress ( d . address )
2024-03-02 10:48:11 +00:00
readonly property bool addressInputHasError: ! ! addressInput . errorMessageCmp . text
onAddressInputHasErrorChanged: addressInput . input . valid = ! addressInputHasError // can't use binding because valid is overwritten in StatusInput
2024-01-26 15:28:49 +00:00
property ListModel cardsModel: ListModel { }
// possible errors/warnings
readonly property int minAddressLen: 1
property bool minAddressLengthRequestError: false
property bool addressAlreadyAddedToWalletError: false
property bool addressAlreadyAddedToSavedAddressesError: false
property bool checkingContactsAddressInProgress: false
property int contactsWithSameAddress: 0
2023-02-20 10:57:45 +00:00
2024-05-27 21:29:31 +00:00
function checkIfAddressIsAlreadyAddedToWallet ( address ) {
2024-03-02 10:48:11 +00:00
let account = root . store . getWalletAccount ( address )
2024-01-26 15:28:49 +00:00
d . cardsModel . clear ( )
d . addressAlreadyAddedToWalletError = ! ! account . name
if ( ! d . addressAlreadyAddedToWalletError ) {
return
}
d . cardsModel . append ( {
type: AddEditSavedAddressPopup . CardType . WalletAccount ,
address: account . mixedcaseAddress ,
title: account . name ,
icon: "" ,
emoji: account . emoji ,
color: Utils . getColorForId ( account . colorId ) . toString ( ) . toUpperCase ( )
} )
2024-01-23 08:15:07 +00:00
}
2024-05-27 21:29:31 +00:00
function checkIfAddressIsAlreadyAddedToSavedAddresses ( address ) {
2024-03-02 10:48:11 +00:00
let savedAddress = root . store . getSavedAddress ( address )
2024-01-26 15:28:49 +00:00
d . cardsModel . clear ( )
d . addressAlreadyAddedToSavedAddressesError = ! ! savedAddress . address
if ( ! d . addressAlreadyAddedToSavedAddressesError ) {
return
}
d . cardsModel . append ( {
type: AddEditSavedAddressPopup . CardType . SavedAddress ,
address: savedAddress . ens || savedAddress . address ,
title: savedAddress . name ,
icon: "" ,
emoji: "" ,
color: Utils . getColorForId ( savedAddress . colorId ) . toString ( ) . toUpperCase ( )
} )
2024-01-09 13:50:01 +00:00
}
2024-01-26 15:28:49 +00:00
property bool resolvingEnsNameInProgress: false
readonly property string uuid: Utils . uuid ( )
readonly property var validateEnsAsync: Backpressure . debounce ( root , 500 , function ( value ) {
var name = value . startsWith ( "@" ) ? value . substring ( 1 ) : value
mainModule . resolveENS ( name , d . uuid )
} ) ;
2024-09-25 17:02:35 +00:00
property var contactsModuleInst: root . sharedRootStore . profileSectionModuleInst . contactsModule
2024-01-26 15:28:49 +00:00
2024-10-03 20:13:01 +00:00
/// Ensures that the \c root.address is not reset when the initial text is set
2023-04-05 11:10:44 +00:00
property bool initialized: false
2024-01-26 15:28:49 +00:00
function resetAddressValues ( fullReset ) {
if ( fullReset ) {
d . ens = ""
d . address = Constants . zeroAddress
}
d . cardsModel . clear ( )
d . resolvingEnsNameInProgress = false
d . checkingContactsAddressInProgress = false
}
function checkForAddressInputOwningErrorsWarnings ( ) {
d . addressAlreadyAddedToWalletError = false
d . addressAlreadyAddedToSavedAddressesError = false
if ( d . addressInputIsAddress ) {
2024-05-27 21:29:31 +00:00
d . checkIfAddressIsAlreadyAddedToWallet ( d . address )
2024-01-26 15:28:49 +00:00
if ( d . addressAlreadyAddedToWalletError ) {
addressInput . errorMessageCmp . text = qsTr ( "You cannot add your own account as a saved address" )
addressInput . errorMessageCmp . visible = true
return
}
2024-05-27 21:29:31 +00:00
d . checkIfAddressIsAlreadyAddedToSavedAddresses ( d . address )
2024-01-26 15:28:49 +00:00
if ( d . addressAlreadyAddedToSavedAddressesError ) {
addressInput . errorMessageCmp . text = qsTr ( "This address is already saved" )
addressInput . errorMessageCmp . visible = true
return
}
d . checkingContactsAddressInProgress = true
d . contactsWithSameAddress = 0
2024-03-29 11:43:49 +00:00
d . contactsModuleInst . fetchProfileShowcaseAccountsByAddress ( d . address )
2024-01-26 15:28:49 +00:00
return
}
addressInput . errorMessageCmp . text = qsTr ( "Not registered ens address" )
addressInput . errorMessageCmp . visible = true
}
2024-07-30 13:00:20 +00:00
function checkIfAddressChecksumIsValid ( ) {
d . incorrectChecksum = false
if ( d . addressInputIsAddress ) {
d . incorrectChecksum = ! root . store . isChecksumValidForAddress ( d . address )
}
}
2024-01-26 15:28:49 +00:00
function checkForAddressInputErrorsWarnings ( ) {
addressInput . errorMessageCmp . visible = false
addressInput . errorMessageCmp . color = Theme . palette . dangerColor1
addressInput . errorMessageCmp . text = ""
d . minAddressLengthRequestError = false
if ( d . editMode || ! addressInput . input . dirty ) {
return
}
if ( d . addressInputIsENS || d . addressInputIsAddress ) {
let value = d . ens || d . address
if ( value . trim ( ) . length < d . minAddressLen ) {
d . minAddressLengthRequestError = true
addressInput . errorMessageCmp . text = qsTr ( "Please enter an ethereum address" )
addressInput . errorMessageCmp . visible = true
return
}
}
if ( d . addressInputIsENS ) {
d . resolvingEnsNameInProgress = true
d . validateEnsAsync ( d . ens )
return
}
if ( d . addressInputIsAddress ) {
d . checkForAddressInputOwningErrorsWarnings ( )
2024-07-30 13:00:20 +00:00
d . checkIfAddressChecksumIsValid ( )
2024-01-26 15:28:49 +00:00
return
}
addressInput . errorMessageCmp . text = qsTr ( "Ethereum address invalid" )
addressInput . errorMessageCmp . visible = true
2023-02-20 10:57:45 +00:00
}
2023-12-29 15:40:14 +00:00
function submit ( event ) {
if ( ! d . valid
2024-01-10 13:31:44 +00:00
|| ! d . dirty
|| event !== undefined && event . key !== Qt . Key_Return && event . key !== Qt . Key_Enter )
2023-12-29 15:40:14 +00:00
return
2024-08-05 08:18:17 +00:00
if ( ! d . editMode && root . store . remainingCapacityForSavedAddresses ( ) === 0 ) {
limitPopup . active = true
return
}
2024-10-03 20:13:01 +00:00
root . store . createOrUpdateSavedAddress ( d . name , d . address , d . ens , d . colorId )
2023-12-29 15:40:14 +00:00
root . close ( )
}
2024-01-26 15:28:49 +00:00
}
2024-01-09 13:50:01 +00:00
2024-01-26 15:28:49 +00:00
Connections {
target: mainModule
function onResolvedENS ( resolvedPubKey: string , resolvedAddress: string , uuid: string ) {
if ( uuid !== d . uuid ) {
return
}
d . resolvingEnsNameInProgress = false
d . address = resolvedAddress
2024-03-02 10:48:11 +00:00
try { // allows to avoid issues in storybook without much refactoring
d . checkForAddressInputOwningErrorsWarnings ( )
}
catch ( e ) {
}
2024-01-26 15:28:49 +00:00
}
}
Connections {
2024-03-29 11:43:49 +00:00
target: d . contactsModuleInst
2024-01-26 15:28:49 +00:00
function onProfileShowcaseAccountsByAddressFetched ( accounts: string ) {
d . cardsModel . clear ( )
d . checkingContactsAddressInProgress = false
try {
let accountsJson = JSON . parse ( accounts )
d . contactsWithSameAddress = accountsJson . length
addressInput . errorMessageCmp . visible = d . contactsWithSameAddress > 0
addressInput . errorMessageCmp . color = Theme . palette . warningColor1
addressInput . errorMessageCmp . text = ""
if ( d . contactsWithSameAddress === 1 )
addressInput . errorMessageCmp . text = qsTr ( "This address belongs to a contact" )
if ( d . contactsWithSameAddress > 1 )
addressInput . errorMessageCmp . text = qsTr ( "This address belongs to the following contacts" )
for ( let i = 0 ; i < accountsJson . length ; ++ i ) {
let contact = Utils . getContactDetailsAsJson ( accountsJson [ i ] . contactId , true , true , true )
d . cardsModel . append ( {
type: AddEditSavedAddressPopup . CardType . Contact ,
address: accountsJson [ i ] . address ,
title: ProfileUtils . displayName ( contact . localNickname , contact . name , contact . displayName , contact . alias ) ,
icon: contact . icon ,
emoji: "" ,
color: Utils . colorForColorId ( contact . colorId ) ,
onlineStatus: contact . onlineStatus ,
colorHash: contact . colorHash
} )
}
}
catch ( e ) {
console . warn ( "error parsing fetched accounts for contact: " , e . message )
}
}
2022-03-01 10:14:13 +00:00
}
2024-01-10 13:31:44 +00:00
StatusScrollView {
id: scrollView
anchors.fill: parent
padding: 0
contentWidth: availableWidth
Column {
2024-03-02 10:48:11 +00:00
id: column
2024-01-10 13:31:44 +00:00
width: scrollView . availableWidth
height: childrenRect . height
2024-03-02 10:48:11 +00:00
2024-01-10 13:31:44 +00:00
topPadding: 24 // (16 + 8 for Name, until we add it to the StatusInput component)
bottomPadding: 28
2024-10-15 19:26:12 +00:00
spacing: Theme . xlPadding
2024-01-10 13:31:44 +00:00
2024-08-05 08:18:17 +00:00
Loader {
id: limitPopup
active: false
asynchronous: true
sourceComponent: StatusDialog {
2024-10-15 19:26:12 +00:00
width: root . width - 2 * Theme . padding
2024-08-05 08:18:17 +00:00
title: Constants . walletConstants . maxNumberOfSavedAddressesTitle
StatusBaseText {
anchors.fill: parent
text: Constants . walletConstants . maxNumberOfSavedAddressesContent
wrapMode: Text . WordWrap
}
standardButtons: Dialog . Ok
onClosed: {
limitPopup . active = false
}
}
onLoaded: {
limitPopup . item . open ( )
}
}
2024-01-10 13:31:44 +00:00
StatusInput {
id: nameInput
implicitWidth: d . componentWidth
anchors.horizontalCenter: parent . horizontalCenter
charLimit: 24
input.edit.objectName: "savedAddressNameInput"
placeholderText: qsTr ( "Address name" )
label: qsTr ( "Name" )
validators: [
StatusMinLengthValidator {
minLength: 1
errorMessage: qsTr ( "Please name your saved address" )
} ,
StatusValidator {
2024-01-23 15:35:53 +00:00
property bool isEmoji: false
2024-01-10 13:31:44 +00:00
name: "check-for-no-emojis"
validate: ( value ) = > {
2024-01-23 15:35:53 +00:00
if ( ! value ) {
return true
}
isEmoji = Constants . regularExpressions . emoji . test ( value )
if ( isEmoji ) {
return false
}
return Constants . regularExpressions . alphanumericalExpanded1 . test ( value )
2024-01-10 13:31:44 +00:00
}
2024-01-23 15:35:53 +00:00
errorMessage: isEmoji ?
Constants . errorMessages . emojRegExp
: Constants . errorMessages . alphanumericalExpanded1RegExp
2024-01-10 13:31:44 +00:00
} ,
StatusValidator {
name: "check-saved-address-existence"
validate: ( value ) = > {
2024-03-02 10:48:11 +00:00
return ! root . store . savedAddressNameExists ( value )
2024-01-10 13:31:44 +00:00
|| d . editMode && d . storedName == value
}
errorMessage: qsTr ( "Name already in use" )
}
]
input.clearable: true
input.rightPadding: 16
2024-03-02 10:48:11 +00:00
input.tabNavItem: addressInput
2023-12-29 15:40:14 +00:00
2024-01-10 13:31:44 +00:00
onKeyPressed: {
d . submit ( event )
}
2023-12-29 15:40:14 +00:00
}
2022-03-01 10:14:13 +00:00
2024-01-10 13:31:44 +00:00
StatusInput {
id: addressInput
implicitWidth: d . componentWidth
anchors.horizontalCenter: parent . horizontalCenter
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: ! ( d . editMode || d . addAddress )
input.edit.textFormat: TextEdit . RichText
2024-03-02 10:48:11 +00:00
input.rightComponent: ( d . resolvingEnsNameInProgress || d . checkingContactsAddressInProgress ) ?
2024-07-30 13:00:20 +00:00
loadingIndicator : d . incorrectChecksum ? incorrectChecksumComponent : null
2024-01-26 15:28:49 +00:00
input.asset.name: d . addressInputValid && ! d . editMode ? "checkbox" : ""
2024-01-10 13:31:44 +00:00
input.asset.color: enabled ? Theme.palette.primaryColor1 : Theme . palette . baseColor1
2024-03-02 10:48:11 +00:00
input.asset.width: 17
input.asset.height: 17
2024-01-10 13:31:44 +00:00
input.rightPadding: 16
input.leftIcon: false
2024-03-02 10:48:11 +00:00
input.tabNavItem: nameInput
2023-02-20 10:57:45 +00:00
2024-01-10 13:31:44 +00:00
multiline: true
2023-02-20 10:57:45 +00:00
2024-01-26 15:28:49 +00:00
property string plainText: input . edit . getText ( 0 , text . length ) . trim ( )
2023-02-20 10:57:45 +00:00
2024-03-02 10:48:11 +00:00
Component {
id: loadingIndicator
StatusLoadingIndicator { }
}
2024-07-30 13:00:20 +00:00
Component {
id: incorrectChecksumComponent
StatusIconWithTooltip {
icon: "warning"
width: 20
height: 20
color: Theme . palette . warningColor1
tooltipText: qsTr ( "Checksum of the entered address is incorrect" )
}
}
2024-01-10 13:31:44 +00:00
onTextChanged: {
if ( skipTextUpdate || ! d . initialized )
return
2023-02-20 10:57:45 +00:00
2024-01-26 15:28:49 +00:00
plainText = input . edit . getText ( 0 , text . length ) . trim ( )
2023-02-20 10:57:45 +00:00
2024-01-10 13:31:44 +00:00
if ( input . edit . previousText != plainText ) {
2024-10-03 20:13:01 +00:00
setRichText ( plainText )
2023-02-20 10:57:45 +00:00
2024-01-10 13:31:44 +00:00
// Reset
2024-01-26 15:28:49 +00:00
d . resetAddressValues ( plainText . length == 0 )
if ( plainText . length > 0 ) {
// Update root values
if ( Utils . isLikelyEnsName ( plainText ) ) {
d . ens = plainText
2024-05-28 10:22:07 +00:00
d . address = Constants . zeroAddress
2024-01-26 15:28:49 +00:00
}
else {
d . ens = ""
2024-10-03 20:13:01 +00:00
d . address = plainText
2024-01-26 15:28:49 +00:00
}
2024-01-10 13:31:44 +00:00
}
2024-01-26 15:28:49 +00:00
d . checkForAddressInputErrorsWarnings ( )
2023-02-20 10:57:45 +00:00
}
}
2024-01-10 13:31:44 +00:00
onKeyPressed: {
d . submit ( event )
}
2023-02-20 10:57:45 +00:00
2024-01-10 13:31:44 +00:00
property bool skipTextUpdate: false
2023-02-20 10:57:45 +00:00
2024-01-10 13:31:44 +00:00
function setPlainText ( newText ) {
text = newText
}
2023-02-20 10:57:45 +00:00
2024-01-10 13:31:44 +00:00
function setRichText ( val ) {
skipTextUpdate = true
input . edit . previousText = plainText
const curPos = input . cursorPosition
setPlainText ( val )
input . cursorPosition = curPos
skipTextUpdate = false
}
2022-03-01 10:14:13 +00:00
}
2023-02-20 10:57:45 +00:00
2024-01-26 15:28:49 +00:00
Column {
width: scrollView . availableWidth
visible: d . cardsModel . count > 0
2024-10-15 19:26:12 +00:00
spacing: Theme . halfPadding
2024-01-26 15:28:49 +00:00
Repeater {
model: d . cardsModel
StatusListItem {
width: d . componentWidth
border.width: 1
border.color: Theme . palette . baseColor2
anchors.horizontalCenter: parent . horizontalCenter
title: model . title
subTitle: model . address
statusListItemSubTitle.font.pixelSize: 12
sensor.hoverEnabled: false
statusListItemIcon.badge.visible: model . type === AddEditSavedAddressPopup . CardType . Contact
statusListItemIcon.badge.color: model . type === AddEditSavedAddressPopup . CardType . Contact && model . onlineStatus === 1 ?
Theme . palette . successColor1
: Theme . palette . baseColor1
statusListItemIcon.hoverEnabled: false
ringSettings.ringSpecModel: model . type === AddEditSavedAddressPopup . CardType . Contact ? model.colorHash : ""
asset {
width: 40
height: 40
name: model . icon
isImage: model . icon !== ""
emoji: model . emoji
color: model . color
isLetterIdenticon: ! model . icon
2024-05-17 17:45:27 +00:00
letterIdenticonBgWithAlpha: model . type === AddEditSavedAddressPopup . CardType . SavedAddress
2024-05-17 14:34:25 +00:00
charactersLen: 2
2024-01-26 15:28:49 +00:00
}
}
}
}
2024-01-10 13:31:44 +00:00
StatusColorSelectorGrid {
id: colorSelection
objectName: "addSavedAddressColor"
width: d . componentWidth
anchors.horizontalCenter: parent . horizontalCenter
model: Theme . palette . customisationColorsArray
title.color: Theme . palette . directColor1
title.font.pixelSize: Constants . addAccountPopup . labelFontSize1
title.text: qsTr ( "Colour" )
selectedColorIndex: - 1
onSelectedColorChanged: {
d . colorId = Utils . getIdForColor ( selectedColor )
}
2023-12-29 13:10:55 +00:00
}
2024-06-15 21:33:12 +00:00
}
2022-03-01 10:14:13 +00:00
}
2024-01-10 13:31:44 +00:00
rightButtons: [
StatusButton {
text: d . editMode ? qsTr ( "Save" ) : qsTr ( "Add address" )
2024-01-26 15:28:49 +00:00
enabled: d . valid && d . dirty
2024-01-10 13:31:44 +00:00
onClicked: {
d . submit ( )
2022-07-25 15:16:22 +00:00
}
2024-01-10 13:31:44 +00:00
objectName: "addSavedAddress"
2022-03-01 10:14:13 +00:00
}
2024-01-10 13:31:44 +00:00
]
2022-03-01 10:14:13 +00:00
}