feat(savedaddresses): update adding saved address popup to match new design

Implemented:
- adding selection color section
- all input field validations
- interactions within the popup
- an ephemeral notification when adding an address

Closes #13089
This commit is contained in:
Sale Djenic 2023-12-29 14:10:55 +01:00 committed by saledjenic
parent 3d5b24b87f
commit ff9062a1b0
27 changed files with 335 additions and 232 deletions

View File

@ -27,7 +27,7 @@ proc init*(self: Controller) =
self.events.on(SIGNAL_SAVED_ADDRESS_UPDATED) do(e:Args): self.events.on(SIGNAL_SAVED_ADDRESS_UPDATED) do(e:Args):
let args = SavedAddressArgs(e) let args = SavedAddressArgs(e)
self.delegate.savedAddressUpdated(args.address, args.ens, args.errorMsg) self.delegate.savedAddressUpdated(args.name, args.address, args.ens, args.errorMsg)
self.events.on(SIGNAL_SAVED_ADDRESS_DELETED) do(e:Args): self.events.on(SIGNAL_SAVED_ADDRESS_DELETED) do(e:Args):
let args = SavedAddressArgs(e) let args = SavedAddressArgs(e)
@ -36,8 +36,9 @@ proc init*(self: Controller) =
proc getSavedAddresses*(self: Controller): seq[saved_address_service.SavedAddressDto] = proc getSavedAddresses*(self: Controller): seq[saved_address_service.SavedAddressDto] =
return self.savedAddressService.getSavedAddresses() return self.savedAddressService.getSavedAddresses()
proc createOrUpdateSavedAddress*(self: Controller, name: string, address: string, favourite: bool, chainShortNames: string, ens: string) = proc createOrUpdateSavedAddress*(self: Controller, name: string, address: string, ens: string, colorId: string,
self.savedAddressService.createOrUpdateSavedAddress(name, address, favourite, chainShortNames, ens) favourite: bool, chainShortNames: string) =
self.savedAddressService.createOrUpdateSavedAddress(name, address, ens, colorId, favourite, chainShortNames)
proc deleteSavedAddress*(self: Controller, address: string, ens: string) = proc deleteSavedAddress*(self: Controller, address: string, ens: string) =
self.savedAddressService.deleteSavedAddress(address, ens) self.savedAddressService.deleteSavedAddress(address, ens)

View File

