582 lines
22 KiB
QML
582 lines
22 KiB
QML
import QtQuick 2.15
|
|
import QtQml 2.15
|
|
import QtQuick.Controls 2.15
|
|
import QtQml.Models 2.15
|
|
import QtQuick.Layouts 1.15
|
|
|
|
import utils 1.0
|
|
import shared.controls 1.0
|
|
import shared.panels 1.0
|
|
import shared.stores 1.0 as SharedStores
|
|
|
|
import StatusQ 0.1
|
|
import StatusQ.Components 0.1
|
|
import StatusQ.Controls 0.1
|
|
import StatusQ.Controls.Validators 0.1
|
|
import StatusQ.Core 0.1
|
|
import StatusQ.Core.Backpressure 0.1
|
|
import StatusQ.Core.Theme 0.1
|
|
import StatusQ.Core.Utils 0.1 as StatusQUtils
|
|
import StatusQ.Popups 0.1
|
|
import StatusQ.Popups.Dialog 0.1
|
|
|
|
import SortFilterProxyModel 0.2
|
|
|
|
import AppLayouts.Wallet.stores 1.0 as WalletStores
|
|
import "../controls"
|
|
import ".."
|
|
|
|
StatusModal {
|
|
id: root
|
|
|
|
required property WalletStores.RootStore store
|
|
required property SharedStores.RootStore sharedRootStore
|
|
|
|
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
|
|
|
|
width: 477
|
|
|
|
headerSettings.title: d.editMode? qsTr("Edit saved address") : qsTr("Add new saved address")
|
|
headerSettings.subTitle: d.editMode? d.name : ""
|
|
|
|
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
|
|
}
|
|
|
|
if (d.addressInputIsENS)
|
|
addressInput.setPlainText(d.ens)
|
|
else
|
|
addressInput.setPlainText("%1".arg(d.address == Constants.zeroAddress? "" : d.address))
|
|
|
|
nameInput.input.edit.forceActiveFocus()
|
|
}
|
|
|
|
enum CardType {
|
|
Contact,
|
|
WalletAccount,
|
|
SavedAddress
|
|
}
|
|
|
|
QtObject {
|
|
id: d
|
|
|
|
readonly property int componentWidth: 445
|
|
|
|
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: ""
|
|
|
|
property bool addressInputValid: d.editMode ||
|
|
addressInput.input.dirty &&
|
|
d.addressInputIsAddress &&
|
|
!d.minAddressLengthRequestError &&
|
|
!d.addressAlreadyAddedToWalletError &&
|
|
!d.addressAlreadyAddedToSavedAddressesError
|
|
readonly property bool valid: d.addressInputValid && nameInput.valid
|
|
readonly property bool dirty: nameInput.input.dirty && (!d.editMode || d.storedName !== d.name)
|
|
|| !d.editMode
|
|
|| d.colorId.toUpperCase() !== d.storedColorId.toUpperCase()
|
|
|
|
property bool incorrectChecksum: false
|
|
|
|
|
|
readonly property bool addressInputIsENS: !!d.ens &&
|
|
Utils.isValidEns(d.ens)
|
|
readonly property bool addressInputIsAddress: !!d.address &&
|
|
d.address != Constants.zeroAddress &&
|
|
Utils.isAddress(d.address)
|
|
readonly property bool addressInputHasError: !!addressInput.errorMessageCmp.text
|
|
onAddressInputHasErrorChanged: addressInput.input.valid = !addressInputHasError // can't use binding because valid is overwritten in StatusInput
|
|
|
|
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
|
|
|
|
function checkIfAddressIsAlreadyAddedToWallet(address) {
|
|
let account = root.store.getWalletAccount(address)
|
|
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()
|
|
})
|
|
}
|
|
|
|
function checkIfAddressIsAlreadyAddedToSavedAddresses(address) {
|
|
let savedAddress = root.store.getSavedAddress(address)
|
|
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()
|
|
})
|
|
}
|
|
|
|
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)
|
|
});
|
|
|
|
property var contactsModuleInst: root.sharedRootStore.profileSectionModuleInst.contactsModule
|
|
|
|
/// Ensures that the \c root.address is not reset when the initial text is set
|
|
property bool initialized: false
|
|
|
|
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) {
|
|
d.checkIfAddressIsAlreadyAddedToWallet(d.address)
|
|
if (d.addressAlreadyAddedToWalletError) {
|
|
addressInput.errorMessageCmp.text = qsTr("You cannot add your own account as a saved address")
|
|
addressInput.errorMessageCmp.visible = true
|
|
return
|
|
}
|
|
d.checkIfAddressIsAlreadyAddedToSavedAddresses(d.address)
|
|
if (d.addressAlreadyAddedToSavedAddressesError) {
|
|
addressInput.errorMessageCmp.text = qsTr("This address is already saved")
|
|
addressInput.errorMessageCmp.visible = true
|
|
return
|
|
}
|
|
|
|
d.checkingContactsAddressInProgress = true
|
|
d.contactsWithSameAddress = 0
|
|
d.contactsModuleInst.fetchProfileShowcaseAccountsByAddress(d.address)
|
|
return
|
|
}
|
|
|
|
addressInput.errorMessageCmp.text = qsTr("Not registered ens address")
|
|
addressInput.errorMessageCmp.visible = true
|
|
}
|
|
|
|
function checkIfAddressChecksumIsValid() {
|
|
d.incorrectChecksum = false
|
|
if (d.addressInputIsAddress) {
|
|
d.incorrectChecksum = !root.store.isChecksumValidForAddress(d.address)
|
|
}
|
|
}
|
|
|
|
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()
|
|
d.checkIfAddressChecksumIsValid()
|
|
return
|
|
}
|
|
|
|
addressInput.errorMessageCmp.text = qsTr("Ethereum address invalid")
|
|
addressInput.errorMessageCmp.visible = true
|
|
}
|
|
|
|
function submit(event) {
|
|
if (!d.valid
|
|
|| !d.dirty
|
|
|| event !== undefined && event.key !== Qt.Key_Return && event.key !== Qt.Key_Enter)
|
|
return
|
|
|
|
if (!d.editMode && root.store.remainingCapacityForSavedAddresses() === 0) {
|
|
limitPopup.active = true
|
|
return
|
|
}
|
|
|
|
root.store.createOrUpdateSavedAddress(d.name, d.address, d.ens, d.colorId)
|
|
root.close()
|
|
}
|
|
}
|
|
|
|
Connections {
|
|
target: mainModule
|
|
function onResolvedENS(resolvedPubKey: string, resolvedAddress: string, uuid: string) {
|
|
if (uuid !== d.uuid) {
|
|
return
|
|
}
|
|
|
|
d.resolvingEnsNameInProgress = false
|
|
d.address = resolvedAddress
|
|
try { // allows to avoid issues in storybook without much refactoring
|
|
d.checkForAddressInputOwningErrorsWarnings()
|
|
}
|
|
catch (e) {
|
|
}
|
|
}
|
|
}
|
|
|
|
Connections {
|
|
target: d.contactsModuleInst
|
|
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)
|
|
}
|
|
}
|
|
}
|
|
|
|
StatusScrollView {
|
|
id: scrollView
|
|
|
|
anchors.fill: parent
|
|
padding: 0
|
|
contentWidth: availableWidth
|
|
|
|
Column {
|
|
id: column
|
|
|
|
width: scrollView.availableWidth
|
|
height: childrenRect.height
|
|
|
|
topPadding: 24 // (16 + 8 for Name, until we add it to the StatusInput component)
|
|
bottomPadding: 28
|
|
|
|
spacing: Theme.xlPadding
|
|
|
|
Loader {
|
|
id: limitPopup
|
|
active: false
|
|
asynchronous: true
|
|
|
|
sourceComponent: StatusDialog {
|
|
width: root.width - 2*Theme.padding
|
|
|
|
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()
|
|
}
|
|
}
|
|
|
|
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 {
|
|
property bool isEmoji: false
|
|
|
|
name: "check-for-no-emojis"
|
|
validate: (value) => {
|
|
if (!value) {
|
|
return true
|
|
}
|
|
|
|
isEmoji = Constants.regularExpressions.emoji.test(value)
|
|
if (isEmoji){
|
|
return false
|
|
}
|
|
|
|
return Constants.regularExpressions.alphanumericalExpanded1.test(value)
|
|
}
|
|
errorMessage: isEmoji?
|
|
Constants.errorMessages.emojRegExp
|
|
: Constants.errorMessages.alphanumericalExpanded1RegExp
|
|
},
|
|
StatusValidator {
|
|
name: "check-saved-address-existence"
|
|
validate: (value) => {
|
|
return !root.store.savedAddressNameExists(value)
|
|
|| d.editMode && d.storedName == value
|
|
}
|
|
errorMessage: qsTr("Name already in use")
|
|
}
|
|
]
|
|
input.clearable: true
|
|
input.rightPadding: 16
|
|
input.tabNavItem: addressInput
|
|
|
|
onKeyPressed: {
|
|
d.submit(event)
|
|
}
|
|
}
|
|
|
|
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
|
|
input.rightComponent: (d.resolvingEnsNameInProgress || d.checkingContactsAddressInProgress) ?
|
|
loadingIndicator : d.incorrectChecksum? incorrectChecksumComponent : null
|
|
input.asset.name: d.addressInputValid && !d.editMode ? "checkbox" : ""
|
|
input.asset.color: enabled ? Theme.palette.primaryColor1 : Theme.palette.baseColor1
|
|
input.asset.width: 17
|
|
input.asset.height: 17
|
|
input.rightPadding: 16
|
|
input.leftIcon: false
|
|
input.tabNavItem: nameInput
|
|
|
|
multiline: true
|
|
|
|
property string plainText: input.edit.getText(0, text.length).trim()
|
|
|
|
Component {
|
|
id: loadingIndicator
|
|
|
|
StatusLoadingIndicator {}
|
|
}
|
|
|
|
Component {
|
|
id: incorrectChecksumComponent
|
|
|
|
StatusIconWithTooltip {
|
|
icon: "warning"
|
|
width: 20
|
|
height: 20
|
|
color: Theme.palette.warningColor1
|
|
tooltipText: qsTr("Checksum of the entered address is incorrect")
|
|
}
|
|
}
|
|
|
|
onTextChanged: {
|
|
if (skipTextUpdate || !d.initialized)
|
|
return
|
|
|
|
plainText = input.edit.getText(0, text.length).trim()
|
|
|
|
if (input.edit.previousText != plainText) {
|
|
setRichText(plainText)
|
|
|
|
// Reset
|
|
d.resetAddressValues(plainText.length == 0)
|
|
|
|
if (plainText.length > 0) {
|
|
// Update root values
|
|
if (Utils.isLikelyEnsName(plainText)) {
|
|
d.ens = plainText
|
|
d.address = Constants.zeroAddress
|
|
}
|
|
else {
|
|
d.ens = ""
|
|
d.address = plainText
|
|
}
|
|
}
|
|
|
|
d.checkForAddressInputErrorsWarnings()
|
|
}
|
|
}
|
|
|
|
onKeyPressed: {
|
|
d.submit(event)
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|
|
|
|
Column {
|
|
width: scrollView.availableWidth
|
|
visible: d.cardsModel.count > 0
|
|
|
|
spacing: Theme.halfPadding
|
|
|
|
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
|
|
letterIdenticonBgWithAlpha: model.type === AddEditSavedAddressPopup.CardType.SavedAddress
|
|
charactersLen: 2
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
rightButtons: [
|
|
StatusButton {
|
|
text: d.editMode? qsTr("Save") : qsTr("Add address")
|
|
enabled: d.valid && d.dirty
|
|
onClicked: {
|
|
d.submit()
|
|
}
|
|
objectName: "addSavedAddress"
|
|
}
|
|
]
|
|
}
|