feat(wallet): resize saved address popup to remove network selection

when ENS name is resolved. Resolve ENS name before used in SendModal.
UI tweaks:
  - red stroke on address input in case of error
  - smaller tick for validation address input
  - added validation spinner to address input, removed from the button
  - handled tab key to move focus between inputs
This commit is contained in:
Ivan Belyakov 2024-03-02 11:48:11 +01:00 committed by IvanBelyakoff
parent 5b8f37cba2
commit da226b75aa
3 changed files with 135 additions and 11 deletions

View File

@ -0,0 +1,67 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import Storybook 1.0
import Models 1.0
import AppLayouts.Wallet.popups 1.0
import utils 1.0
SplitView {
orientation: Qt.Horizontal
PopupBackground {
id: popupBg
property var popupIntance: null
SplitView.fillWidth: true
SplitView.fillHeight: true
Button {
id: reopenButton
anchors.centerIn: parent
text: "Reopen"
enabled: !dialog.visible
onClicked: dialog.open()
}
AddEditSavedAddressPopup {
id: dialog
visible: true
store: QtObject {
property var savedAddressNameExists: function() { return false }
}
// Emulate resoling ENS by simple validation
QtObject {
id: mainModule
function resolveENS(name, uuid) {
if (Utils.isValidEns(name)) {
resolvedENS("", "0x1234567890123456789012345678901234567890", uuid)
}
else {
resolvedENS("", "", uuid)
}
}
signal resolvedENS(string pubkey, string address, string uuid)
}
Component.onCompleted: initWithParams()
}
}
Pane {
SplitView.minimumWidth: 300
SplitView.preferredWidth: 300
}
}
// category: Popups
// https://www.figma.com/file/idUoxN7OIW2Jpp3PMJ1Rl8/%E2%9A%99%EF%B8%8F-Settings-%7C-Desktop?type=design&node-id=23256-263282&mode=design&t=0DRwQJKDGYJPHkq1-4

View File