@ -17,18 +17,25 @@ method viewDidLoad*(self: AccessInterface) {.base.} =
method loadSavedAddresses*(self: AccessInterface) {.base.} = method loadSavedAddresses*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
method createOrUpdateSavedAddress*(self: AccessInterface, name: string, address: string, favourite: bool, chainShortNames: string, ens: string) {.base.} = method createOrUpdateSavedAddress*(self: AccessInterface, name: string, address: string, ens: string, colorId: string,
favourite: bool, chainShortNames: string) {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
method deleteSavedAddress*(self: AccessInterface, address: string, ens: string) {.base.} = method deleteSavedAddress*(self: AccessInterface, address: string, ens: string) {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
method savedAddressUpdated*(self: AccessInterface, address: string, ens: string, errorMsg: string) {.base.} = method savedAddressUpdated*(self: AccessInterface, name: string, address: string, ens: string, errorMsg: string) {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
method savedAddressDeleted*(self: AccessInterface, address: string, ens: string, errorMsg: string) {.base.} = method savedAddressDeleted*(self: AccessInterface, address: string, ens: string, errorMsg: string) {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
method savedAddressNameExists*(self: AccessInterface, name: string): bool {.base.} =
raise newException(ValueError, "No implementation available")
method getSavedAddressAsJson*(self: AccessInterface, address: string): string {.base.} =
raise newException(ValueError, "No implementation available")
type type
## Abstract class (concept) which must be implemented by object/s used in this ## Abstract class (concept) which must be implemented by object/s used in this
## module. ## module.

View File

@ -5,6 +5,7 @@ type
name: string name: string
address: string address: string
ens: string ens: string
colorId: string
favourite: bool favourite: bool
chainShortNames: string chainShortNames: string
isTest: bool isTest: bool
@ -12,8 +13,9 @@ type
proc initItem*( proc initItem*(
name: string, name: string,
address: string, address: string,
favourite: bool,
ens: string, ens: string,
colorId: string,
favourite: bool,
chainShortNames: string, chainShortNames: string,
isTest: bool isTest: bool
): Item = ): Item =
@ -21,6 +23,7 @@ proc initItem*(
result.address = address result.address = address
result.favourite = favourite result.favourite = favourite
result.ens = ens result.ens = ens
result.colorId = colorId
result.chainShortNames = chainShortNames result.chainShortNames = chainShortNames
result.isTest = isTest result.isTest = isTest
@ -28,8 +31,9 @@ proc `$`*(self: Item): string =
result = fmt"""SavedAddressItem( result = fmt"""SavedAddressItem(
name: {self.name}, name: {self.name},
address: {self.address}, address: {self.address},
favourite: {self.favourite},
ens: {self.ens}, ens: {self.ens},
colorId: {self.colorId},
favourite: {self.favourite},
chainShortNames: {self.chainShortNames}, chainShortNames: {self.chainShortNames},
isTest: {self.isTest}, isTest: {self.isTest},
]""" ]"""
@ -43,6 +47,9 @@ proc getEns*(self: Item): string =
proc getAddress*(self: Item): string = proc getAddress*(self: Item): string =
return self.address return self.address
proc getColorId*(self: Item): string =
return self.colorId
proc getFavourite*(self: Item): bool = proc getFavourite*(self: Item): bool =
return self.favourite return self.favourite

View File

@ -1,13 +1,16 @@
import NimQml, Tables, strutils, strformat import NimQml, Tables, strutils, strformat
import ./item import item
export item
type type
ModelRole {.pure.} = enum ModelRole {.pure.} = enum
Name = UserRole + 1, Name = UserRole + 1,
Address Address
Favourite
Ens Ens
ColorId
Favourite
ChainShortNames ChainShortNames
IsTest IsTest
@ -48,8 +51,9 @@ QtObject:
{ {
ModelRole.Name.int:"name", ModelRole.Name.int:"name",
ModelRole.Address.int:"address", ModelRole.Address.int:"address",
ModelRole.Favourite.int:"favourite",
ModelRole.Ens.int:"ens", ModelRole.Ens.int:"ens",
ModelRole.ColorId.int:"colorId",
ModelRole.Favourite.int:"favourite",
ModelRole.ChainShortNames.int:"chainShortNames", ModelRole.ChainShortNames.int:"chainShortNames",
ModelRole.IsTest.int:"isTest", ModelRole.IsTest.int:"isTest",
}.toTable }.toTable
@ -69,10 +73,12 @@ QtObject:
result = newQVariant(item.getName()) result = newQVariant(item.getName())
of ModelRole.Address: of ModelRole.Address:
result = newQVariant(item.getAddress()) result = newQVariant(item.getAddress())
of ModelRole.Favourite:
result = newQVariant(item.getFavourite())
of ModelRole.Ens: of ModelRole.Ens:
result = newQVariant(item.getEns()) result = newQVariant(item.getEns())
of ModelRole.ColorId:
result = newQVariant(item.getColorId())
of ModelRole.Favourite:
result = newQVariant(item.getFavourite())
of ModelRole.ChainShortNames: of ModelRole.ChainShortNames:
result = newQVariant(item.getChainShortNames()) result = newQVariant(item.getChainShortNames())
of ModelRole.IsTest: of ModelRole.IsTest:
@ -85,8 +91,9 @@ QtObject:
case column: case column:
of "name": result = $item.getName() of "name": result = $item.getName()
of "address": result = $item.getAddress() of "address": result = $item.getAddress()
of "favourite": result = $item.getFavourite()
of "ens": result = $item.getEns() of "ens": result = $item.getEns()
of "colorId": result = $item.getColorId()
of "favourite": result = $item.getFavourite()
of "chainShortNames": result = $item.getChainShortNames() of "chainShortNames": result = $item.getChainShortNames()
of "isTest": result = $item.getIsTest() of "isTest": result = $item.getIsTest()
@ -99,20 +106,13 @@ QtObject:
for item in items: for item in items:
self.itemChanged(item.getAddress()) self.itemChanged(item.getAddress())
proc getNameByAddress*(self: Model, address: string): string = proc getItemByAddress*(self: Model, address: string): Item =
for item in self.items: for item in self.items:
if(cmpIgnoreCase(item.getAddress(), address) == 0): if cmpIgnoreCase(item.getAddress(), address) == 0:
return item.getName() return item
return ""
proc getChainShortNamesForAddress*(self: Model, address: string): string = proc nameExists*(self: Model, name: string): bool =
for item in self.items: for item in self.items:
if(cmpIgnoreCase(item.getAddress(), address) == 0): if item.getName() == name:
return item.getChainShortNames() return true
return "" return false
proc getEnsForAddress*(self: Model, address: string): string =
for item in self.items:
if(cmpIgnoreCase(item.getAddress(), address) == 0):
return item.getEns()
return ""

View File

@ -1,11 +1,11 @@
import NimQml, sugar, sequtils import NimQml, json, sugar, sequtils
import ../io_interface as delegate_interface import ../io_interface as delegate_interface
import app/global/global_singleton import app/global/global_singleton
import app/core/eventemitter import app/core/eventemitter
import app_service/service/saved_address/service as saved_address_service import app_service/service/saved_address/service as saved_address_service
import ./io_interface, ./view, ./controller, ./item import io_interface, view, controller, model
export io_interface export io_interface
@ -36,8 +36,9 @@ method loadSavedAddresses*(self: Module) =
savedAddresses.map(s => initItem( savedAddresses.map(s => initItem(
s.name, s.name,
s.address, s.address,
s.favourite,
s.ens, s.ens,
s.colorId,
s.favourite,
s.chainShortNames, s.chainShortNames,
s.isTest, s.isTest,
)) ))
@ -57,16 +58,33 @@ method viewDidLoad*(self: Module) =
self.moduleLoaded = true self.moduleLoaded = true
self.delegate.savedAddressesModuleDidLoad() self.delegate.savedAddressesModuleDidLoad()
method createOrUpdateSavedAddress*(self: Module, name: string, address: string, favourite: bool, chainShortNames: string, ens: string) = method createOrUpdateSavedAddress*(self: Module, name: string, address: string, ens: string, colorId: string,
self.controller.createOrUpdateSavedAddress(name, address, favourite, chainShortNames, ens) favourite: bool, chainShortNames: string) =
self.controller.createOrUpdateSavedAddress(name, address, ens, colorId, favourite, chainShortNames)
method deleteSavedAddress*(self: Module, address: string, ens: string) = method deleteSavedAddress*(self: Module, address: string, ens: string) =
self.controller.deleteSavedAddress(address, ens) self.controller.deleteSavedAddress(address, ens)
method savedAddressUpdated*(self: Module, address: string, ens: string, errorMsg: string) = method savedAddressUpdated*(self: Module, name: string, address: string, ens: string, errorMsg: string) =
self.loadSavedAddresses() self.loadSavedAddresses()
self.view.savedAddressUpdated(address, ens, errorMsg) self.view.savedAddressUpdated(name, address, ens, errorMsg)
method savedAddressDeleted*(self: Module, address: string, ens: string, errorMsg: string) = method savedAddressDeleted*(self: Module, address: string, ens: string, errorMsg: string) =
self.loadSavedAddresses() self.loadSavedAddresses()
self.view.savedAddressDeleted(address, ens, errorMsg) self.view.savedAddressDeleted(address, ens, errorMsg)
method savedAddressNameExists*(self: Module, name: string): bool =
return self.view.getModel().nameExists(name)
method getSavedAddressAsJson*(self: Module, address: string): string =
let item = self.view.getModel().getItemByAddress(address)
let jsonObj = %* {
"name": item.getName(),
"address": item.getAddress(),
"ens": item.getEns(),
"colorId": item.getColorId(),
"favourite": item.getFavourite(),
"chainShortNames": item.getChainShortNames(),
"isTest": item.getIsTest(),
}
return $jsonObj

View File

@ -1,7 +1,7 @@
import NimQml import NimQml
import ./model, ./item import model
import ./io_interface import io_interface
QtObject: QtObject:
type type
@ -27,31 +27,32 @@ QtObject:
proc modelChanged*(self: View) {.signal.} proc modelChanged*(self: View) {.signal.}
proc getModel(self: View): QVariant {.slot.} = proc getModel*(self: View): Model =
return self.model
proc getModelVariant(self: View): QVariant {.slot.} =
return self.modelVariant return self.modelVariant
QtProperty[QVariant] model: QtProperty[QVariant] model:
read = getModel read = getModelVariant
notify = modelChanged notify = modelChanged
proc setItems*(self: View, items: seq[Item]) = proc setItems*(self: View, items: seq[Item]) =
self.model.setItems(items) self.model.setItems(items)
proc savedAddressUpdated*(self: View, address: string, ens: string, errorMsg: string) {.signal.} proc savedAddressUpdated*(self: View, name: string, address: string, ens: string, errorMsg: string) {.signal.}
proc createOrUpdateSavedAddress*(self: View, name: string, address: string, favourite: bool, chainShortNames: string, ens: string) {.slot.} = proc createOrUpdateSavedAddress*(self: View, name: string, address: string, ens: string, colorId: string,
self.delegate.createOrUpdateSavedAddress(name, address, favourite, chainShortNames, ens) favourite: bool, chainShortNames: string) {.slot.} =
self.delegate.createOrUpdateSavedAddress(name, address, ens, colorId, favourite, chainShortNames)
proc savedAddressDeleted*(self: View, address: string, ens: string, errorMsg: string) {.signal.} proc savedAddressDeleted*(self: View, address: string, ens: string, errorMsg: string) {.signal.}
proc deleteSavedAddress*(self: View, address: string, ens: string) {.slot.} = proc deleteSavedAddress*(self: View, address: string, ens: string) {.slot.} =
self.delegate.deleteSavedAddress(address, ens) self.delegate.deleteSavedAddress(address, ens)
proc getNameByAddress*(self: View, address: string): string {.slot.} = proc savedAddressNameExists*(self: View, name: string): bool {.slot.} =
return self.model.getNameByAddress(address) return self.delegate.savedAddressNameExists(name)
proc getChainShortNamesForAddress*(self: View, address: string): string {.slot.} = proc getSavedAddressAsJson*(self: View, address: string): string {.slot.} =
return self.model.getChainShortNamesForAddress(address) return self.delegate.getSavedAddressAsJson(address)
proc getEnsForAddress*(self: View, address: string): string {.slot.} =
return self.model.getEnsForAddress(address)

View File

@ -7,6 +7,7 @@ type
SavedAddressTaskArg = ref object of QObjectTaskArg SavedAddressTaskArg = ref object of QObjectTaskArg
name: string name: string
address: string address: string
colorId: string
favourite: bool favourite: bool
chainShortNames: string chainShortNames: string
ens: string ens: string
@ -16,14 +17,16 @@ const upsertSavedAddressTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.
let arg = decode[SavedAddressTaskArg](argEncoded) let arg = decode[SavedAddressTaskArg](argEncoded)
var response = %* { var response = %* {
"response": "", "response": "",
"name": %* arg.name,
"address": %* arg.address, "address": %* arg.address,
"ens": %* arg.ens, "ens": %* arg.ens,
"error": "", "error": "",
} }
try: try:
let rpcResponse = backend.upsertSavedAddress(backend.SavedAddress( let rpcResponse = backend.upsertSavedAddress(SavedAddressDto(
name: arg.name, name: arg.name,
address: arg.address, address: arg.address,
colorId: arg.colorId,
favourite: arg.favourite, favourite: arg.favourite,
chainShortNames: arg.chainShortNames, chainShortNames: arg.chainShortNames,
ens: arg.ens, ens: arg.ens,

View File

@ -1,4 +1,4 @@
import json import json, strutils
include ../../common/json_utils include ../../common/json_utils
@ -7,33 +7,19 @@ type
name*: string name*: string
address*: string address*: string
ens*: string ens*: string
colorId*: string
favourite*: bool favourite*: bool
chainShortNames*: string chainShortNames*: string
isTest*: bool isTest*: bool
createdAt*: int64 createdAt*: int64
proc newSavedAddressDto*(
name: string,
address: string,
ens: string,
favourite: bool,
chainShortNames: string,
isTest: bool
): SavedAddressDto =
return SavedAddressDto(
name: name,
address: address,
ens: ens,
favourite: favourite,
chainShortNames: chainShortNames,
isTest: isTest
)
proc toSavedAddressDto*(jsonObj: JsonNode): SavedAddressDto = proc toSavedAddressDto*(jsonObj: JsonNode): SavedAddressDto =
result = SavedAddressDto() result = SavedAddressDto()
discard jsonObj.getProp("name", result.name) discard jsonObj.getProp("name", result.name)
discard jsonObj.getProp("address", result.address) discard jsonObj.getProp("address", result.address)
discard jsonObj.getProp("ens", result.ens) discard jsonObj.getProp("ens", result.ens)
discard jsonObj.getProp("colorId", result.colorId)
result.colorId = result.colorId.toUpper() # to match `preDefinedWalletAccountColors` on the qml side
discard jsonObj.getProp("favourite", result.favourite) discard jsonObj.getProp("favourite", result.favourite)
discard jsonObj.getProp("chainShortNames", result.chainShortNames) discard jsonObj.getProp("chainShortNames", result.chainShortNames)
discard jsonObj.getProp("isTest", result.isTest) discard jsonObj.getProp("isTest", result.isTest)

View File

@ -24,6 +24,7 @@ const SIGNAL_SAVED_ADDRESS_DELETED* = "savedAddressDeleted"
type type
SavedAddressArgs* = ref object of Args SavedAddressArgs* = ref object of Args
name*: string
address*: string address*: string
ens*: string ens*: string
errorMsg*: string errorMsg*: string
@ -83,14 +84,15 @@ QtObject:
proc getSavedAddresses*(self: Service): seq[SavedAddressDto] = proc getSavedAddresses*(self: Service): seq[SavedAddressDto] =
return self.savedAddresses return self.savedAddresses
proc createOrUpdateSavedAddress*(self: Service, name: string, address: string, favourite: bool, chainShortNames: string, proc createOrUpdateSavedAddress*(self: Service, name: string, address: string, ens: string, colorId: string,
ens: string) = favourite: bool, chainShortNames: string) =
let arg = SavedAddressTaskArg( let arg = SavedAddressTaskArg(
name: name, name: name,
address: address, address: address,
ens: ens,
colorId: colorId,
favourite: favourite, favourite: favourite,
chainShortNames: chainShortNames, chainShortNames: chainShortNames,
ens: ens,
isTestAddress: self.settingsService.areTestNetworksEnabled(), isTestAddress: self.settingsService.areTestNetworksEnabled(),
tptr: cast[ByteAddress](upsertSavedAddressTask), tptr: cast[ByteAddress](upsertSavedAddressTask),
vptr: cast[ByteAddress](self.vptr), vptr: cast[ByteAddress](self.vptr),
@ -107,6 +109,7 @@ QtObject:
if rpcResponseObj{"response"}.kind != JNull and rpcResponseObj{"response"}.getStr != "ok": if rpcResponseObj{"response"}.kind != JNull and rpcResponseObj{"response"}.getStr != "ok":
raise newException(CatchableError, "invalid response") raise newException(CatchableError, "invalid response")
arg.name = rpcResponseObj{"name"}.getStr
arg.address = rpcResponseObj{"address"}.getStr arg.address = rpcResponseObj{"address"}.getStr
arg.ens = rpcResponseObj{"ens"}.getStr arg.ens = rpcResponseObj{"ens"}.getStr
except Exception as e: except Exception as e:

View File

@ -1,6 +1,7 @@
import json, json_serialization, strformat import json, json_serialization, strformat
import hashes import hashes
import ./core, ./response_type import ./core, ./response_type
import app_service/service/saved_address/dto as saved_address_dto
from ./gen import rpc from ./gen import rpc
export response_type export response_type
@ -26,14 +27,6 @@ type
address* {.serializedFieldName("address").}: string address* {.serializedFieldName("address").}: string
permissions* {.serializedFieldName("permissions").}: seq[string] permissions* {.serializedFieldName("permissions").}: seq[string]
SavedAddress* = ref object of RootObj
name* {.serializedFieldName("name").}: string
address* {.serializedFieldName("address").}: string
favourite* {.serializedFieldName("favourite").}: bool
chainShortNames* {.serializedFieldName("chainShortNames").}: string
ens* {.serializedFieldName("ens").}: string
isTest* {.serializedFieldName("isTest").}: bool
Network* = ref object of RootObj Network* = ref object of RootObj
chainId* {.serializedFieldName("chainId").}: int chainId* {.serializedFieldName("chainId").}: int
nativeCurrencyDecimals* {.serializedFieldName("nativeCurrencyDecimals").}: int nativeCurrencyDecimals* {.serializedFieldName("nativeCurrencyDecimals").}: int
@ -94,7 +87,7 @@ rpc(fetchChainIDForURL, "wallet"):
url: string url: string
rpc(upsertSavedAddress, "wakuext"): rpc(upsertSavedAddress, "wakuext"):
savedAddress: SavedAddress savedAddress: SavedAddressDto
rpc(deleteSavedAddress, "wakuext"): rpc(deleteSavedAddress, "wakuext"):
address: string address: string

View File

@ -27,7 +27,7 @@ Column {
id: title id: title
width: parent.width width: parent.width
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
font.pixelSize: 13 font.pixelSize: 15
color: Theme.palette.baseColor1 color: Theme.palette.baseColor1
} }

View File

@ -22,6 +22,7 @@ StatusListItem {
property string name property string name
property string address property string address
property string ens property string ens
property string colorId
property string chainShortNames property string chainShortNames
property bool favourite: false property bool favourite: false
property bool areTestNetworksEnabled: false property bool areTestNetworksEnabled: false
@ -88,6 +89,7 @@ StatusListItem {
favourite: root.favourite, favourite: root.favourite,
chainShortNames: root.chainShortNames, chainShortNames: root.chainShortNames,
ens: root.ens, ens: root.ens,
colorId: root.colorId,
} }
); );
} }
@ -115,6 +117,7 @@ StatusListItem {
property bool storeFavourite property bool storeFavourite
property string contactChainShortNames property string contactChainShortNames
property string contactEns property string contactEns
property string colorId
readonly property int maxHeight: 341 readonly property int maxHeight: 341
height: implicitHeight > maxHeight ? maxHeight : implicitHeight height: implicitHeight > maxHeight ? maxHeight : implicitHeight
@ -126,6 +129,7 @@ StatusListItem {
storeFavourite = model.favourite; storeFavourite = model.favourite;
contactChainShortNames = model.chainShortNames; contactChainShortNames = model.chainShortNames;
contactEns = model.ens; contactEns = model.ens;
colorId = model.colorId;
popup(parent, x, y); popup(parent, x, y);
} }
onClosed: { onClosed: {
@ -146,7 +150,8 @@ StatusListItem {
name: editDeleteMenu.contactName, name: editDeleteMenu.contactName,
favourite: editDeleteMenu.storeFavourite, favourite: editDeleteMenu.storeFavourite,
chainShortNames: editDeleteMenu.contactChainShortNames, chainShortNames: editDeleteMenu.contactChainShortNames,
ens: editDeleteMenu.contactEns ens: editDeleteMenu.contactEns,
colorId: editDeleteMenu.colorId
}) })
} }
} }

View File

@ -189,7 +189,7 @@ Rectangle {
StatusNetworkListItemTag { StatusNetworkListItemTag {
id: networkTag id: networkTag
title: model.chainName title: model.shortName
asset.height: root.asset.height asset.height: root.asset.height
asset.width: root.asset.width asset.width: root.asset.width

View File

@ -201,8 +201,14 @@ Column {
Repeater { Repeater {
model: activityFilterStore.savedAddressFilters model: activityFilterStore.savedAddressFilters
delegate: ActivityFilterTagItem { delegate: ActivityFilterTagItem {
tagPrimaryLabel.text: activityFilterStore.getEnsForSavedWalletAddress(modelData) tagPrimaryLabel.text: {
|| activityFilterStore.getChainShortNamesForSavedWalletAddress(modelData) + StatusQUtils.Utils.elideText(modelData,6,4) let savedAddress = root.store.getSavedAddress(modelData)
if (!!savedAddress.ens) {
return savedAddress.ens
}
return savedAddress.chainShortNames + StatusQUtils.Utils.elideText(modelData,6,4)
}
onClosed: activityFilterStore.toggleSavedAddress(modelData) onClosed: activityFilterStore.toggleSavedAddress(modelData)
} }
} }

View File

@ -25,41 +25,80 @@ import ".."
StatusDialog { StatusDialog {
id: root id: root
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
property bool edit: false
property bool addAddress: false
property string address: Constants.zeroAddress // Setting as zero address since we don't have the address yet
property string chainShortNames
property string ens
property alias name: nameInput.text
property bool favourite: false
property var allNetworks property var allNetworks
function applyParams(params = {}) { closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
root.addAddress = params.addAddress?? false
root.address = params.address?? Constants.zeroAddress width: 477
root.ens = params.ens?? "" topPadding: 24 // (16 + 8 for Name, until we add it to the StatusInput component)
root.edit = params.edit?? false bottomPadding: 28
root.name = params.name?? ""
root.favourite = params.favourite?? false header: StatusDialogHeader {
root.chainShortNames = params.chainShortNames?? "" headline.title: d.editMode? qsTr("Edit saved address") : qsTr("Add new saved address")
headline.subtitle: d.editMode? d.name : ""
actions.closeButton.onClicked: root.close()
}
function initWithParams(params = {}) {
d.storedName = params.name?? ""
d.storedColorId = params.colorId?? ""
d.storedChainShortNames = params.chainShortNames?? ""
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.chainShortNames = d.storedChainShortNames
d.favourite = params.favourite?? false
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.ens)
addressInput.setPlainText(d.ens)
else
addressInput.setPlainText("%1%2"
.arg(d.chainShortNames)
.arg(d.address == Constants.zeroAddress? "" : d.address))
nameInput.input.edit.forceActiveFocus(Qt.MouseFocusReason)
} }
QtObject { QtObject {
id: d id: d
readonly property int validationMode: root.edit ?
StatusInput.ValidationMode.Always property bool editMode: false
: StatusInput.ValidationMode.OnlyWhenDirty property bool addAddress: false
readonly property bool valid: addressInput.valid && nameInput.valid 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 chainShortNames: ""
property bool favourite: false
property string storedName: ""
property string storedColorId: ""
property string storedChainShortNames: ""
property bool chainShortNamesDirty: false property bool chainShortNamesDirty: false
readonly property bool dirty: nameInput.input.dirty || chainShortNamesDirty readonly property bool valid: addressInput.valid && nameInput.valid
readonly property bool dirty: nameInput.input.dirty && (!d.editMode || d.storedName !== d.name)
|| chainShortNamesDirty && (!d.editMode || d.storedChainShortNames !== d.chainShortNames)
|| d.colorId.toUpperCase() !== d.storedColorId.toUpperCase()
readonly property var chainPrefixRegexPattern: /[^:]+\:?|:/g readonly property var chainPrefixRegexPattern: /[^:]+\:?|:/g
readonly property string visibleAddress: root.address == Constants.zeroAddress ? "" : root.address readonly property bool addressInputIsENS: !!d.ens
readonly property bool addressInputIsENS: !!root.ens
/// Ensures that the \c root.address and \c root.chainShortNames are not reset when the initial text is set /// Ensures that the \c root.address and \c root.chainShortNames are not reset when the initial text is set
property bool initialized: false property bool initialized: false
@ -69,60 +108,53 @@ StatusDialog {
} }
function resetAddressValues() { function resetAddressValues() {
root.ens = "" d.ens = ""
root.address = Constants.zeroAddress d.address = Constants.zeroAddress
root.chainShortNames = "" d.chainShortNames = ""
allNetworksModelCopy.setEnabledNetworks([]) allNetworksModelCopy.setEnabledNetworks([])
} }
} }
width: 574
height: 490
header: StatusDialogHeader {
headline.title: edit ? qsTr("Edit saved address") : qsTr("Add saved address")
headline.subtitle: edit ? name : ""
actions.closeButton.onClicked: root.close()
}
onOpened: {
d.initialized = true
if(edit || addAddress) {
if (root.ens)
addressInput.setPlainText(root.ens)
else
addressInput.setPlainText(root.chainShortNames + d.visibleAddress)
}
nameInput.input.edit.forceActiveFocus(Qt.MouseFocusReason)
}
Column { Column {
width: parent.width width: parent.width
height: childrenRect.height height: childrenRect.height
topPadding: Style.current.bigPadding
spacing: Style.current.bigPadding spacing: Style.current.xlPadding
StatusInput { StatusInput {
id: nameInput id: nameInput
implicitWidth: parent.width implicitWidth: parent.width
charLimit: 24
input.edit.objectName: "savedAddressNameInput" input.edit.objectName: "savedAddressNameInput"
placeholderText: qsTr("Address name") placeholderText: qsTr("Address name")
label: qsTr("Name") label: qsTr("Name")
validators: [ validators: [
StatusMinLengthValidator { StatusMinLengthValidator {
minLength: 1 minLength: 1
errorMessage: qsTr("Name must not be blank") errorMessage: qsTr("Please name your saved address")
},
StatusValidator {
name: "check-for-no-emojis"
validate: (value) => {
return !Constants.regularExpressions.emoji.test(value)
}
errorMessage: Constants.errorMessages.emojRegExp
}, },
StatusRegularExpressionValidator { StatusRegularExpressionValidator {
regularExpression: /^[^<>]+$/ regularExpression: Constants.regularExpressions.alphanumericalExpanded1
errorMessage: qsTr("This is not a valid account name") errorMessage: Constants.errorMessages.alphanumericalExpanded1RegExp
},
StatusValidator {
name: "check-saved-address-existence"
validate: (value) => {
return !RootStore.savedAddressNameExists(value)
|| d.editMode && d.storedName == value
}
errorMessage: qsTr("Name already in use")
} }
] ]
input.clearable: true input.clearable: true
input.rightPadding: 16 input.rightPadding: 16
validationMode: d.validationMode
} }
StatusInput { StatusInput {
@ -134,7 +166,7 @@ StatusDialog {
placeholderText: qsTr("Ethereum address") placeholderText: qsTr("Ethereum address")
maximumHeight: 66 maximumHeight: 66
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: !(root.edit || root.addAddress) enabled: !(d.editMode || d.addAddress)
validators: [ validators: [
StatusMinLengthValidator { StatusMinLengthValidator {
minLength: 1 minLength: 1
@ -148,10 +180,9 @@ StatusDialog {
} }
} }
] ]
validationMode: d.validationMode
input.edit.textFormat: TextEdit.RichText input.edit.textFormat: TextEdit.RichText
input.asset.name: addressInput.valid && !root.edit ? "checkbox" : "" input.asset.name: addressInput.valid && !d.editMode ? "checkbox" : ""
input.asset.color: enabled ? Theme.palette.primaryColor1 : Theme.palette.baseColor1 input.asset.color: enabled ? Theme.palette.primaryColor1 : Theme.palette.baseColor1
input.rightPadding: 16 input.rightPadding: 16
input.leftIcon: false input.leftIcon: false
@ -185,14 +216,14 @@ StatusDialog {
// Update root values // Update root values
if (Utils.isLikelyEnsName(plainText)) { if (Utils.isLikelyEnsName(plainText)) {
root.ens = plainText d.ens = plainText
root.address = Constants.zeroAddress d.address = Constants.zeroAddress
root.chainShortNames = "" d.chainShortNames = ""
} }
else { else {
root.ens = "" d.ens = ""
root.address = prefixAndAddress.address d.address = prefixAndAddress.address
root.chainShortNames = prefixAndAddress.prefix d.chainShortNames = prefixAndAddress.prefix
let prefixArrWithColumn = d.getPrefixArrayWithColumns(prefixAndAddress.prefix) let prefixArrWithColumn = d.getPrefixArrayWithColumns(prefixAndAddress.prefix)
if (!prefixArrWithColumn) if (!prefixArrWithColumn)
@ -253,10 +284,26 @@ StatusDialog {
} }
} }
StatusColorSelectorGrid {
id: colorSelection
objectName: "addSavedAddressColor"
width: parent.width
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)
}
}
StatusNetworkSelector { StatusNetworkSelector {
id: networkSelector id: networkSelector
objectName: "addSavedAddressNetworkSelector" objectName: "addSavedAddressNetworkSelector"
title: "Network preference" title: "Network preference"
implicitWidth: parent.width
enabled: addressInput.valid && !d.addressInputIsENS enabled: addressInput.valid && !d.addressInputIsENS
defaultItemText: "Add networks" defaultItemText: "Add networks"
defaultItemImageSource: "add" defaultItemImageSource: "add"
@ -278,11 +325,11 @@ StatusDialog {
onCountChanged: { onCountChanged: {
if (!networkSelector.modelUpdateBlocked && d.initialized) { if (!networkSelector.modelUpdateBlocked && d.initialized) {
// Initially source model is empty, filter proxy is also empty, but does // Initially source model is empty, filter proxy is also empty, but does
// extra work and mistakenly overwrites root.chainShortNames property // extra work and mistakenly overwrites d.chainShortNames property
if (sourceModel.count != 0) { if (sourceModel.count != 0) {
const prefixAndAddress = Utils.splitToChainPrefixAndAddress(addressInput.plainText) const prefixAndAddress = Utils.splitToChainPrefixAndAddress(addressInput.plainText)
const syncedPrefix = addressInput.syncChainPrefixWithModel(prefixAndAddress.prefix, this) const syncedPrefix = addressInput.syncChainPrefixWithModel(prefixAndAddress.prefix, this)
root.chainShortNames = syncedPrefix d.chainShortNames = syncedPrefix
addressInput.setPlainText(syncedPrefix + prefixAndAddress.address) addressInput.setPlainText(syncedPrefix + prefixAndAddress.address)
} }
} }
@ -345,10 +392,10 @@ StatusDialog {
footer: StatusDialogFooter { footer: StatusDialogFooter {
rightButtons: ObjectModel { rightButtons: ObjectModel {
StatusButton { StatusButton {
text: root.edit ? qsTr("Save") : qsTr("Add address") text: d.editMode? qsTr("Save") : qsTr("Add address")
enabled: d.valid && d.dirty enabled: d.valid && d.dirty
onClicked: { onClicked: {
RootStore.createOrUpdateSavedAddress(name, address, root.favourite, chainShortNames, ens) RootStore.createOrUpdateSavedAddress(d.name, d.address, d.ens, d.colorId, d.favourite, d.chainShortNames)
root.close() root.close()
} }
objectName: "addSavedAddress" objectName: "addSavedAddress"

View File

@ -52,6 +52,7 @@ StatusMenu {
property string addressName: "" property string addressName: ""
property string addressEns: "" property string addressEns: ""
property string colorId: ""
property string addressChains: "" property string addressChains: ""
property string contractName: "" property string contractName: ""
@ -136,17 +137,19 @@ StatusMenu {
if (isContact) { if (isContact) {
d.addressName = contactData.name d.addressName = contactData.name
} else { } else {
// Revisit here after this issue (resolving source for preferred chains...):
// https://github.com/status-im/status-desktop/issues/13109
d.addressName = WalletStores.RootStore.getNameForWalletAddress(address) d.addressName = WalletStores.RootStore.getNameForWalletAddress(address)
isWalletAccount = d.addressName.length > 0 isWalletAccount = d.addressName.length > 0
if (!isWalletAccount) { if (!isWalletAccount) {
d.addressName = WalletStores.RootStore.getNameForSavedWalletAddress(address) let savedAddress = WalletStores.RootStore.getSavedAddress(address)
d.addressName = savedAddress.name
d.addressEns = savedAddress.ens
d.colorId = savedAddress.colorId
d.addressChains = savedAddress.chainShortNames
} }
} }
d.addressName = contactData.isContact ? contactData.name : WalletStores.RootStore.getNameForAddress(address)
d.addressEns = RootStore.getEnsForSavedWalletAddress(address)
d.addressChains = RootStore.getChainShortNamesForSavedWalletAddress(address)
showOnEtherscanAction.enabled = true showOnEtherscanAction.enabled = true
showOnArbiscanAction.enabled = address.includes(Constants.networkShortChainNames.arbiscan + ":") showOnArbiscanAction.enabled = address.includes(Constants.networkShortChainNames.arbiscan + ":")
showOnOptimismAction.enabled = address.includes(Constants.networkShortChainNames.optimism + ":") showOnOptimismAction.enabled = address.includes(Constants.networkShortChainNames.optimism + ":")
@ -329,6 +332,7 @@ StatusMenu {
name: d.addressName, name: d.addressName,
address: d.selectedAddress, address: d.selectedAddress,
ens: d.addressEns, ens: d.addressEns,
colorId: d.colorId,
chainShortNames: d.addressChains chainShortNames: d.addressChains
}) })
} }

View File

@ -228,14 +228,6 @@ QtObject {
activityController.updateFilter() activityController.updateFilter()
} }
function getChainShortNamesForSavedWalletAddress(address) {
return walletSectionSavedAddresses.getChainShortNamesForAddress(address)
}
function getEnsForSavedWalletAddress(address) {
return walletSectionSavedAddresses.getEnsForAddress(address)
}
property var savedAddressesModel: walletSectionSavedAddresses.model property var savedAddressesModel: walletSectionSavedAddresses.model
property bool areTestNetworksEnabled: networksModule.areTestNetworksEnabled property bool areTestNetworksEnabled: networksModule.areTestNetworksEnabled
property var savedAddressList: SortFilterProxyModel { property var savedAddressList: SortFilterProxyModel {

View File

@ -18,6 +18,7 @@ QtObject {
readonly property bool showAllAccounts: !root.showSavedAddresses && !root.selectedAddress readonly property bool showAllAccounts: !root.showSavedAddresses && !root.selectedAddress
property var lastCreatedSavedAddress property var lastCreatedSavedAddress
property var lastDeletedSavedAddress
property bool addingSavedAddress: false property bool addingSavedAddress: false
property bool deletingSavedAddress: false property bool deletingSavedAddress: false
@ -249,18 +250,37 @@ QtObject {
return globalUtils.hex2Dec(value) return globalUtils.hex2Dec(value)
} }
function getNameForSavedWalletAddress(address) {
return walletSectionSavedAddresses.getNameByAddress(address)
}
function getNameForWalletAddress(address) { function getNameForWalletAddress(address) {
return walletSectionAccounts.getNameByAddress(address) return walletSectionAccounts.getNameByAddress(address)
} }
function getSavedAddress(address) {
const defaultValue = {
name: "",
address: "",
ens: "",
colorId: Constants.walletAccountColors.primary,
favourite: false,
chainShortNames: "",
isTest: false,
}
const jsonObj = root.walletSectionSavedAddressesInst.getSavedAddressAsJson(address)
try {
return JSON.parse(jsonObj)
}
catch (e) {
console.warn("error parsing saved address for address: ", address, " error: ", e.message)
return defaultValue
}
}
function getNameForAddress(address) { function getNameForAddress(address) {
var name = getNameForWalletAddress(address) var name = getNameForWalletAddress(address)
if (name.length === 0) { if (name.length === 0) {
name = getNameForSavedWalletAddress(address) let savedAddress = getSavedAddress(address)
name = savedAddress.name
} }
return name return name
} }
@ -330,9 +350,9 @@ QtObject {
return walletSectionAccounts.getColorByAddress(address) return walletSectionAccounts.getColorByAddress(address)
} }
function createOrUpdateSavedAddress(name, address, favourite, chainShortNames, ens) { function createOrUpdateSavedAddress(name, address, ens, colorId, favourite, chainShortNames) {
root.addingSavedAddress = true root.addingSavedAddress = true
walletSectionSavedAddresses.createOrUpdateSavedAddress(name, address, favourite, chainShortNames, ens) walletSectionSavedAddresses.createOrUpdateSavedAddress(name, address, ens, colorId, favourite, chainShortNames)
} }
function deleteSavedAddress(address, ens) { function deleteSavedAddress(address, ens) {
@ -340,6 +360,10 @@ QtObject {
walletSectionSavedAddresses.deleteSavedAddress(address, ens) walletSectionSavedAddresses.deleteSavedAddress(address, ens)
} }
function savedAddressNameExists(name) {
return walletSectionSavedAddresses.savedAddressNameExists(name)
}
function toggleNetwork(chainId) { function toggleNetwork(chainId) {
networksModule.toggleNetwork(chainId) networksModule.toggleNetwork(chainId)
} }

View File

@ -22,23 +22,11 @@ ColumnLayout {
QtObject { QtObject {
id: d id: d
function saveAddress(name, address, favourite, chainShortNames, ens) {
RootStore.createOrUpdateSavedAddress(name, address, favourite, chainShortNames, ens)
}
function reset() { function reset() {
RootStore.lastCreatedSavedAddress = undefined RootStore.lastCreatedSavedAddress = undefined
} }
} }
SavedAddressesError {
id: error
Layout.alignment: Qt.AlignHCenter
text: RootStore.lastCreatedSavedAddress? RootStore.lastCreatedSavedAddress.error?? "" : ""
visible: !!text
height: visible ? 36 : 0
}
ShapeRectangle { ShapeRectangle {
id: noSavedAddresses id: noSavedAddresses
Layout.fillWidth: true Layout.fillWidth: true
@ -54,7 +42,7 @@ ColumnLayout {
} }
Item { Item {
visible: error.visible || noSavedAddresses.visible || loadingIndicator.visible visible: noSavedAddresses.visible || loadingIndicator.visible
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
} }
@ -77,6 +65,7 @@ ColumnLayout {
address: model.address address: model.address
chainShortNames: model.chainShortNames chainShortNames: model.chainShortNames
ens: model.ens ens: model.ens
colorId: model.colorId
favourite: model.favourite favourite: model.favourite
store: RootStore store: RootStore
contactsStore: root.contactsStore contactsStore: root.contactsStore
@ -84,15 +73,12 @@ ColumnLayout {
isSepoliaEnabled: RootStore.isSepoliaEnabled isSepoliaEnabled: RootStore.isSepoliaEnabled
onOpenSendModal: root.sendModal.open(recipient); onOpenSendModal: root.sendModal.open(recipient);
saveAddress: function(name, address, favourite, chainShortNames, ens) {
d.saveAddress(name, address, favourite, chainShortNames, ens)
}
states: [ states: [
State { State {
name: "highlighted" name: "highlighted"
when: RootStore.lastCreatedSavedAddress ? (RootStore.lastCreatedSavedAddress.address.toLowerCase() === address.toLowerCase() && when: RootStore.lastCreatedSavedAddress ? (!RootStore.lastCreatedSavedAddress.error &&
RootStore.lastCreatedSavedAddress.ens === ens) : false RootStore.lastCreatedSavedAddress.address.toLowerCase() === address.toLowerCase() &&
RootStore.lastCreatedSavedAddress.ens === ens) : false
PropertyChanges { target: savedAddressDelegate; color: Theme.palette.baseColor2 } PropertyChanges { target: savedAddressDelegate; color: Theme.palette.baseColor2 }
StateChangeScript { StateChangeScript {
script: Qt.callLater(d.reset) script: Qt.callLater(d.reset)

View File

@ -1700,7 +1700,7 @@ Item {
} }
onLoaded: { onLoaded: {
addEditSavedAddress.item.applyParams(addEditSavedAddress.params) addEditSavedAddress.item.initWithParams(addEditSavedAddress.params)
addEditSavedAddress.item.open() addEditSavedAddress.item.open()
} }
@ -1715,15 +1715,29 @@ Item {
Connections { Connections {
target: WalletStore.RootStore.walletSectionSavedAddressesInst target: WalletStore.RootStore.walletSectionSavedAddressesInst
function onSavedAddressUpdated(address: string, ens: string, errorMsg: string) { function onSavedAddressUpdated(name: string, address: string, ens: string, errorMsg: string) {
WalletStore.RootStore.addingSavedAddress = false WalletStore.RootStore.addingSavedAddress = false
WalletStore.RootStore.lastCreatedSavedAddress = { address: address, ens: ens, error: errorMsg }
if (!!errorMsg) { if (!!errorMsg) {
WalletStore.RootStore.lastCreatedSavedAddress = { error: errorMsg } Global.displayToastMessage(qsTr("An error occurred while adding %1 addresses").arg(name),
"",
"warning",
false,
Constants.ephemeralNotificationType.danger,
""
)
return return
} }
WalletStore.RootStore.lastCreatedSavedAddress = { address: address, ens: ens } Global.displayToastMessage(qsTr("%1 successfully added to your saved addresses").arg(name),
"",
"checkmark-circle",
false,
Constants.ephemeralNotificationType.success,
""
)
} }
} }
} }
@ -1804,7 +1818,7 @@ Item {
function onSavedAddressDeleted(address: string, ens: string, errorMsg: string) { function onSavedAddressDeleted(address: string, ens: string, errorMsg: string) {
WalletStore.RootStore.deletingSavedAddress = false WalletStore.RootStore.deletingSavedAddress = false
WalletStore.RootStore.lastCreatedSavedAddress = { error: errorMsg } WalletStore.RootStore.lastDeletedSavedAddress = { address: address, ens: ens, error: errorMsg }
} }
} }
} }

View File

@ -90,7 +90,11 @@ Item {
} }
function refreshSavedAddressName() { function refreshSavedAddressName() {
d.savedAddressName = !!root.rootStore ? root.rootStore.getNameForSavedWalletAddress(root.address) : "" if (!root.rootStore) {
return
}
let savedAddress = root.rootStore.getSavedAddress(root.address)
d.savedAddressName = savedAddress.name
} }
function refreshWalletAddress() { function refreshWalletAddress() {

View File

@ -26,7 +26,7 @@ Item {
colorSelection.selectedColorIndex = Math.floor(Math.random() * colorSelection.model.length) colorSelection.selectedColorIndex = Math.floor(Math.random() * colorSelection.model.length)
} }
else { else {
let ind = d.evaluateColorIndex(Utils.getColorForId(root.store.addAccountModule.selectedColorId)) let ind = Utils.getColorIndexForId(root.store.addAccountModule.selectedColorId)
colorSelection.selectedColorIndex = ind colorSelection.selectedColorIndex = ind
} }
@ -48,15 +48,6 @@ Item {
id: d id: d
readonly property bool isEdit: root.store.editMode readonly property bool isEdit: root.store.editMode
function evaluateColorIndex(color) {
for (let i = 0; i < Theme.palette.customisationColorsArray.length; i++) {
if(Theme.palette.customisationColorsArray[i] === color) {
return i
}
}
return 0
}
function openEmojiPopup(showLeft) { function openEmojiPopup(showLeft) {
if (!root.store.emojiPopup) { if (!root.store.emojiPopup) {
return return

View File

@ -179,18 +179,6 @@ QtObject {
return "" return ""
} }
function getNameForSavedWalletAddress(address) {
return walletSectionSavedAddresses.getNameByAddress(address)
}
function getChainShortNamesForSavedWalletAddress(address) {
return walletSectionSavedAddresses.getChainShortNamesForAddress(address)
}
function getEnsForSavedWalletAddress(address) {
return walletSectionSavedAddresses.getEnsForAddress(address)
}
function getCurrencyAmount(amount, symbol) { function getCurrencyAmount(amount, symbol) {
return currencyStore.getCurrencyAmount(amount, symbol) return currencyStore.getCurrencyAmount(amount, symbol)
} }

View File

@ -159,7 +159,19 @@ Control {
} }
delegate: StatusListItem { delegate: StatusListItem {
id: accountDelegate id: accountDelegate
property bool saved: root.walletStore.getNameForSavedWalletAddress(model.address) !== "" property bool saved: {
let savedAddress = root.walletStore.getSavedAddress(model.address)
if (savedAddress.name !== "")
return true
if (!!root.walletStore.lastCreatedSavedAddress) {
if (root.walletStore.lastCreatedSavedAddress.address.toLowerCase() === model.address.toLowerCase()) {
return !!root.walletStore.lastCreatedSavedAddress.error
}
}
return false
}
border.width: 1 border.width: 1
border.color: Theme.palette.baseColor2 border.color: Theme.palette.baseColor2
width: ListView.view.width width: ListView.view.width
@ -184,14 +196,11 @@ Control {
enabled: !accountDelegate.saved enabled: !accountDelegate.saved
text: accountDelegate.saved ? qsTr("Address saved") : qsTr("Save Address") text: accountDelegate.saved ? qsTr("Address saved") : qsTr("Save Address")
onClicked: { onClicked: {
accountDelegate.saved = root.walletStore.createOrUpdateSavedAddress(model.name, model.address, false) === "" // From here, we should just run add saved address popup
Global.displayToastMessage(qsTr("%1 saved to your wallet").arg(accountDelegate.subTitle), Global.openAddEditSavedAddressesPopup({
qsTr("Go to your wallet"), addAddress: true,
"wallet", address: model.address
false, })
Constants.ephemeralNotificationType.normal,
`#${Constants.appSection.wallet}` // internal link to wallet section
)
} }
}, },
StatusFlatRoundButton { StatusFlatRoundButton {

View File

@ -679,18 +679,22 @@ QtObject {
readonly property QtObject regularExpressions: QtObject { readonly property QtObject regularExpressions: QtObject {
readonly property var alphanumerical: /^$|^[a-zA-Z0-9]+$/ readonly property var alphanumerical: /^$|^[a-zA-Z0-9]+$/
readonly property var alphanumericalExpanded: /^$|^[a-zA-Z0-9\-_.\u0020]+$/ readonly property var alphanumericalExpanded: /^$|^[a-zA-Z0-9\-_.\u0020]+$/
readonly property var alphanumericalExpanded1: /^[a-zA-Z0-9\-_]+(?: [a-zA-Z0-9\-_]+)*$/
readonly property var alphanumericalWithSpace: /^$|^[a-zA-Z0-9\s]+$/ readonly property var alphanumericalWithSpace: /^$|^[a-zA-Z0-9\s]+$/
readonly property var asciiPrintable: /^$|^[!-~]+$/ readonly property var asciiPrintable: /^$|^[!-~]+$/
readonly property var ascii: /^$|^[\x00-\x7F]+$/ readonly property var ascii: /^$|^[\x00-\x7F]+$/
readonly property var capitalOnly: /^$|^[A-Z]+$/ readonly property var capitalOnly: /^$|^[A-Z]+$/
readonly property var numerical: /^$|^[0-9]+$/ readonly property var numerical: /^$|^[0-9]+$/
readonly property var emoji: /\ud83c\udff4(\udb40[\udc61-\udc7a])+\udb40\udc7f|(\ud83c[\udde6-\uddff]){2}|([\#\*0-9]\ufe0f?\u20e3)|(\u00a9|\u00ae|[\u203c\u2049\u20e3\u2122\u2139\u2194-\u2199\u21a9\u21aa\u231a\u231b\u2328\u23cf\u23e9-\u23fa\u24c2\u25aa\u25ab\u25b6\u25c0\u25fb-\u25fe\u2600-\u2604\u260e\u2611\u2614\u2615\u2618\u261d\u2620\u2622\u2623\u2626\u262a\u262e\u262f\u2638-\u263a\u2640\u2642\u2648-\u2653\u265f\u2660\u2663\u2665\u2666\u2668\u267b\u267e\u267f\u2692-\u2697\u2699\u269b\u269c\u26a0\u26a1\u26a7\u26aa\u26ab\u26b0\u26b1\u26bd\u26be\u26c4\u26c5\u26c8\u26ce\u26cf\u26d1\u26d3\u26d4\u26e9\u26ea\u26f0-\u26f5\u26f7-\u26fa\u26fd\u2702\u2705\u2708-\u270d\u270f\u2712\u2714\u2716\u271d\u2721\u2728\u2733\u2734\u2744\u2747\u274c\u274e\u2753-\u2755\u2757\u2763\u2764\u2795-\u2797\u27a1\u27b0\u27bf\u2934\u2935\u2b05-\u2b07\u2b1b\u2b1c\u2b50\u2b55\u3030\u303d\u3297\u3299]|\ud83c[\udc04\udccf\udd70\udd71\udd7e\udd7f\udd8e\udd91-\udd9a\udde6-\uddff\ude01\ude02\ude1a\ude2f\ude32-\ude3a\ude50\ude51\udf00-\udf21\udf24-\udf93\udf96\udf97\udf99-\udf9b\udf9e-\udff0\udff3-\udff5\udff7-\udfff]|\ud83d[\udc00-\udcfd\udcff-\udd3d\udd49-\udd4e\udd50-\udd67\udd6f\udd70\udd73-\udd7a\udd87\udd8a-\udd8d\udd90\udd95\udd96\udda4\udda5\udda8\uddb1\uddb2\uddbc\uddc2-\uddc4\uddd1-\uddd3\udddc-\uddde\udde1\udde3\udde8\uddef\uddf3\uddfa-\ude4f\ude80-\udec5\udecb-\uded2\uded5-\uded7\udedc-\udee5\udee9\udeeb\udeec\udef0\udef3-\udefc\udfe0-\udfeb\udff0]|\ud83e[\udd0c-\udd3a\udd3c-\udd45\udd47-\ude7c\ude80-\ude88\ude90-\udebd\udebf-\udec5\udece-\udedb\udee0-\udee8\udef0-\udef8])((\ud83c[\udffb-\udfff])?(\ud83e[\uddb0-\uddb3])?(\ufe0f?\u200d([\u2000-\u3300]|[\ud83c-\ud83e][\ud000-\udfff])\ufe0f?)?)*/g;
} }
readonly property QtObject errorMessages: QtObject { readonly property QtObject errorMessages: QtObject {
readonly property string alphanumericalRegExp: qsTr("Only letters and numbers allowed") readonly property string alphanumericalRegExp: qsTr("Only letters and numbers allowed")
readonly property string alphanumericalExpandedRegExp: qsTr("Only letters, numbers, underscores, whitespaces and hyphens allowed") readonly property string alphanumericalExpandedRegExp: qsTr("Only letters, numbers, underscores, periods, whitespaces and hyphens allowed")
readonly property string alphanumericalExpanded1RegExp: qsTr("Only letters, numbers, underscores, whitespaces and hyphens allowed")
readonly property string alphanumericalWithSpaceRegExp: qsTr("Special characters are not allowed") readonly property string alphanumericalWithSpaceRegExp: qsTr("Special characters are not allowed")
readonly property string asciiRegExp: qsTr("Only letters, numbers and ASCII characters allowed") readonly property string asciiRegExp: qsTr("Only letters, numbers and ASCII characters allowed")
readonly property string emojRegExp: qsTr("Name is too cool (use letters, numbers, underscores, whitespaces and hyphens only)")
} }
readonly property QtObject socialLinkType: QtObject { readonly property QtObject socialLinkType: QtObject {

View File

@ -836,6 +836,16 @@ QtObject {
return Theme.palette.customisationColors.blue return Theme.palette.customisationColors.blue
} }
function getColorIndexForId(colorId) {
let color = getColorForId(colorId)
for (let i = 0; i < Theme.palette.customisationColorsArray.length; i++) {
if(Theme.palette.customisationColorsArray[i] === color) {
return i
}
}
return 0
}
function getPathForDisplay(path) { function getPathForDisplay(path) {
return path.split("/").join(" / ") return path.split("/").join(" / ")
} }

2
vendor/status-go vendored

@ -1 +1 @@
Subproject commit 843bae56597ecf3811d724504bd0216b867979f7 Subproject commit a8357dceacd1b737952660272bf80251df19b8f8