diff --git a/storybook/pages/SavedAddressPopupPage.qml b/storybook/pages/SavedAddressPopupPage.qml new file mode 100644 index 00000000000..cb8a9f37c87 --- /dev/null +++ b/storybook/pages/SavedAddressPopupPage.qml @@ -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 diff --git a/ui/app/AppLayouts/Wallet/popups/AddEditSavedAddressPopup.qml b/ui/app/AppLayouts/Wallet/popups/AddEditSavedAddressPopup.qml index 64d3dfa8d8c..a88eb59fea3 100644 --- a/ui/app/AppLayouts/Wallet/popups/AddEditSavedAddressPopup.qml +++ b/ui/app/AppLayouts/Wallet/popups/AddEditSavedAddressPopup.qml @@ -1,7 +1,8 @@ -import QtQuick 2.13 -import QtQuick.Controls 2.13 -import QtQml.Models 2.14 -import QtQuick.Layouts 1.14 +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 @@ -35,6 +36,8 @@ StatusModal { headerSettings.title: d.editMode? qsTr("Edit saved address") : qsTr("Add new saved address") headerSettings.subTitle: d.editMode? d.name : "" + property var store: RootStore + onClosed: { root.close() } @@ -115,6 +118,9 @@ StatusModal { readonly property bool addressInputIsAddress: !!d.address && d.address != Constants.zeroAddress && (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 {} @@ -127,7 +133,7 @@ StatusModal { property int contactsWithSameAddress: 0 function checkIfAddressIsAlreadyAdddedToWallet(address) { - let account = RootStore.getWalletAccount(address) + let account = root.store.getWalletAccount(address) d.cardsModel.clear() d.addressAlreadyAddedToWalletError = !!account.name if (!d.addressAlreadyAddedToWalletError) { @@ -144,7 +150,7 @@ StatusModal { } function checkIfAddressIsAlreadyAdddedToSavedAddresses(address) { - let savedAddress = RootStore.getSavedAddress(address) + let savedAddress = root.store.getSavedAddress(address) d.cardsModel.clear() d.addressAlreadyAddedToSavedAddressesError = !!savedAddress.address if (!d.addressAlreadyAddedToSavedAddressesError) { @@ -244,6 +250,7 @@ StatusModal { return } + networkSelector.state = "" if (d.addressInputIsAddress) { d.checkForAddressInputOwningErrorsWarnings() return @@ -259,7 +266,7 @@ StatusModal { || event !== undefined && event.key !== Qt.Key_Return && event.key !== Qt.Key_Enter) 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() } } @@ -273,7 +280,16 @@ StatusModal { d.resolvingEnsNameInProgress = false 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 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 @@ -365,7 +384,7 @@ StatusModal { StatusValidator { name: "check-saved-address-existence" validate: (value) => { - return !RootStore.savedAddressNameExists(value) + return !root.store.savedAddressNameExists(value) || d.editMode && d.storedName == value } errorMessage: qsTr("Name already in use") @@ -373,6 +392,7 @@ StatusModal { ] input.clearable: true input.rightPadding: 16 + input.tabNavItem: addressInput onKeyPressed: { 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 enabled: !(d.editMode || d.addAddress) input.edit.textFormat: TextEdit.RichText + input.rightComponent: (d.resolvingEnsNameInProgress || d.checkingContactsAddressInProgress) ? + loadingIndicator : 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 {} + } + onTextChanged: { if (skipTextUpdate || !d.initialized) return @@ -424,6 +455,7 @@ StatusModal { // Update root values if (Utils.isLikelyEnsName(plainText)) { d.ens = plainText + d.address = "" d.chainShortNames = "" } else { @@ -560,6 +592,7 @@ StatusModal { StatusNetworkSelector { id: networkSelector + objectName: "addSavedAddressNetworkSelector" title: "Network preference" implicitWidth: d.componentWidth @@ -612,6 +645,28 @@ StatusModal { item.modelRef.isEnabled = !item.modelRef.isEnabled 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 { text: d.editMode? qsTr("Save") : qsTr("Add address") enabled: d.valid && d.dirty - loading: d.resolvingEnsNameInProgress || d.checkingContactsAddressInProgress onClicked: { d.submit() } diff --git a/ui/imports/shared/popups/send/views/RecipientView.qml b/ui/imports/shared/popups/send/views/RecipientView.qml index 8c64cad1b9e..b9123e7b384 100644 --- a/ui/imports/shared/popups/send/views/RecipientView.qml +++ b/ui/imports/shared/popups/send/views/RecipientView.qml @@ -44,8 +44,11 @@ Loader { } case TabAddressSelectorView.Type.SavedAddress: { root.addressText = root.selectedRecipient.address + + // Resolve before using 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) break