diff --git a/src/app/boot/app_controller.nim b/src/app/boot/app_controller.nim index bbc67aaa28..991cee4b10 100644 --- a/src/app/boot/app_controller.nim +++ b/src/app/boot/app_controller.nim @@ -215,7 +215,8 @@ proc newAppController*(statusFoundation: StatusFoundation): AppController = # result.mnemonicService = mnemonic_service.newService() result.privacyService = privacy_service.newService(statusFoundation.events, result.settingsService, result.accountsService) - result.savedAddressService = saved_address_service.newService(statusFoundation.events, result.networkService, result.settingsService) + result.savedAddressService = saved_address_service.newService(statusFoundation.threadpool, statusFoundation.events, + result.networkService, result.settingsService) result.devicesService = devices_service.newService(statusFoundation.events, statusFoundation.threadpool, result.settingsService, result.accountsService, result.walletAccountService) result.mailserversService = mailservers_service.newService(statusFoundation.events, statusFoundation.threadpool, diff --git a/src/app/modules/main/wallet_section/saved_addresses/controller.nim b/src/app/modules/main/wallet_section/saved_addresses/controller.nim index 98d0ae420c..f21a42e103 100644 --- a/src/app/modules/main/wallet_section/saved_addresses/controller.nim +++ b/src/app/modules/main/wallet_section/saved_addresses/controller.nim @@ -1,6 +1,6 @@ import io_interface -import ../../../../core/eventemitter -import ../../../../../app_service/service/saved_address/service as saved_address_service +import app/core/eventemitter +import app_service/service/saved_address/service as saved_address_service type Controller* = ref object of RootObj @@ -22,14 +22,22 @@ proc delete*(self: Controller) = discard proc init*(self: Controller) = - self.events.on(SIGNAL_SAVED_ADDRESS_CHANGED) do(e:Args): + self.events.on(SIGNAL_SAVED_ADDRESSES_UPDATED) do(e:Args): self.delegate.loadSavedAddresses() + self.events.on(SIGNAL_SAVED_ADDRESS_UPDATED) do(e:Args): + let args = SavedAddressArgs(e) + self.delegate.savedAddressUpdated(args.address, args.ens, args.errorMsg) + + self.events.on(SIGNAL_SAVED_ADDRESS_DELETED) do(e:Args): + let args = SavedAddressArgs(e) + self.delegate.savedAddressDeleted(args.address, args.ens, args.errorMsg) + proc getSavedAddresses*(self: Controller): seq[saved_address_service.SavedAddressDto] = return self.savedAddressService.getSavedAddresses() -proc createOrUpdateSavedAddress*(self: Controller, name: string, address: string, favourite: bool, chainShortNames: string, ens: string): string = - return self.savedAddressService.createOrUpdateSavedAddress(name, address, favourite, chainShortNames, ens) +proc createOrUpdateSavedAddress*(self: Controller, name: string, address: string, favourite: bool, chainShortNames: string, ens: string) = + self.savedAddressService.createOrUpdateSavedAddress(name, address, favourite, chainShortNames, ens) -proc deleteSavedAddress*(self: Controller, address: string, ens: string): string = - return self.savedAddressService.deleteSavedAddress(address, ens) +proc deleteSavedAddress*(self: Controller, address: string, ens: string) = + self.savedAddressService.deleteSavedAddress(address, ens) diff --git a/src/app/modules/main/wallet_section/saved_addresses/io_interface.nim b/src/app/modules/main/wallet_section/saved_addresses/io_interface.nim index 549776df2c..30fa7ad3ec 100644 --- a/src/app/modules/main/wallet_section/saved_addresses/io_interface.nim +++ b/src/app/modules/main/wallet_section/saved_addresses/io_interface.nim @@ -17,10 +17,16 @@ method viewDidLoad*(self: AccessInterface) {.base.} = method loadSavedAddresses*(self: AccessInterface) {.base.} = raise newException(ValueError, "No implementation available") -method createOrUpdateSavedAddress*(self: AccessInterface, name: string, address: string, favourite: bool, chainShortNames: string, ens: string): string {.base.} = +method createOrUpdateSavedAddress*(self: AccessInterface, name: string, address: string, favourite: bool, chainShortNames: string, ens: string) {.base.} = raise newException(ValueError, "No implementation available") -method deleteSavedAddress*(self: AccessInterface, address: string, ens: string): string {.base.} = +method deleteSavedAddress*(self: AccessInterface, address: string, ens: string) {.base.} = + raise newException(ValueError, "No implementation available") + +method savedAddressUpdated*(self: AccessInterface, address: string, ens: string, errorMsg: string) {.base.} = + raise newException(ValueError, "No implementation available") + +method savedAddressDeleted*(self: AccessInterface, address: string, ens: string, errorMsg: string) {.base.} = raise newException(ValueError, "No implementation available") type diff --git a/src/app/modules/main/wallet_section/saved_addresses/module.nim b/src/app/modules/main/wallet_section/saved_addresses/module.nim index 614a244fd3..ed45e147c7 100644 --- a/src/app/modules/main/wallet_section/saved_addresses/module.nim +++ b/src/app/modules/main/wallet_section/saved_addresses/module.nim @@ -1,9 +1,9 @@ import NimQml, sugar, sequtils import ../io_interface as delegate_interface -import ../../../../global/global_singleton -import ../../../../core/eventemitter -import ../../../../../app_service/service/saved_address/service as saved_address_service +import app/global/global_singleton +import app/core/eventemitter +import app_service/service/saved_address/service as saved_address_service import ./io_interface, ./view, ./controller, ./item @@ -57,8 +57,16 @@ method viewDidLoad*(self: Module) = self.moduleLoaded = true self.delegate.savedAddressesModuleDidLoad() -method createOrUpdateSavedAddress*(self: Module, name: string, address: string, favourite: bool, chainShortNames: string, ens: string): string = - return self.controller.createOrUpdateSavedAddress(name, address, favourite, chainShortNames, ens) +method createOrUpdateSavedAddress*(self: Module, name: string, address: string, favourite: bool, chainShortNames: string, ens: string) = + self.controller.createOrUpdateSavedAddress(name, address, favourite, chainShortNames, ens) -method deleteSavedAddress*(self: Module, address: string, ens: string): string = - return self.controller.deleteSavedAddress(address, ens) +method deleteSavedAddress*(self: Module, address: string, ens: string) = + self.controller.deleteSavedAddress(address, ens) + +method savedAddressUpdated*(self: Module, address: string, ens: string, errorMsg: string) = + self.loadSavedAddresses() + self.view.savedAddressUpdated(address, ens, errorMsg) + +method savedAddressDeleted*(self: Module, address: string, ens: string, errorMsg: string) = + self.loadSavedAddresses() + self.view.savedAddressDeleted(address, ens, errorMsg) \ No newline at end of file diff --git a/src/app/modules/main/wallet_section/saved_addresses/view.nim b/src/app/modules/main/wallet_section/saved_addresses/view.nim index 34783ec456..e34472fe5e 100644 --- a/src/app/modules/main/wallet_section/saved_addresses/view.nim +++ b/src/app/modules/main/wallet_section/saved_addresses/view.nim @@ -37,11 +37,15 @@ QtObject: proc setItems*(self: View, items: seq[Item]) = self.model.setItems(items) - proc createOrUpdateSavedAddress*(self: View, name: string, address: string, favourite: bool, chainShortNames: string, ens: string): string {.slot.} = - return self.delegate.createOrUpdateSavedAddress(name, address, favourite, chainShortNames, ens) + proc savedAddressUpdated*(self: View, address: string, ens: string, errorMsg: string) {.signal.} - proc deleteSavedAddress*(self: View, address: string, ens: string): string {.slot.} = - return self.delegate.deleteSavedAddress(address, ens) + proc createOrUpdateSavedAddress*(self: View, name: string, address: string, favourite: bool, chainShortNames: string, ens: string) {.slot.} = + self.delegate.createOrUpdateSavedAddress(name, address, favourite, chainShortNames, ens) + + proc savedAddressDeleted*(self: View, address: string, ens: string, errorMsg: string) {.signal.} + + proc deleteSavedAddress*(self: View, address: string, ens: string) {.slot.} = + self.delegate.deleteSavedAddress(address, ens) proc getNameByAddress*(self: View, address: string): string {.slot.} = return self.model.getNameByAddress(address) diff --git a/src/app_service/service/saved_address/async_tasks.nim b/src/app_service/service/saved_address/async_tasks.nim new file mode 100644 index 0000000000..804431dbf5 --- /dev/null +++ b/src/app_service/service/saved_address/async_tasks.nim @@ -0,0 +1,54 @@ +include app_service/common/json_utils +include app/core/tasks/common + +import backend/backend + +type + SavedAddressTaskArg = ref object of QObjectTaskArg + name: string + address: string + favourite: bool + chainShortNames: string + ens: string + isTestAddress: bool + +const upsertSavedAddressTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} = + let arg = decode[SavedAddressTaskArg](argEncoded) + var response = %* { + "response": "", + "address": %* arg.address, + "ens": %* arg.ens, + "error": "", + } + try: + let rpcResponse = backend.upsertSavedAddress(backend.SavedAddress( + name: arg.name, + address: arg.address, + favourite: arg.favourite, + chainShortNames: arg.chainShortNames, + ens: arg.ens, + isTest: arg.isTestAddress) + ) + if not rpcResponse.error.isNil: + raise newException(CatchableError, rpcResponse.error.message) + response["response"] = %* "ok" + except Exception as e: + response["error"] = %* e.msg + arg.finish(response) + +const deleteSavedAddressTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} = + let arg = decode[SavedAddressTaskArg](argEncoded) + var response = %* { + "response": "", + "address": %* arg.address, + "ens": %* arg.ens, + "error": "", + } + try: + let rpcResponse = backend.deleteSavedAddress(arg.address, arg.ens, arg.isTestAddress) + if not rpcResponse.error.isNil: + raise newException(CatchableError, rpcResponse.error.message) + response["response"] = %* "ok" + except Exception as e: + response["error"] = %* e.msg + arg.finish(response) diff --git a/src/app_service/service/saved_address/service.nim b/src/app_service/service/saved_address/service.nim index 83d8e59467..426977fcb6 100644 --- a/src/app_service/service/saved_address/service.nim +++ b/src/app_service/service/saved_address/service.nim @@ -1,93 +1,144 @@ -import chronicles, sequtils, json +import NimQml, chronicles, sequtils, json import dto -import ../../../app/core/eventemitter -import ../../../backend/backend -import ../../../app/core/[main] -import ../network/service as network_service -import ../settings/service as settings_service +import backend/backend +import app/core/eventemitter +import app/core/signals/types +import app/core/[main] +import app/core/tasks/[qt, threadpool] +import app_service/service/network/service as network_service +import app_service/service/settings/service as settings_service export dto +include async_tasks + logScope: topics = "saved-address-service" # Signals which may be emitted by this service: -const SIGNAL_SAVED_ADDRESS_CHANGED* = "savedAddressChanged" +const SIGNAL_SAVED_ADDRESSES_UPDATED* = "savedAddressesUpdated" +const SIGNAL_SAVED_ADDRESS_UPDATED* = "savedAddressUpdated" +const SIGNAL_SAVED_ADDRESS_DELETED* = "savedAddressDeleted" type - Service* = ref object of RootObj + SavedAddressArgs* = ref object of Args + address*: string + ens*: string + errorMsg*: string + +QtObject: + type Service* = ref object of QObject + threadpool: ThreadPool events: EventEmitter savedAddresses: seq[SavedAddressDto] networkService: network_service.Service settingsService: settings_service.Service -proc delete*(self: Service) = - discard + proc delete*(self: Service) = + self.QObject.delete -proc newService*(events: EventEmitter, networkService: network_service.Service, - settingsService: settings_service.Service): Service = - result = Service() - result.events = events - result.networkService = networkService - result.settingsService = settingsService + proc newService*(threadpool: ThreadPool, events: EventEmitter, networkService: network_service.Service, + settingsService: settings_service.Service): Service = + new(result, delete) + result.QObject.setup + result.threadpool = threadpool + result.events = events + result.networkService = networkService + result.settingsService = settingsService -proc fetchAddresses(self: Service) = - try: - let response = backend.getSavedAddresses() - self.savedAddresses = map( - response.result.getElems(), - proc(x: JsonNode): SavedAddressDto = toSavedAddressDto(x) + proc fetchAddresses(self: Service) = + try: + let response = backend.getSavedAddresses() + self.savedAddresses = map( + response.result.getElems(), + proc(x: JsonNode): SavedAddressDto = toSavedAddressDto(x) + ) + let chainId = self.networkService.getNetworkForEns().chainId + for savedAddress in self.savedAddresses: + if savedAddress.ens != "": + try: + let nameResponse = backend.getName(chainId, savedAddress.address) + savedAddress.ens = nameResponse.result.getStr + except: + continue + + except Exception as e: + error "error: ", procName="fetchAddress", errName = e.name, errDesription = e.msg + + proc updateAddresses(self: Service) = + self.fetchAddresses() + self.events.emit(SIGNAL_SAVED_ADDRESSES_UPDATED, Args()) + + proc init*(self: Service) = + # Subscribe to sync events and check for changes + self.events.on(SignalType.Message.event) do(e:Args): + var data = MessageSignal(e) + if(len(data.savedAddresses) > 0): + self.updateAddresses() + + self.fetchAddresses() + + proc getSavedAddresses*(self: Service): seq[SavedAddressDto] = + return self.savedAddresses + + proc createOrUpdateSavedAddress*(self: Service, name: string, address: string, favourite: bool, chainShortNames: string, + ens: string) = + let arg = SavedAddressTaskArg( + name: name, + address: address, + favourite: favourite, + chainShortNames: chainShortNames, + ens: ens, + isTestAddress: self.settingsService.areTestNetworksEnabled(), + tptr: cast[ByteAddress](upsertSavedAddressTask), + vptr: cast[ByteAddress](self.vptr), + slot: "onSavedAddressCreatedOrUpdated", ) - let chainId = self.networkService.getNetworkForEns().chainId - for savedAddress in self.savedAddresses: - if savedAddress.ens != "": - try: - let nameResponse = backend.getName(chainId, savedAddress.address) - savedAddress.ens = nameResponse.result.getStr - except: - continue + self.threadpool.start(arg) - except Exception as e: - error "error: ", procName="fetchAddress", errName = e.name, errDesription = e.msg + proc onSavedAddressCreatedOrUpdated*(self: Service, rpcResponse: string) {.slot.} = + var arg = SavedAddressArgs() + try: + let rpcResponseObj = rpcResponse.parseJson + if rpcResponseObj{"error"}.kind != JNull and rpcResponseObj{"error"}.getStr != "": + raise newException(CatchableError, rpcResponseObj{"error"}.getStr) + if rpcResponseObj{"response"}.kind != JNull and rpcResponseObj{"response"}.getStr != "ok": + raise newException(CatchableError, "invalid response") -proc updateAddresses(self: Service) = - self.fetchAddresses() - self.events.emit(SIGNAL_SAVED_ADDRESS_CHANGED, Args()) + arg.address = rpcResponseObj{"address"}.getStr + arg.ens = rpcResponseObj{"ens"}.getStr + except Exception as e: + error "onSavedAddressCreatedOrUpdated", msg = e.msg + arg.errorMsg = e.msg + self.fetchAddresses() + self.events.emit(SIGNAL_SAVED_ADDRESS_UPDATED, arg) -proc init*(self: Service) = - # Subscribe to sync events and check for changes - self.events.on(SignalType.Message.event) do(e:Args): - var data = MessageSignal(e) - if(len(data.savedAddresses) > 0): - self.updateAddresses() + proc deleteSavedAddress*(self: Service, address: string, ens: string) = + let arg = SavedAddressTaskArg( + address: address, + ens: ens, + isTestAddress: self.settingsService.areTestNetworksEnabled(), + tptr: cast[ByteAddress](deleteSavedAddressTask), + vptr: cast[ByteAddress](self.vptr), + slot: "onDeleteSavedAddress", + ) + self.threadpool.start(arg) - self.fetchAddresses() + proc onDeleteSavedAddress*(self: Service, rpcResponse: string) {.slot.} = + var arg = SavedAddressArgs() + try: + let rpcResponseObj = rpcResponse.parseJson + if rpcResponseObj{"error"}.kind != JNull and rpcResponseObj{"error"}.getStr != "": + raise newException(CatchableError, rpcResponseObj{"error"}.getStr) + if rpcResponseObj{"response"}.kind != JNull and rpcResponseObj{"response"}.getStr != "ok": + raise newException(CatchableError, "invalid response") -proc getSavedAddresses*(self: Service): seq[SavedAddressDto] = - return self.savedAddresses - -proc createOrUpdateSavedAddress*(self: Service, name: string, address: string, favourite: bool, chainShortNames: string, ens: string): string = - try: - let isTestAddress = self.settingsService.areTestNetworksEnabled() - discard backend.upsertSavedAddress(backend.SavedAddress(name: name, address: address, favourite: favourite, chainShortNames: chainShortNames, ens: ens, isTest: isTestAddress)) - self.updateAddresses() - return "" - except Exception as e: - let errDesription = e.msg - error "error: ", errDesription - return errDesription - -proc deleteSavedAddress*(self: Service, address: string, ens: string): string = - try: - let isTestAddress = self.settingsService.areTestNetworksEnabled() - var response = backend.deleteSavedAddress(address, ens, isTestAddress) - if not response.error.isNil: - raise newException(Exception, response.error.message) - - self.updateAddresses() - return "" - except Exception as e: - let errDesription = e.msg - return errDesription + arg.address = rpcResponseObj{"address"}.getStr + arg.ens = rpcResponseObj{"ens"}.getStr + except Exception as e: + error "onDeleteSavedAddress", msg = e.msg + arg.errorMsg = e.msg + self.fetchAddresses() + self.events.emit(SIGNAL_SAVED_ADDRESS_DELETED, arg) \ No newline at end of file diff --git a/ui/app/AppLayouts/Wallet/WalletLayout.qml b/ui/app/AppLayouts/Wallet/WalletLayout.qml index 96c23481ca..db5aa9cc8e 100644 --- a/ui/app/AppLayouts/Wallet/WalletLayout.qml +++ b/ui/app/AppLayouts/Wallet/WalletLayout.qml @@ -3,6 +3,7 @@ import QtQuick.Controls 2.13 import QtQuick.Layouts 1.13 import StatusQ.Layout 0.1 +import StatusQ.Core.Utils 0.1 as StatusQUtils import utils 1.0 import shared.controls 1.0 @@ -18,7 +19,6 @@ Item { id: root property bool hideSignPhraseModal: false - property bool showAllAccounts: true property var store property var contactsStore property var emojiPopup: null @@ -26,7 +26,9 @@ Item { property var networkConnectionStore property bool appMainVisible - onAppMainVisibleChanged: showSigningPhrasePopup() + onAppMainVisibleChanged: { + resetView() + } onVisibleChanged: { resetView() @@ -36,7 +38,7 @@ Item { target: walletSection function onFilterChanged(address, allAddresses) { - root.showAllAccounts = allAddresses + RootStore.selectedAddress = allAddresses ? "" : address } function onDisplayKeypairImportPopup() { @@ -49,14 +51,54 @@ Item { } function showSigningPhrasePopup(){ - if(!hideSignPhraseModal && !RootStore.hideSignPhraseModal && visible && appMainVisible){ + + } + + function resetView() { + if (!visible || !appMainVisible) { + return + } + + d.displayAllAddresses() + + if (!!rightPanelStackView.currentItem.resetView) { + rightPanelStackView.currentItem.resetView() + } + + if(!hideSignPhraseModal && !RootStore.hideSignPhraseModal){ signPhrasePopup.open(); } } - function resetView() { - if (!!rightPanelStackView.currentItem.resetView) - rightPanelStackView.currentItem.resetView() + QtObject { + id: d + + readonly property bool showSavedAddresses: RootStore.showSavedAddresses + onShowSavedAddressesChanged: { + if(showSavedAddresses) { + rightPanelStackView.replace(cmpSavedAddresses) + } else { + rightPanelStackView.replace(walletContainer) + } + RootStore.backButtonName = "" + } + + function displayAllAddresses() { + RootStore.showSavedAddresses = false + RootStore.selectedAddress = "" + RootStore.setFillterAllAddresses() + } + + function displayAddress(address) { + RootStore.showSavedAddresses = false + RootStore.selectedAddress = address + RootStore.setFilterAddress(address) + } + + function displaySavedAddresses() { + RootStore.showSavedAddresses = true + RootStore.selectedAddress = "" + } } SignPhraseModal { @@ -74,11 +116,15 @@ Item { Component { id: cmpSavedAddresses SavedAddressesView { - anchors.top: parent ? parent.top: undefined - anchors.left: parent ? parent.left: undefined - anchors.right: parent ? parent.right: undefined + store: root.store contactsStore: root.contactsStore sendModal: root.sendModalPopup + + networkFilter.visible: false + headerButton.text: qsTr("Add new address") + headerButton.onClicked: { + Global.openAddEditSavedAddressesPopup({}) + } } } @@ -89,7 +135,9 @@ Item { contactsStore: root.contactsStore sendModal: root.sendModalPopup networkConnectionStore: root.networkConnectionStore - showAllAccounts: leftTab.showAllAccounts + + headerButton.text: RootStore.overview.ens || StatusQUtils.Utils.elideText(RootStore.overview.mixedcaseAddress, 6, 4) + headerButton.visible: !RootStore.overview.isAllAccounts onLaunchShareAddressModal: Global.openPopup(receiveModalComponent); } } @@ -110,24 +158,18 @@ Item { leftPanel: LeftTabView { id: leftTab anchors.fill: parent - changeSelectedAccount: function(address) { - root.resetView() - RootStore.setFilterAddress(address) - } - selectAllAccounts: function() { - root.resetView() - RootStore.setFillterAllAddresses() - } - onCurrentAddressChanged: root.resetView() - onShowSavedAddressesChanged: { - if(showSavedAddresses) - rightPanelStackView.replace(cmpSavedAddresses) - else - rightPanelStackView.replace(walletContainer) - RootStore.backButtonName = "" - } emojiPopup: root.emojiPopup networkConnectionStore: root.networkConnectionStore + + changeSelectedAccount: function(address) { + d.displayAddress(address) + } + selectAllAccounts: function() { + d.displayAllAddresses() + } + selectSavedAddresses: function() { + d.displaySavedAddresses() + } } centerPanel: StackView { @@ -155,9 +197,9 @@ Item { readonly property bool isCommunityCollectible: !!walletStore.currentViewedCollectible ? walletStore.currentViewedCollectible.communityId !== "" : false readonly property bool isOwnerCommunityCollectible: isCommunityCollectible ? (walletStore.currentViewedCollectible.communityPrivilegesLevel === Constants.TokenPrivilegesLevel.Owner) : false - visible: !root.showAllAccounts + visible: !RootStore.showAllAccounts width: parent.width - height: root.showAllAccounts ? implicitHeight : 61 + height: RootStore.showAllAccounts ? implicitHeight : 61 walletStore: RootStore networkConnectionStore: root.networkConnectionStore isCommunityOwnershipTransfer: footer.isHoldingSelected && footer.isOwnerCommunityCollectible diff --git a/ui/app/AppLayouts/Wallet/controls/SavedAddressesDelegate.qml b/ui/app/AppLayouts/Wallet/controls/SavedAddressesDelegate.qml index 8ba74ccbf5..fcdcc24c27 100644 --- a/ui/app/AppLayouts/Wallet/controls/SavedAddressesDelegate.qml +++ b/ui/app/AppLayouts/Wallet/controls/SavedAddressesDelegate.qml @@ -27,7 +27,6 @@ StatusListItem { property bool areTestNetworksEnabled: false property bool isSepoliaEnabled: false property var saveAddress: function (name, address, favourite, chainShortNames, ens) {} - property var deleteSavedAddress: function (address, ens) {} signal openSendModal(string recipient) @@ -100,12 +99,11 @@ StatusListItem { type: StatusRoundButton.Type.Tertiary icon.name: "add" onClicked: { - Global.openPopup(addEditSavedAddress, - { - addAddress: true, - address: d.visibleAddress, - ens: root.ens - }) + Global.openAddEditSavedAddressesPopup({ + addAddress: true, + address: d.visibleAddress, + ens: root.ens + }) } } ] @@ -142,15 +140,14 @@ StatusListItem { objectName: "editroot" assetSettings.name: "pencil-outline" onTriggered: { - Global.openPopup(addEditSavedAddress, - { - edit: true, - address: editDeleteMenu.contactAddress, - name: editDeleteMenu.contactName, - favourite: editDeleteMenu.storeFavourite, - chainShortNames: editDeleteMenu.contactChainShortNames, - ens: editDeleteMenu.contactEns - }) + Global.openAddEditSavedAddressesPopup({ + edit: true, + address: editDeleteMenu.contactAddress, + name: editDeleteMenu.contactName, + favourite: editDeleteMenu.storeFavourite, + chainShortNames: editDeleteMenu.contactChainShortNames, + ens: editDeleteMenu.contactEns + }) } } StatusAction { @@ -214,65 +211,13 @@ StatusListItem { assetSettings.name: "delete" objectName: "deleteSavedAddress" onTriggered: { - deleteAddressConfirm.name = editDeleteMenu.contactName; - deleteAddressConfirm.address = editDeleteMenu.contactAddress; - deleteAddressConfirm.favourite = editDeleteMenu.storeFavourite; - deleteAddressConfirm.ens = editDeleteMenu.contactEns - deleteAddressConfirm.open() + Global.openDeleteSavedAddressesPopup({ + name: editDeleteMenu.contactName, + address: editDeleteMenu.contactAddress, + favourite: editDeleteMenu.storeFavourite, + ens: editDeleteMenu.contactEns + }) } } } - - Component { - id: addEditSavedAddress - AddEditSavedAddressPopup { - id: addEditModal - anchors.centerIn: parent - onClosed: destroy() - contactsStore: root.contactsStore - store: root.store - onSave: { - root.saveAddress(name, address, favourite, chainShortNames, ens) - close() - } - } - } - - StatusModal { - id: deleteAddressConfirm - property string address - property string ens - property string name - property bool favourite - anchors.centerIn: parent - headerSettings.title: qsTr("Are you sure?") - headerSettings.subTitle: name - contentItem: StatusBaseText { - anchors.centerIn: parent - height: contentHeight + topPadding + bottomPadding - text: qsTr("Are you sure you want to remove '%1' from your saved addresses?").arg(name) - font.pixelSize: 15 - color: Theme.palette.directColor1 - wrapMode: Text.Wrap - topPadding: Style.current.padding - rightPadding: Style.current.padding - bottomPadding: Style.current.padding - leftPadding: Style.current.padding - } - rightButtons: [ - StatusButton { - text: qsTr("Cancel") - onClicked: deleteAddressConfirm.close() - }, - StatusButton { - type: StatusBaseButton.Type.Danger - objectName: "confirmDeleteSavedAddress" - text: qsTr("Delete") - onClicked: { - root.deleteSavedAddress(deleteAddressConfirm.address, deleteAddressConfirm.ens) - deleteAddressConfirm.close() - } - } - ] - } } diff --git a/ui/app/AppLayouts/Wallet/controls/SavedAddressesError.qml b/ui/app/AppLayouts/Wallet/controls/SavedAddressesError.qml index f2dfe8a483..2503eba429 100644 --- a/ui/app/AppLayouts/Wallet/controls/SavedAddressesError.qml +++ b/ui/app/AppLayouts/Wallet/controls/SavedAddressesError.qml @@ -1,19 +1,18 @@ import QtQuick 2.13 -import QtQuick.Controls 2.13 import utils 1.0 import StatusQ.Core 0.1 import StatusQ.Core.Theme 0.1 -import StatusQ.Controls 0.1 Item { - id: addEditError - anchors.left: parent.left - anchors.right: parent.right + id: root property alias text: label.text + implicitHeight: childrenRect.height + implicitWidth: childrenRect.width + StatusIcon { id: errorIcon icon: "warning" @@ -21,6 +20,7 @@ Item { anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left } + StatusBaseText { id: label anchors.verticalCenter: parent.verticalCenter diff --git a/ui/app/AppLayouts/Wallet/panels/WalletHeader.qml b/ui/app/AppLayouts/Wallet/panels/WalletHeader.qml index c0b9ca94ff..5a6998236b 100644 --- a/ui/app/AppLayouts/Wallet/panels/WalletHeader.qml +++ b/ui/app/AppLayouts/Wallet/panels/WalletHeader.qml @@ -20,8 +20,10 @@ Item { property var store property var walletStore - signal launchShareAddressModal() - signal switchHideWatchOnlyAccounts() + property alias headerButton: headerButton + property alias networkFilter: networkFilter + + signal buttonClicked() implicitHeight: 88 @@ -37,19 +39,30 @@ Item { objectName: "accountName" Layout.alignment: Qt.AlignVCenter verticalAlignment: Text.AlignVCenter - color: overview.isAllAccounts ? Theme.palette.directColor5 : Utils.getColorForId(overview.colorId) + color: { + if (root.walletStore.showSavedAddresses) + return Theme.palette.directColor1 + + return overview.isAllAccounts ? Theme.palette.directColor5 : Utils.getColorForId(overview.colorId) + } lineHeightMode: Text.FixedHeight lineHeight: 38 font.bold: true font.pixelSize: 28 - text: overview.isAllAccounts ? qsTr("All Accounts") : overview.name + text: { + if (root.walletStore.showSavedAddresses) + return qsTr("Saved addresses") + + return overview.isAllAccounts ? qsTr("All Accounts") : overview.name + } } StatusEmoji { Layout.alignment: Qt.AlignVCenter Layout.preferredWidth: 28 Layout.preferredHeight: 28 - emojiId: StatusQUtils.Emoji.iconId(overview.emoji ?? "", StatusQUtils.Emoji.size.big) || "" - visible: !overview.isAllAccounts + emojiId: !!root.overview && StatusQUtils.Emoji.iconId(root.overview.emoji ?? "", StatusQUtils.Emoji.size.big) || "" + visible: !root.walletStore.showSavedAddresses && + !!root.overview && !root.overview.isAllAccounts } } @@ -59,27 +72,24 @@ Item { Layout.topMargin: 5 StatusButton { + id: headerButton Layout.preferredHeight: 38 Layout.alignment: Qt.AlignTop spacing: 8 size: StatusBaseButton.Size.Small - borderColor: Theme.palette.directColor7 - normalColor: Theme.palette.transparent - hoverColor: Theme.palette.baseColor2 + borderColor: root.walletStore.showSavedAddresses? "transparent" : Theme.palette.directColor7 + normalColor: root.walletStore.showSavedAddresses? Theme.palette.primaryColor3 : Theme.palette.transparent + hoverColor: root.walletStore.showSavedAddresses? Theme.palette.primaryColor2 : Theme.palette.baseColor2 - font.weight: Font.Normal + font.weight: root.walletStore.showSavedAddresses? Font.Medium : Font.Normal textPosition: StatusBaseButton.TextPosition.Left - textColor: Theme.palette.baseColor1 - text: overview.ens || StatusQUtils.Utils.elideText(overview.mixedcaseAddress, 6, 4) + textColor: root.walletStore.showSavedAddresses? Theme.palette.primaryColor1 : Theme.palette.baseColor1 - icon.name: "invite-users" + icon.name: root.walletStore.showSavedAddresses? "" : "invite-users" icon.height: 16 icon.width: 16 icon.color: hovered ? Theme.palette.directColor1 : Theme.palette.baseColor1 - - onClicked: launchShareAddressModal() - visible: !overview.isAllAccounts } // network filter @@ -101,13 +111,18 @@ Item { RowLayout { spacing: 4 - visible: !networkConnectionStore.accountBalanceNotAvailable + visible: !root.walletStore.showSavedAddresses && + !!root.networkConnectionStore && + !networkConnectionStore.accountBalanceNotAvailable StatusTextWithLoadingState { font.pixelSize: 28 font.bold: true customColor: Theme.palette.directColor1 - text: loading ? Constants.dummyText : LocaleUtils.currencyAmountToLocaleString(root.overview.currencyBalance) - loading: root.overview.balanceLoading + text: loading ? + Constants.dummyText : + !!root.overview? + LocaleUtils.currencyAmountToLocaleString(root.overview.currencyBalance) : "" + loading: !!root.overview && root.overview.balanceLoading lineHeightMode: Text.FixedHeight lineHeight: 38 } diff --git a/ui/app/AppLayouts/Wallet/popups/AddEditSavedAddressPopup.qml b/ui/app/AppLayouts/Wallet/popups/AddEditSavedAddressPopup.qml index d74511b77f..5b529ef0f6 100644 --- a/ui/app/AppLayouts/Wallet/popups/AddEditSavedAddressPopup.qml +++ b/ui/app/AppLayouts/Wallet/popups/AddEditSavedAddressPopup.qml @@ -25,7 +25,7 @@ import ".." StatusDialog { id: root - closePolicy: Popup.CloseOnEscape + closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside property bool edit: false property bool addAddress: false @@ -35,10 +35,18 @@ StatusDialog { property alias name: nameInput.text property bool favourite: false - property var contactsStore - property var store - signal save(string name, string address, string chainShortNames, string ens) + property var allNetworks + + function applyParams(params = {}) { + root.addAddress = params.addAddress?? false + root.address = params.address?? Constants.zeroAddress + root.ens = params.ens?? "" + root.edit = params.edit?? false + root.name = params.name?? "" + root.favourite = params.favourite?? false + root.chainShortNames = params.chainShortNames?? "" + } QtObject { id: d @@ -339,7 +347,10 @@ StatusDialog { StatusButton { text: root.edit ? qsTr("Save") : qsTr("Add address") enabled: d.valid && d.dirty - onClicked: root.save(name, address, chainShortNames, ens) + onClicked: { + RootStore.createOrUpdateSavedAddress(name, address, root.favourite, chainShortNames, ens) + root.close() + } objectName: "addSavedAddress" } } @@ -348,7 +359,7 @@ StatusDialog { CloneModel { id: allNetworksModelCopy - sourceModel: store.allNetworks + sourceModel: root.allNetworks roles: ["layer", "chainId", "chainColor", "chainName","shortName", "iconUrl"] rolesOverride: [{ role: "isEnabled", transform: (modelData) => Boolean(false) }] diff --git a/ui/app/AppLayouts/Wallet/popups/TransactionAddressMenu.qml b/ui/app/AppLayouts/Wallet/popups/TransactionAddressMenu.qml index 23d762c444..aae111a30f 100644 --- a/ui/app/AppLayouts/Wallet/popups/TransactionAddressMenu.qml +++ b/ui/app/AppLayouts/Wallet/popups/TransactionAddressMenu.qml @@ -311,8 +311,7 @@ StatusMenu { } icon.name: "star-icon-outline" onTriggered: { - Global.openPopup(addEditSavedAddress, - { + Global.openAddEditSavedAddressesPopup({ addAddress: true, address: d.selectedAddress, ens: d.addressEns, @@ -325,8 +324,7 @@ StatusMenu { enabled: false text: qsTr("Edit saved address") icon.name: "pencil-outline" - onTriggered: Global.openPopup(addEditSavedAddress, - { + onTriggered: Global.openAddEditSavedAddressesPopup({ edit: true, name: d.addressName, address: d.selectedAddress, @@ -351,21 +349,6 @@ StatusMenu { onTriggered: root.openSendModal(d.selectedAddress) } - Component { - id: addEditSavedAddress - AddEditSavedAddressPopup { - id: addEditModal - anchors.centerIn: parent - onClosed: destroy() - contactsStore: root.contactsStore - store: WalletStores.RootStore - onSave: { - RootStore.createOrUpdateSavedAddress(name, address, false, chainShortNames, ens) - close() - } - } - } - Component { id: addressQr ReceiveModal { diff --git a/ui/app/AppLayouts/Wallet/popups/qmldir b/ui/app/AppLayouts/Wallet/popups/qmldir index 911c512ace..4ad982cb22 100644 --- a/ui/app/AppLayouts/Wallet/popups/qmldir +++ b/ui/app/AppLayouts/Wallet/popups/qmldir @@ -3,3 +3,4 @@ ActivityFilterMenu 1.0 ActivityFilterMenu.qml ActivityPeriodFilterSubMenu 1.0 filterSubMenus/ActivityPeriodFilterSubMenu.qml ActivityTypeFilterSubMenu 1.0 filterSubMenus/ActivityTypeFilterSubMenu.qml ReceiveModal 1.0 ReceiveModal.qml +AddEditSavedAddressPopup 1.0 AddEditSavedAddressPopup.qml diff --git a/ui/app/AppLayouts/Wallet/stores/RootStore.qml b/ui/app/AppLayouts/Wallet/stores/RootStore.qml index e75e300894..3b8358c8cc 100644 --- a/ui/app/AppLayouts/Wallet/stores/RootStore.qml +++ b/ui/app/AppLayouts/Wallet/stores/RootStore.qml @@ -13,6 +13,14 @@ import StatusQ.Core.Utils 0.1 as SQUtils QtObject { id: root + property bool showSavedAddresses: false + property string selectedAddress: "" + readonly property bool showAllAccounts: !root.showSavedAddresses && !root.selectedAddress + + property var lastCreatedSavedAddress + property bool addingSavedAddress: false + property bool deletingSavedAddress: false + readonly property TokensStore tokensStore: TokensStore {} readonly property WalletAssetsStore walletAssetsStore: WalletAssetsStore { walletTokensStore: tokensStore @@ -41,6 +49,7 @@ QtObject { // "walletSection" is a context property slow to lookup, so we cache it here property var mainModuleInst: mainModule property var walletSectionInst: walletSection + property var walletSectionSavedAddressesInst: walletSectionSavedAddresses property var totalCurrencyBalance: walletSectionInst.totalCurrencyBalance property var activityController: walletSectionInst.activityController property var tmpActivityController: walletSectionInst.tmpActivityController @@ -322,11 +331,13 @@ QtObject { } function createOrUpdateSavedAddress(name, address, favourite, chainShortNames, ens) { - return walletSectionSavedAddresses.createOrUpdateSavedAddress(name, address, favourite, chainShortNames, ens) + root.addingSavedAddress = true + walletSectionSavedAddresses.createOrUpdateSavedAddress(name, address, favourite, chainShortNames, ens) } function deleteSavedAddress(address, ens) { - return walletSectionSavedAddresses.deleteSavedAddress(address, ens) + root.deletingSavedAddress = true + walletSectionSavedAddresses.deleteSavedAddress(address, ens) } function toggleNetwork(chainId) { diff --git a/ui/app/AppLayouts/Wallet/views/AssetsDetailView.qml b/ui/app/AppLayouts/Wallet/views/AssetsDetailView.qml index c11fef3a02..e5bca28a90 100644 --- a/ui/app/AppLayouts/Wallet/views/AssetsDetailView.qml +++ b/ui/app/AppLayouts/Wallet/views/AssetsDetailView.qml @@ -35,9 +35,9 @@ Item { QtObject { id: d property var marketValueStore : RootStore.marketValueStore - readonly property string symbol: root.token ? root.token.symbol : "" - property bool marketDetailsLoading: token && token.marketDetailsLoading ? token.marketDetailsLoading: false - property bool tokenDetailsLoading: token && token.detailsLoading? token.detailsLoading: false + readonly property string symbol: !!root.token? root.token.symbol?? "" : "" + property bool marketDetailsLoading: !!root.token? root.token.marketDetailsLoading?? false : false + property bool tokenDetailsLoading: !!root.token? root.token.detailsLoading?? false: false readonly property LeftJoinModel addressPerChainModel: LeftJoinModel { leftModel: token && token.addressPerChain ? token.addressPerChain: null diff --git a/ui/app/AppLayouts/Wallet/views/LeftTabView.qml b/ui/app/AppLayouts/Wallet/views/LeftTabView.qml index 1aa7345698..1dfcf000d2 100644 --- a/ui/app/AppLayouts/Wallet/views/LeftTabView.qml +++ b/ui/app/AppLayouts/Wallet/views/LeftTabView.qml @@ -30,31 +30,7 @@ Rectangle { property var networkConnectionStore property var selectAllAccounts: function(){} property var changeSelectedAccount: function(){} - property bool showSavedAddresses: false - property bool showAllAccounts: true - property string currentAddress: "" - - onCurrentAddressChanged: { - if (!currentAddress) - return - root.showAllAccounts = false - root.showSavedAddresses = false - } - - onShowSavedAddressesChanged: { - if (!showSavedAddresses) - return - root.currentAddress = "" - root.showAllAccounts = false - } - - onShowAllAccountsChanged: { - if (!showAllAccounts) - return - root.currentAddress = "" - root.showSavedAddresses = false - } - + property var selectSavedAddresses: function(){} property var emojiPopup: null color: Style.current.secondaryMenuBackground @@ -169,10 +145,6 @@ Rectangle { function onDestroyAddAccountPopup() { addAccount.active = false } - function onFilterChanged(address, allAddresses) { - root.currentAddress = allAddresses ? "" : address - root.showAllAccounts = allAddresses - } } MouseArea { @@ -273,7 +245,7 @@ Rectangle { objectName: "walletAccount-" + model.name readonly property bool itemLoaded: !model.assetsLoading // needed for e2e tests width: ListView.view.width - Style.current.padding * 2 - highlighted: root.currentAddress.toLowerCase() === model.address.toLowerCase() + highlighted: RootStore.selectedAddress.toLowerCase() === model.address.toLowerCase() onHighlightedChanged: { if (highlighted) ListView.view.currentIndex = index @@ -324,7 +296,7 @@ Rectangle { id: header verticalPadding: Style.current.padding horizontalPadding: Style.current.padding - highlighted: root.showAllAccounts + highlighted: RootStore.showAllAccounts objectName: "allAccountsBtn" leftInset: Style.current.padding @@ -442,7 +414,7 @@ Rectangle { contentItem: StatusFlatButton { objectName: "savedAddressesBtn" - highlighted: root.showSavedAddresses + highlighted: RootStore.showSavedAddresses hoverColor: Style.current.backgroundHover asset.bgColor: Theme.palette.primaryColor3 text: qsTr("Saved addresses") @@ -454,7 +426,7 @@ Rectangle { textColor: Theme.palette.directColor1 textFillWidth: true spacing: walletAccountsListView.firstItem.statusListItemTitleArea.anchors.leftMargin - onClicked: root.showSavedAddresses = true + onClicked: root.selectSavedAddresses() MouseArea { anchors.fill: parent diff --git a/ui/app/AppLayouts/Wallet/views/RightTabBaseView.qml b/ui/app/AppLayouts/Wallet/views/RightTabBaseView.qml new file mode 100644 index 0000000000..ab691aa4d5 --- /dev/null +++ b/ui/app/AppLayouts/Wallet/views/RightTabBaseView.qml @@ -0,0 +1,47 @@ +import QtQuick 2.14 +import QtQuick.Layouts 1.14 + +import StatusQ.Core 0.1 + +import "../stores" +import "../panels" + +FocusScope { + id: root + + property var store + property var contactsStore + property var networkConnectionStore + + property var sendModal + + property alias header: header + property alias headerButton: header.headerButton + property alias networkFilter: header.networkFilter + + default property Item content + + Component.onCompleted: { + content.parent = contentWrapper + } + + ColumnLayout { + anchors.left: parent.left + anchors.right: parent.right + spacing: 0 + + WalletHeader { + id: header + Layout.fillWidth: true + overview: RootStore.overview + store: root.store + walletStore: RootStore + networkConnectionStore: root.networkConnectionStore + } + + Column { + id: contentWrapper + Layout.fillWidth: true + } + } +} diff --git a/ui/app/AppLayouts/Wallet/views/RightTabView.qml b/ui/app/AppLayouts/Wallet/views/RightTabView.qml index 2059c12480..23312bdb7d 100644 --- a/ui/app/AppLayouts/Wallet/views/RightTabView.qml +++ b/ui/app/AppLayouts/Wallet/views/RightTabView.qml @@ -16,18 +16,17 @@ import "../stores" import "../panels" import "../views/collectibles" -Item { +RightTabBaseView { id: root property alias currentTabIndex: walletTabBar.currentIndex - property var store - property var contactsStore - property var sendModal - property var networkConnectionStore - property bool showAllAccounts: false signal launchShareAddressModal() + headerButton.onClicked: { + root.launchShareAddressModal() + } + function resetView() { stack.currentIndex = 0 root.currentTabIndex = 0 @@ -38,48 +37,42 @@ Item { stack.currentIndex = 0; } - Connections { - target: walletSection - - function onFilterChanged() { - root.resetStack() - } - } - - QtObject { - id: d - function getBackButtonText(index) { - switch(index) { - case 1: - return qsTr("Collectibles") - case 2: - return qsTr("Assets") - case 3: - return qsTr("Activity") - default: - return "" - } - } - } - StackLayout { id: stack - anchors.fill: parent + width: parent.width + + Connections { + target: walletSection + + function onFilterChanged() { + root.resetStack() + } + } + onCurrentIndexChanged: { RootStore.backButtonName = d.getBackButtonText(currentIndex) } - ColumnLayout { - spacing: 0 - WalletHeader { - Layout.fillWidth: true - overview: RootStore.overview - store: root.store - walletStore: RootStore - networkConnectionStore: root.networkConnectionStore - onLaunchShareAddressModal: root.launchShareAddressModal() - onSwitchHideWatchOnlyAccounts: RootStore.toggleWatchOnlyAccounts() + QtObject { + id: d + function getBackButtonText(index) { + switch(index) { + case 1: + return qsTr("Collectibles") + case 2: + return qsTr("Assets") + case 3: + return qsTr("Activity") + default: + return "" + } } + } + + ColumnLayout { + Layout.fillWidth: true + Layout.fillHeight: true + spacing: 0 ImportKeypairInfo { Layout.fillWidth: true @@ -187,7 +180,7 @@ Item { HistoryView { id: historyView overview: RootStore.overview - showAllAccounts: root.showAllAccounts + showAllAccounts: RootStore.showAllAccounts sendModal: root.sendModal filterVisible: filterButton.checked onLaunchTransactionDetail: function (entry, entryIndex) { @@ -214,7 +207,7 @@ Item { allNetworksModel: RootStore.allNetworks address: RootStore.overview.mixedcaseAddress - showAllAccounts: root.showAllAccounts + showAllAccounts: RootStore.showAllAccounts currencyStore: RootStore.currencyStore networkFilters: RootStore.networkFilters @@ -240,7 +233,7 @@ Item { transaction = null } } - showAllAccounts: root.showAllAccounts + showAllAccounts: RootStore.showAllAccounts sendModal: root.sendModal contactsStore: root.contactsStore visible: (stack.currentIndex === 3) diff --git a/ui/app/AppLayouts/Wallet/views/SavedAddresses.qml b/ui/app/AppLayouts/Wallet/views/SavedAddresses.qml new file mode 100644 index 0000000000..91b34a68e0 --- /dev/null +++ b/ui/app/AppLayouts/Wallet/views/SavedAddresses.qml @@ -0,0 +1,114 @@ +import QtQuick 2.14 +import QtQuick.Controls 2.14 +import QtQuick.Layouts 1.14 + +import StatusQ.Components 0.1 +import StatusQ.Core 0.1 +import StatusQ.Core.Theme 0.1 + +import SortFilterProxyModel 0.2 + +import shared.controls 1.0 + +import "../stores" +import "../controls" + +ColumnLayout { + id: root + + property var sendModal + property var contactsStore + + QtObject { + id: d + + function saveAddress(name, address, favourite, chainShortNames, ens) { + RootStore.createOrUpdateSavedAddress(name, address, favourite, chainShortNames, ens) + } + + function reset() { + RootStore.lastCreatedSavedAddress = undefined + } + } + + SavedAddressesError { + id: error + Layout.alignment: Qt.AlignHCenter + text: RootStore.lastCreatedSavedAddress? RootStore.lastCreatedSavedAddress.error?? "" : "" + visible: !!text + height: visible ? 36 : 0 + } + + ShapeRectangle { + id: noSavedAddresses + Layout.fillWidth: true + visible: listView.count === 0 + text: qsTr("Your saved addresses will appear here") + } + + StatusLoadingIndicator { + id: loadingIndicator + Layout.alignment: Qt.AlignHCenter + visible: RootStore.addingSavedAddress || RootStore.deletingSavedAddress + color: Theme.palette.directColor4 + } + + Item { + visible: error.visible || noSavedAddresses.visible || loadingIndicator.visible + Layout.fillWidth: true + Layout.fillHeight: true + } + + StatusListView { + id: listView + objectName: "SavedAddressesView_savedAddresses" + Layout.fillWidth: true + Layout.fillHeight: true + spacing: 5 + visible: count > 0 + model: SortFilterProxyModel { + sourceModel: RootStore.savedAddresses + sorters: RoleSorter { roleName: "createdAt"; sortOrder: Qt.DescendingOrder } + } + delegate: SavedAddressesDelegate { + id: savedAddressDelegate + objectName: "savedAddressView_Delegate_" + name + name: model.name + address: model.address + chainShortNames: model.chainShortNames + ens: model.ens + favourite: model.favourite + store: RootStore + contactsStore: root.contactsStore + areTestNetworksEnabled: RootStore.areTestNetworksEnabled + isSepoliaEnabled: RootStore.isSepoliaEnabled + onOpenSendModal: root.sendModal.open(recipient); + + saveAddress: function(name, address, favourite, chainShortNames, ens) { + d.saveAddress(name, address, favourite, chainShortNames, ens) + } + + states: [ + State { + name: "highlighted" + when: RootStore.lastCreatedSavedAddress ? (RootStore.lastCreatedSavedAddress.address.toLowerCase() === address.toLowerCase() && + RootStore.lastCreatedSavedAddress.ens === ens) : false + PropertyChanges { target: savedAddressDelegate; color: Theme.palette.baseColor2 } + StateChangeScript { + script: Qt.callLater(d.reset) + } + } + ] + + transitions: [ + Transition { + from: "highlighted" + ColorAnimation { + target: savedAddressDelegate + duration: 3000 + } + } + ] + } + } +} diff --git a/ui/app/AppLayouts/Wallet/views/SavedAddressesView.qml b/ui/app/AppLayouts/Wallet/views/SavedAddressesView.qml index 08b6c45d03..f44a3ac677 100644 --- a/ui/app/AppLayouts/Wallet/views/SavedAddressesView.qml +++ b/ui/app/AppLayouts/Wallet/views/SavedAddressesView.qml @@ -1,174 +1,13 @@ import QtQuick 2.13 -import QtQuick.Controls 2.13 -import QtQuick.Layouts 1.13 -import utils 1.0 - -import StatusQ.Controls 0.1 -import StatusQ.Components 0.1 -import StatusQ.Core 0.1 -import StatusQ.Core.Theme 0.1 -import StatusQ.Popups 0.1 -import shared.controls 1.0 -import SortFilterProxyModel 0.2 - -import "../stores" -import "../popups" -import "../controls" - -Item { +RightTabBaseView { id: root - anchors.leftMargin: Style.current.padding - anchors.rightMargin: Style.current.padding - property var sendModal - property var contactsStore + SavedAddresses { + width: root.width + height: root.height - header.height - QtObject { - id: _internal - property bool loading: false - property string error: "" - property var lastCreatedAddress // used to display animation for the newly saved address - function saveAddress(name, address, favourite, chainShortNames, ens) { - loading = true - error = RootStore.createOrUpdateSavedAddress(name, address, favourite, chainShortNames, ens) - loading = false - } - function deleteSavedAddress(address, ens) { - loading = true - error = RootStore.deleteSavedAddress(address, ens) - loading = false - } - - function resetLastCreatedAddress() { - lastCreatedAddress = undefined - } - } - - Item { - id: header - anchors.left: parent.left - anchors.right: parent.right - anchors.top: parent.top - height: btnAdd.height - - StatusBaseText { - anchors.left: parent.left - anchors.verticalCenter: parent.verticalCenter - id: title - text: qsTr("Saved addresses") - font.weight: Font.Bold - font.pixelSize: 28 - color: Theme.palette.directColor1 - } - StatusButton { - objectName: "addNewAddressBtn" - id: btnAdd - anchors.right: parent.right - anchors.top: parent.top - anchors.verticalCenter: parent.verticalCenter - size: StatusBaseButton.Size.Small - font.weight: Font.Medium - text: qsTr("Add new address") - visible: !_internal.loading - onClicked: { - Global.openPopup(addEditSavedAddress) - } - } - StatusLoadingIndicator { - anchors.centerIn: parent - visible: _internal.loading - color: Theme.palette.directColor4 - } - } - - SavedAddressesError { - id: errorMessage - anchors.top: header.bottom - anchors.topMargin: Style.current.padding - visible: _internal.error !== "" - text: _internal.error - height: visible ? 36 : 0 - } - - StatusBaseText { - anchors.centerIn: parent - visible: listView.count === 0 - color: Theme.palette.baseColor1 - text: qsTr("No saved addresses") - } - - StatusListView { - id: listView - objectName: "SavedAddressesView_savedAddresses" - anchors.top: header.bottom - anchors.topMargin: Style.current.padding - anchors.bottom: parent.bottom - anchors.right: parent.right - anchors.left: parent.left - spacing: 5 - visible: count > 0 - model: SortFilterProxyModel { - sourceModel: RootStore.savedAddresses - sorters: RoleSorter { roleName: "createdAt"; sortOrder: Qt.DescendingOrder } - } - delegate: SavedAddressesDelegate { - id: savedAddressDelegate - objectName: "savedAddressView_Delegate_" + name - name: model.name - address: model.address - chainShortNames: model.chainShortNames - ens: model.ens - favourite: model.favourite - store: RootStore - contactsStore: root.contactsStore - areTestNetworksEnabled: RootStore.areTestNetworksEnabled - isSepoliaEnabled: RootStore.isSepoliaEnabled - onOpenSendModal: root.sendModal.open(recipient); - saveAddress: function(name, address, favourite, chainShortNames, ens) { - _internal.saveAddress(name, address, favourite, chainShortNames, ens) - } - deleteSavedAddress: function(address, ens) { - _internal.deleteSavedAddress(address, ens) - } - - states: [ - State { - name: "highlighted" - when: _internal.lastCreatedAddress ? (_internal.lastCreatedAddress.address.toLowerCase() === address.toLowerCase() && - _internal.lastCreatedAddress.ens === ens) : false - PropertyChanges { target: savedAddressDelegate; color: Theme.palette.baseColor2 } - StateChangeScript { - script: Qt.callLater(_internal.resetLastCreatedAddress) - } - } - ] - - transitions: [ - Transition { - from: "highlighted" - ColorAnimation { - target: savedAddressDelegate - duration: 3000 - } - } - ] - } - } - - Component { - id: addEditSavedAddress - AddEditSavedAddressPopup { - id: addEditModal - anchors.centerIn: parent - onClosed: destroy() - contactsStore: root.contactsStore - store: RootStore - onSave: { - _internal.lastCreatedAddress = { address: address, ens: ens } - _internal.saveAddress(name, address, favourite, chainShortNames, ens) - close() - } - } + sendModal: root.sendModal + contactsStore: root.contactsStore } } diff --git a/ui/app/AppLayouts/Wallet/views/qmldir b/ui/app/AppLayouts/Wallet/views/qmldir index a4182f7c8c..ee26025ae7 100644 --- a/ui/app/AppLayouts/Wallet/views/qmldir +++ b/ui/app/AppLayouts/Wallet/views/qmldir @@ -1,2 +1,3 @@ CollectiblesView 1.0 CollectiblesView.qml AssetsDetailView 1.0 AssetsDetailView.qml +SavedAddresses 1.0 SavedAddresses.qml diff --git a/ui/app/mainui/AppMain.qml b/ui/app/mainui/AppMain.qml index 746d56f5b1..3a64f53f1a 100644 --- a/ui/app/mainui/AppMain.qml +++ b/ui/app/mainui/AppMain.qml @@ -40,6 +40,7 @@ import AppLayouts.stores 1.0 import AppLayouts.Chat.stores 1.0 as ChatStores import AppLayouts.Communities.stores 1.0 import AppLayouts.Wallet.stores 1.0 as WalletStore +import AppLayouts.Wallet.popups 1.0 as WalletPopups import AppLayouts.Wallet.views.walletconnect 1.0 import mainui.activitycenter.stores 1.0 @@ -345,6 +346,14 @@ Item { function onSwitchToCommunity(communityId: string) { appMain.communitiesStore.setActiveCommunity(communityId) } + + function onOpenAddEditSavedAddressesPopup(params) { + addEditSavedAddress.open(params) + } + + function onOpenDeleteSavedAddressesPopup(params) { + deleteSavedAddress.open(params) + } } Connections { @@ -1233,7 +1242,7 @@ Item { appMainVisible: appMain.visible } onLoaded: { - item.showSigningPhrasePopup() + item.resetView() } } @@ -1672,6 +1681,132 @@ Item { } } + Loader { + id: addEditSavedAddress + + active: false + + property var params + + function open(params = {}) { + addEditSavedAddress.params = params + addEditSavedAddress.active = true + } + + function close() { + addEditSavedAddress.active = false + } + + onLoaded: { + addEditSavedAddress.item.applyParams(addEditSavedAddress.params) + addEditSavedAddress.item.open() + } + + sourceComponent: WalletPopups.AddEditSavedAddressPopup { + allNetworks: RootStore.allNetworks + + onClosed: { + addEditSavedAddress.close() + } + } + + Connections { + target: WalletStore.RootStore.walletSectionSavedAddressesInst + + function onSavedAddressUpdated(address: string, ens: string, errorMsg: string) { + WalletStore.RootStore.addingSavedAddress = false + + if (!!errorMsg) { + WalletStore.RootStore.lastCreatedSavedAddress = { error: errorMsg } + return + } + + WalletStore.RootStore.lastCreatedSavedAddress = { address: address, ens: ens } + } + } + } + + Loader { + id: deleteSavedAddress + + active: false + + property var params + + function open(params = {}) { + deleteSavedAddress.params = params + deleteSavedAddress.active = true + } + + function close() { + deleteSavedAddress.active = false + } + + onLoaded: { + deleteSavedAddress.item.address = deleteSavedAddress.params.address?? "" + deleteSavedAddress.item.ens = deleteSavedAddress.params.ens?? "" + deleteSavedAddress.item.name = deleteSavedAddress.params.name?? "" + deleteSavedAddress.item.favourite = deleteSavedAddress.params.favourite?? false + + deleteSavedAddress.item.open() + } + + sourceComponent: StatusModal { + + property string address + property string ens + property string name + property bool favourite + + closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside + headerSettings.title: qsTr("Are you sure?") + headerSettings.subTitle: name + + + contentItem: StatusBaseText { + anchors.centerIn: parent + height: contentHeight + topPadding + bottomPadding + text: qsTr("Are you sure you want to remove '%1' from your saved addresses?").arg(name) + font.pixelSize: 15 + color: Theme.palette.directColor1 + wrapMode: Text.Wrap + topPadding: Style.current.padding + rightPadding: Style.current.padding + bottomPadding: Style.current.padding + leftPadding: Style.current.padding + } + + onClosed: { + deleteSavedAddress.close() + } + + rightButtons: [ + StatusButton { + text: qsTr("Cancel") + onClicked: close() + }, + StatusButton { + type: StatusBaseButton.Type.Danger + objectName: "confirmDeleteSavedAddress" + text: qsTr("Delete") + onClicked: { + WalletStore.RootStore.deleteSavedAddress(address, ens) + close() + } + } + ] + } + + Connections { + target: WalletStore.RootStore.walletSectionSavedAddressesInst + + function onSavedAddressDeleted(address: string, ens: string, errorMsg: string) { + WalletStore.RootStore.deletingSavedAddress = false + WalletStore.RootStore.lastCreatedSavedAddress = { error: errorMsg } + } + } + } + DropAreaPanel { id: rootDropAreaPanel diff --git a/ui/imports/shared/stores/RootStore.qml b/ui/imports/shared/stores/RootStore.qml index 07ad00ad1d..ebe4c4a3f8 100644 --- a/ui/imports/shared/stores/RootStore.qml +++ b/ui/imports/shared/stores/RootStore.qml @@ -191,14 +191,6 @@ QtObject { return walletSectionSavedAddresses.getEnsForAddress(address) } - function createOrUpdateSavedAddress(name, address, favourite, chainShortNames, ens) { - return walletSectionSavedAddresses.createOrUpdateSavedAddress(name, address, favourite, chainShortNames, ens) - } - - function deleteSavedAddress(addresse, ens) { - return walletSectionSavedAddresses.deleteSavedAddress(address, ens) - } - function getCurrencyAmount(amount, symbol) { return currencyStore.getCurrencyAmount(amount, symbol) } diff --git a/ui/imports/utils/Global.qml b/ui/imports/utils/Global.qml index f03056bc9e..95a3012168 100644 --- a/ui/imports/utils/Global.qml +++ b/ui/imports/utils/Global.qml @@ -84,6 +84,9 @@ QtObject { signal popupWalletConnect() + signal openAddEditSavedAddressesPopup(var params) + signal openDeleteSavedAddressesPopup(var params) + function openProfilePopup(publicKey, parentPopup, cb) { root.openProfilePopupRequested(publicKey, parentPopup, cb) }