@ -1,7 +1,8 @@
import QtQuick 2.13 import QtQuick 2.15
import QtQuick.Controls 2.13 import QtQml 2.15
import QtQml.Models 2.14 import QtQuick.Controls 2.15
import QtQuick.Layouts 1.14 import QtQml.Models 2.15
import QtQuick.Layouts 1.15
import utils 1.0 import utils 1.0
import shared.controls 1.0 import shared.controls 1.0
@ -35,6 +36,8 @@ StatusModal {
headerSettings.title: d.editMode? qsTr("Edit saved address") : qsTr("Add new saved address") headerSettings.title: d.editMode? qsTr("Edit saved address") : qsTr("Add new saved address")
headerSettings.subTitle: d.editMode? d.name : "" headerSettings.subTitle: d.editMode? d.name : ""
property var store: RootStore
onClosed: { onClosed: {
root.close() root.close()
} }
@ -115,6 +118,9 @@ StatusModal {
readonly property bool addressInputIsAddress: !!d.address && readonly property bool addressInputIsAddress: !!d.address &&
d.address != Constants.zeroAddress && d.address != Constants.zeroAddress &&
(Utils.isAddress(d.address) || Utils.isValidAddressWithChainPrefix(d.address)) (Utils.isAddress(d.address) || Utils.isValidAddressWithChainPrefix(d.address))
readonly property bool addressInputHasError: !!addressInput.errorMessageCmp.text
onAddressInputHasErrorChanged: addressInput.input.valid = !addressInputHasError // can't use binding because valid is overwritten in StatusInput
readonly property string networksHiddenState: "networksHidden"
property ListModel cardsModel: ListModel {} property ListModel cardsModel: ListModel {}
@ -127,7 +133,7 @@ StatusModal {
property int contactsWithSameAddress: 0 property int contactsWithSameAddress: 0
function checkIfAddressIsAlreadyAdddedToWallet(address) { function checkIfAddressIsAlreadyAdddedToWallet(address) {
let account = RootStore.getWalletAccount(address) let account = root.store.getWalletAccount(address)
d.cardsModel.clear() d.cardsModel.clear()
d.addressAlreadyAddedToWalletError = !!account.name d.addressAlreadyAddedToWalletError = !!account.name
if (!d.addressAlreadyAddedToWalletError) { if (!d.addressAlreadyAddedToWalletError) {
@ -144,7 +150,7 @@ StatusModal {
} }
function checkIfAddressIsAlreadyAdddedToSavedAddresses(address) { function checkIfAddressIsAlreadyAdddedToSavedAddresses(address) {
let savedAddress = RootStore.getSavedAddress(address) let savedAddress = root.store.getSavedAddress(address)
d.cardsModel.clear() d.cardsModel.clear()
d.addressAlreadyAddedToSavedAddressesError = !!savedAddress.address d.addressAlreadyAddedToSavedAddressesError = !!savedAddress.address
if (!d.addressAlreadyAddedToSavedAddressesError) { if (!d.addressAlreadyAddedToSavedAddressesError) {
@ -244,6 +250,7 @@ StatusModal {
return return
} }
networkSelector.state = ""
if (d.addressInputIsAddress) { if (d.addressInputIsAddress) {
d.checkForAddressInputOwningErrorsWarnings() d.checkForAddressInputOwningErrorsWarnings()
return return
@ -259,7 +266,7 @@ StatusModal {
|| event !== undefined && event.key !== Qt.Key_Return && event.key !== Qt.Key_Enter) || event !== undefined && event.key !== Qt.Key_Return && event.key !== Qt.Key_Enter)
return return
RootStore.createOrUpdateSavedAddress(d.name, d.address, d.ens, d.colorId, d.chainShortNames) root.store.createOrUpdateSavedAddress(d.name, d.address, d.ens, d.colorId, d.chainShortNames)
root.close() root.close()
} }
} }
@ -273,7 +280,16 @@ StatusModal {
d.resolvingEnsNameInProgress = false d.resolvingEnsNameInProgress = false
d.address = resolvedAddress d.address = resolvedAddress
d.checkForAddressInputOwningErrorsWarnings() try { // allows to avoid issues in storybook without much refactoring
d.checkForAddressInputOwningErrorsWarnings()
}
catch (e) {
}
if (!d.addressInputHasError)
networkSelector.state = d.networksHiddenState
else
networkSelector.state = ""
} }
} }
@ -322,8 +338,11 @@ StatusModal {
contentWidth: availableWidth contentWidth: availableWidth
Column { Column {
id: column
width: scrollView.availableWidth width: scrollView.availableWidth
height: childrenRect.height height: childrenRect.height
topPadding: 24 // (16 + 8 for Name, until we add it to the StatusInput component) topPadding: 24 // (16 + 8 for Name, until we add it to the StatusInput component)
bottomPadding: 28 bottomPadding: 28
@ -365,7 +384,7 @@ StatusModal {
StatusValidator { StatusValidator {
name: "check-saved-address-existence" name: "check-saved-address-existence"
validate: (value) => { validate: (value) => {
return !RootStore.savedAddressNameExists(value) return !root.store.savedAddressNameExists(value)
|| d.editMode && d.storedName == value || d.editMode && d.storedName == value
} }
errorMessage: qsTr("Name already in use") errorMessage: qsTr("Name already in use")
@ -373,6 +392,7 @@ StatusModal {
] ]
input.clearable: true input.clearable: true
input.rightPadding: 16 input.rightPadding: 16
input.tabNavItem: addressInput
onKeyPressed: { onKeyPressed: {
d.submit(event) d.submit(event)
@ -391,15 +411,26 @@ StatusModal {
input.implicitHeight: Math.min(Math.max(input.edit.contentHeight + topPadding + bottomPadding, minimumHeight), maximumHeight) // setting height instead does not work input.implicitHeight: Math.min(Math.max(input.edit.contentHeight + topPadding + bottomPadding, minimumHeight), maximumHeight) // setting height instead does not work
enabled: !(d.editMode || d.addAddress) enabled: !(d.editMode || d.addAddress)
input.edit.textFormat: TextEdit.RichText input.edit.textFormat: TextEdit.RichText
input.rightComponent: (d.resolvingEnsNameInProgress || d.checkingContactsAddressInProgress) ?
loadingIndicator : null
input.asset.name: d.addressInputValid && !d.editMode ? "checkbox" : "" input.asset.name: d.addressInputValid && !d.editMode ? "checkbox" : ""
input.asset.color: enabled ? Theme.palette.primaryColor1 : Theme.palette.baseColor1 input.asset.color: enabled ? Theme.palette.primaryColor1 : Theme.palette.baseColor1
input.asset.width: 17
input.asset.height: 17
input.rightPadding: 16 input.rightPadding: 16
input.leftIcon: false input.leftIcon: false
input.tabNavItem: nameInput
multiline: true multiline: true
property string plainText: input.edit.getText(0, text.length).trim() property string plainText: input.edit.getText(0, text.length).trim()
Component {
id: loadingIndicator
StatusLoadingIndicator {}
}
onTextChanged: { onTextChanged: {
if (skipTextUpdate || !d.initialized) if (skipTextUpdate || !d.initialized)
return return
@ -424,6 +455,7 @@ StatusModal {
// Update root values // Update root values
if (Utils.isLikelyEnsName(plainText)) { if (Utils.isLikelyEnsName(plainText)) {
d.ens = plainText d.ens = plainText
d.address = ""
d.chainShortNames = "" d.chainShortNames = ""
} }
else { else {
@ -560,6 +592,7 @@ StatusModal {
StatusNetworkSelector { StatusNetworkSelector {
id: networkSelector id: networkSelector
objectName: "addSavedAddressNetworkSelector" objectName: "addSavedAddressNetworkSelector"
title: "Network preference" title: "Network preference"
implicitWidth: d.componentWidth implicitWidth: d.componentWidth
@ -612,6 +645,28 @@ StatusModal {
item.modelRef.isEnabled = !item.modelRef.isEnabled item.modelRef.isEnabled = !item.modelRef.isEnabled
d.chainShortNamesDirty = true d.chainShortNamesDirty = true
} }
readonly property int animationDuration: 350
states: [
// As when networks seclector becomes invisible, spacing before it disappears as well, we see jumping height
// To overcome this, we animate bottom padding to 0 and when spacing disappears, reset bottom padding to spacing to compensate it
State {
name: d.networksHiddenState
PropertyChanges { target: networkSelector; height: 0 }
PropertyChanges { target: networkSelector; opacity: 0 }
PropertyChanges { target: column; bottomPadding: 0 }
}
]
transitions: [
Transition {
NumberAnimation { property: "height"; duration: networkSelector.animationDuration; easing.type: Easing.OutCirc }
NumberAnimation { property: "opacity"; duration: networkSelector.animationDuration; easing.type: Easing.OutCirc}
SequentialAnimation {
NumberAnimation { property: "bottomPadding"; duration: networkSelector.animationDuration; easing.type: Easing.OutCirc }
PropertyAction { target: column; property: "bottomPadding"; value: column.spacing }
}
}
]
} }
} }
} }
@ -655,7 +710,6 @@ StatusModal {
StatusButton { StatusButton {
text: d.editMode? qsTr("Save") : qsTr("Add address") text: d.editMode? qsTr("Save") : qsTr("Add address")
enabled: d.valid && d.dirty enabled: d.valid && d.dirty
loading: d.resolvingEnsNameInProgress || d.checkingContactsAddressInProgress
onClicked: { onClicked: {
d.submit() d.submit()
} }

View File

@ -44,8 +44,11 @@ Loader {
} }
case TabAddressSelectorView.Type.SavedAddress: { case TabAddressSelectorView.Type.SavedAddress: {
root.addressText = root.selectedRecipient.address root.addressText = root.selectedRecipient.address
// Resolve before using
if (!!root.selectedRecipient.ens && root.selectedRecipient.ens.length > 0) { if (!!root.selectedRecipient.ens && root.selectedRecipient.ens.length > 0) {
root.resolvedENSAddress = root.selectedRecipient.ens d.isPending = true
d.resolveENS(root.selectedRecipient.ens)
} }
preferredChainIds = store.getShortChainIds(root.selectedRecipient.chainShortNames) preferredChainIds = store.getShortChainIds(root.selectedRecipient.chainShortNames)
break break