feat(savedaddresses): display various cards if an address user is trying to save is known

Closes: #13280
This commit is contained in:
Sale Djenic 2024-01-26 16:28:49 +01:00 committed by saledjenic
parent aaa9937124
commit bbaafa8954
27 changed files with 488 additions and 181 deletions

View File

@ -251,7 +251,8 @@ method getCommunitySectionModule*(self: AccessInterface, communityId: string): Q
method getAppSearchModule*(self: AccessInterface): QVariant {.base.} =
raise newException(ValueError, "No implementation available")
method getContactDetailsAsJson*(self: AccessInterface, publicKey: string, getVerificationRequest: bool, getOnlineStatus: bool): string {.base.} =
method getContactDetailsAsJson*(self: AccessInterface, publicKey: string, getVerificationRequest: bool = false,
getOnlineStatus: bool = false, includeDetails: bool = false): string {.base.} =
raise newException(ValueError, "No implementation available")
method getOwnerTokenAsJson*(self: AccessInterface, communityId: string): string {.base.} =

View File

@ -1099,40 +1099,55 @@ method onCommunityMuted*[T](
method getVerificationRequestFrom*[T](self: Module[T], publicKey: string): VerificationRequest =
self.controller.getVerificationRequestFrom(publicKey)
method getContactDetailsAsJson*[T](self: Module[T], publicKey: string, getVerificationRequest: bool, getOnlineStatus: bool): string =
let contact = self.controller.getContact(publicKey)
method getContactDetailsAsJson*[T](self: Module[T], publicKey: string, getVerificationRequest: bool = false,
getOnlineStatus: bool = false, includeDetails: bool = false): string =
var contactDetails: ContactDetails
## If includeDetails is true, additional details are calculated, like color hash and that results in higher CPU usage,
## that's why by default it is false and we should set it to true only when we really need it.
if includeDetails:
contactDetails = self.controller.getContactDetails(publicKey)
else:
contactDetails.dto = self.controller.getContact(publicKey)
var requestStatus = 0
if getVerificationRequest:
requestStatus = self.getVerificationRequestFrom(publicKey).status.int
var onlineStatus = OnlineStatus.Inactive
if getOnlineStatus:
onlineStatus = toOnlineStatus(self.controller.getStatusForContactWithId(publicKey).statusType)
let jsonObj = %* {
"displayName": contact.displayName,
"displayIcon": contact.image.thumbnail,
"publicKey": contact.id,
"name": contact.name,
"ensVerified": contact.ensVerified,
"alias": contact.alias,
"lastUpdated": contact.lastUpdated,
"lastUpdatedLocally": contact.lastUpdatedLocally,
"localNickname": contact.localNickname,
"thumbnailImage": contact.image.thumbnail,
"largeImage": contact.image.large,
"isContact": contact.isContact,
"isBlocked": contact.blocked,
"requestReceived": contact.hasAddedUs,
"isAdded": contact.isContactRequestSent,
"isSyncing": contact.isSyncing,
"removed": contact.removed,
"trustStatus": contact.trustStatus.int,
# contact details props
"defaultDisplayName": contactDetails.defaultDisplayName,
"optionalName": contactDetails.optionalName,
"icon": contactDetails.icon,
"isCurrentUser": contactDetails.isCurrentUser,
"colorId": contactDetails.colorId,
"colorHash": contactDetails.colorHash,
# contact dto props
"displayName": contactDetails.dto.displayName,
"publicKey": contactDetails.dto.id,
"name": contactDetails.dto.name,
"ensVerified": contactDetails.dto.ensVerified,
"alias": contactDetails.dto.alias,
"lastUpdated": contactDetails.dto.lastUpdated,
"lastUpdatedLocally": contactDetails.dto.lastUpdatedLocally,
"localNickname": contactDetails.dto.localNickname,
"thumbnailImage": contactDetails.dto.image.thumbnail,
"largeImage": contactDetails.dto.image.large,
"isContact": contactDetails.dto.isContact,
"isBlocked": contactDetails.dto.isBlocked,
"isContactRequestReceived": contactDetails.dto.isContactRequestReceived,
"isContactRequestSent": contactDetails.dto.isContactRequestSent,
"isSyncing": contactDetails.dto.isSyncing,
"removed": contactDetails.dto.removed,
"trustStatus": contactDetails.dto.trustStatus.int,
# TODO rename verificationStatus to outgoingVerificationStatus
"contactRequestState": contact.contactRequestState.int,
"verificationStatus": contact.verificationStatus.int,
"contactRequestState": contactDetails.dto.contactRequestState.int,
"verificationStatus": contactDetails.dto.verificationStatus.int,
"incomingVerificationStatus": requestStatus,
"hasAddedUs": contact.hasAddedUs,
"socialLinks": $contact.socialLinks.toJsonNode(),
"bio": contact.bio,
"socialLinks": $contactDetails.dto.socialLinks.toJsonNode(),
"bio": contactDetails.dto.bio,
"onlineStatus": onlineStatus.int
}
return $jsonObj
@ -1334,7 +1349,7 @@ method displayEphemeralWithActionNotification*[T](self: Module[T], title: string
# TO UNIFY with the one above.
# Further refactor will be done in a next step
method displayEphemeralImageWithActionNotification*[T](self: Module[T], title: string, subTitle: string, image: string, ephNotifType: int,
method displayEphemeralImageWithActionNotification*[T](self: Module[T], title: string, subTitle: string, image: string, ephNotifType: int,
actionType: int, actionData: string, details = NotificationDetails()) =
let now = getTime()
let id = now.toUnix * 1000000000 + now.nanosecond
@ -1345,7 +1360,7 @@ method displayEphemeralImageWithActionNotification*[T](self: Module[T], title: s
finalEphNotifType = EphemeralNotificationType.Danger
let item = ephemeral_notification_item.initItem(id, title, TOAST_MESSAGE_VISIBILITY_DURATION_IN_MS, subTitle, image, "", "", false,
let item = ephemeral_notification_item.initItem(id, title, TOAST_MESSAGE_VISIBILITY_DURATION_IN_MS, subTitle, image, "", "", false,
finalEphNotifType, "", actionType, actionData, details)
self.view.ephemeralNotificationModel().addItem(item)

View File

@ -64,6 +64,10 @@ proc init*(self: Controller) =
let args = ProfileShowcaseForContactArgs(e)
self.delegate.updateProfileShowcase(args.profileShowcase)
self.events.on(SIGNAL_PROFILE_SHOWCASE_ACCOUNTS_BY_ADDRESS_FETCHED) do(e: Args):
let args = ProfileShowcaseForContactArgs(e)
self.delegate.onProfileShowcaseAccountsByAddressFetched(args.profileShowcase.accounts)
self.events.on(SIGNAL_COMMUNITIES_UPDATE) do(e: Args):
let args = CommunitiesArgs(e)
self.delegate.onCommunitiesUpdated(args.communities)
@ -117,5 +121,8 @@ proc requestProfileShowcasePreferences*(self: Controller) =
proc requestProfileShowcaseForContact*(self: Controller, contactId: string) =
self.profileService.requestProfileShowcaseForContact(contactId)
proc fetchProfileShowcaseAccountsByAddress*(self: Controller, address: string) =
self.profileService.fetchProfileShowcaseAccountsByAddress(address)
proc requestCommunityInfo*(self: Controller, communityId: string, shard: Shard) =
self.communityService.requestCommunityInfo(communityId, shard)

View File

@ -66,6 +66,12 @@ method requestProfileShowcasePreferences*(self: AccessInterface) {.base.} =
method requestProfileShowcase*(self: AccessInterface, publicKey: string) {.base.} =
raise newException(ValueError, "No implementation available")
method fetchProfileShowcaseAccountsByAddress*(self: AccessInterface, address: string) {.base.} =
raise newException(ValueError, "No implementation available")
method onProfileShowcaseAccountsByAddressFetched*(self: AccessInterface, accounts: seq[ProfileShowcaseAccount]) {.base.} =
raise newException(ValueError, "No implementation available")
method updateProfileShowcase*(self: AccessInterface, profileShowcase: ProfileShowcaseDto) {.base.} =
raise newException(ValueError, "No implementation available")

View File

@ -1,4 +1,4 @@
import NimQml, chronicles, sequtils, sugar
import NimQml, chronicles, sequtils, sugar, json
import ./io_interface, ./view, ./controller
import ../io_interface as delegate_interface
@ -174,6 +174,13 @@ method requestProfileShowcase*(self: Module, publicKey: string) =
self.controller.requestProfileShowcaseForContact(publicKey)
method fetchProfileShowcaseAccountsByAddress*(self: Module, address: string) =
self.controller.fetchProfileShowcaseAccountsByAddress(address)
method onProfileShowcaseAccountsByAddressFetched*(self: Module, accounts: seq[ProfileShowcaseAccount]) =
let jsonObj = % accounts
self.view.emitProfileShowcaseAccountsByAddressFetchedSignal($jsonObj)
method updateProfileShowcase(self: Module, profileShowcase: ProfileShowcaseDto) =
if self.presentedPublicKey != profileShowcase.contactId:
return

View File

@ -238,3 +238,10 @@ QtObject:
proc updateProfileShowcaseAssets*(self: View, assets: seq[ProfileShowcaseAssetItem]) =
self.profileShowcaseAssetsModel.reset(assets.sorted((a, b) => cmp(a.order, b.order), SortOrder.Ascending))
proc fetchProfileShowcaseAccountsByAddress*(self: View, address: string) {.slot.} =
self.delegate.fetchProfileShowcaseAccountsByAddress(address)
proc profileShowcaseAccountsByAddressFetched*(self: View, accounts: string) {.signal.}
proc emitProfileShowcaseAccountsByAddressFetchedSignal*(self: View, accounts: string) =
self.profileShowcaseAccountsByAddressFetched(accounts)

View File

@ -126,7 +126,7 @@ QtObject:
# TO UNIFY with the one above.
# Further refactor will be done in a next step
proc displayEphemeralImageWithActionNotification*(self: View, title: string, subTitle: string, image: string, ephNotifType: int,
proc displayEphemeralImageWithActionNotification*(self: View, title: string, subTitle: string, image: string, ephNotifType: int,
actionType: int, actionData: string) {.slot.} =
self.delegate.displayEphemeralImageWithActionNotification(title, subTitle, image, ephNotifType, actionType, actionData)
@ -233,8 +233,9 @@ QtObject:
QtProperty[QVariant] appSearchModule:
read = getAppSearchModule
proc getContactDetailsAsJson(self: View, publicKey: string, getVerificationRequest: bool, getOnlineStatus: bool): string {.slot.} =
return self.delegate.getContactDetailsAsJson(publicKey, getVerificationRequest, getOnlineStatus)
proc getContactDetailsAsJson(self: View, publicKey: string, getVerificationRequest: bool, getOnlineStatus: bool,
includeDetails: bool): string {.slot.} =
return self.delegate.getContactDetailsAsJson(publicKey, getVerificationRequest, getOnlineStatus, includeDetails)
proc getOwnerTokenAsJson(self: View, communityId: string): string {.slot.} =
return self.delegate.getOwnerTokenAsJson(communityId)

View File

@ -1,3 +1,5 @@
import json
type
AccessInterface* {.pure inheritable.} = ref object of RootObj
## Abstract class for any input/interaction with this module.
@ -23,6 +25,9 @@ method filterChanged*(self: AccessInterface, addresses: seq[string], chainIds: s
method updateAccount*(self: AccessInterface, address: string, accountName: string, colorId: string, emoji: string) {.base.} =
raise newException(ValueError, "No implementation available")
method getWalletAccountAsJson*(self: AccessInterface, address: string): JsonNode {.base.} =
raise newException(ValueError, "No implementation available")
# View Delegate Interface
# Delegate for the view must be declared here due to use of QtObject and multi
# inheritance, which is not well supported in Nim.

View File

@ -1,13 +1,13 @@
import NimQml, sequtils, sugar
import NimQml, json, sequtils, sugar
import ./io_interface, ./view, ./controller
import ../io_interface as delegate_interface
import ../../../../global/global_singleton
import ../../../../core/eventemitter
import ../../../../../app_service/service/wallet_account/service as wallet_account_service
import ../../../../../app_service/service/network/service as network_service
import ../../../../../app_service/service/currency/service as currency_service
import ../../../shared/wallet_utils
import app/global/global_singleton
import app/core/eventemitter
import app_service/service/wallet_account/service as wallet_account_service
import app_service/service/network/service as network_service
import app_service/service/currency/service as currency_service
import app/modules/shared/wallet_utils
export io_interface
@ -84,3 +84,9 @@ method updateWalletAccountTestPreferredChains*(self: Module, address, preferredC
method updateWatchAccountHiddenFromTotalBalance*(self: Module, address: string, hideFromTotalBalance: bool) =
self.controller.updateWatchAccountHiddenFromTotalBalance(address, hideFromTotalBalance)
method getWalletAccountAsJson*(self: Module, address: string): JsonNode =
let walletAccountDto = self.controller.getWalletAccount(address)
if walletAccountDto.isNil:
return newJNull()
return % walletAccountDto

View File

@ -1,6 +1,6 @@
import NimQml, sequtils, strutils
import NimQml, json, sequtils, strutils
import ../../../../../app_service/service/wallet_account/service as wallet_account_service
import app_service/service/wallet_account/service as wallet_account_service
import ./model
import ./item
@ -66,3 +66,6 @@ QtObject:
proc updateWatchAccountHiddenFromTotalBalance*(self: View, address: string, hideFromTotalBalance: bool) {.slot.} =
self.delegate.updateWatchAccountHiddenFromTotalBalance(address, hideFromTotalBalance)
proc getWalletAccountAsJson*(self: View, address: string): string {.slot.} =
return $self.delegate.getWalletAccountAsJson(address)

View File

@ -36,3 +36,22 @@ const asyncGetProfileShowcaseForContactTask: Task = proc(argEncoded: string) {.g
"publicKey": arg.pubkey,
"error": e.msg,
})
type
FetchProfileShowcaseAccountsTaskArg = ref object of QObjectTaskArg
address: string
const fetchProfileShowcaseAccountsTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
let arg = decode[FetchProfileShowcaseAccountsTaskArg](argEncoded)
var response = %* {
"response": "",
"error": "",
}
try:
let rpcResponse = status_accounts.getProfileShowcaseAccountsByAddress(arg.address)
if not rpcResponse.error.isNil:
raise newException(CatchableError, rpcResponse.error.message)
response["response"] = rpcResponse.result
except Exception as e:
response["error"] = %* e.msg
arg.finish(response)

View File

@ -7,6 +7,7 @@ type ProfileShowcaseCommunity* = ref object of RootObj
order*: int
type ProfileShowcaseAccount* = ref object of RootObj
contactId*: string
address*: string
name*: string
colorId*: string
@ -45,6 +46,7 @@ proc toProfileShowcaseCommunity*(jsonObj: JsonNode): ProfileShowcaseCommunity =
proc toProfileShowcaseAccount*(jsonObj: JsonNode): ProfileShowcaseAccount =
result = ProfileShowcaseAccount()
discard jsonObj.getProp("contactId", result.contactId)
discard jsonObj.getProp("address", result.address)
discard jsonObj.getProp("name", result.name)
discard jsonObj.getProp("colorId", result.colorId)
@ -86,3 +88,12 @@ proc toProfileShowcaseDto*(jsonObj: JsonNode): ProfileShowcaseDto =
result.verifiedTokens.add(jsonMsg.toProfileShowcaseVerifiedToken())
for jsonMsg in jsonObj["unverifiedTokens"]:
result.unverifiedTokens.add(jsonMsg.toProfileShowcaseUnverifiedToken())
proc `%`*(x: ProfileShowcaseAccount): JsonNode =
result = newJobject()
result["contactId"] = % x.contactId
result["address"] = % x.address
result["name"] = % x.name
result["colorId"] = % x.colorId
result["emoji"] = % x.emoji
result["order"] = % x.order

View File

@ -1,4 +1,4 @@
import NimQml, json, chronicles, tables
import NimQml, json, chronicles, tables, sequtils
import ../settings/service as settings_service
import ../../../app/global/global_singleton
@ -28,6 +28,7 @@ type
# Signals which may be emitted by this service:
const SIGNAL_PROFILE_SHOWCASE_PREFERENCES_UPDATED* = "profileShowcasePreferencesUpdated"
const SIGNAL_PROFILE_SHOWCASE_FOR_CONTACT_UPDATED* = "profileShowcaseForContactUpdated"
const SIGNAL_PROFILE_SHOWCASE_ACCOUNTS_BY_ADDRESS_FETCHED* = "profileShowcaseAccountsByAddressFetched"
QtObject:
type Service* = ref object of QObject
@ -128,6 +129,33 @@ QtObject:
except Exception as e:
error "Error requesting profile showcase for a contact", msg = e.msg
proc fetchProfileShowcaseAccountsByAddress*(self: Service, address: string) =
let arg = FetchProfileShowcaseAccountsTaskArg(
address: address,
tptr: cast[ByteAddress](fetchProfileShowcaseAccountsTask),
vptr: cast[ByteAddress](self.vptr),
slot: "onProfileShowcaseAccountsByAddressFetched",
)
self.threadpool.start(arg)
proc onProfileShowcaseAccountsByAddressFetched*(self: Service, rpcResponse: string) {.slot.} =
var data = ProfileShowcaseForContactArgs(
profileShowcase: ProfileShowcaseDto(
accounts: @[],
),
)
try:
let rpcResponseObj = rpcResponse.parseJson
if rpcResponseObj{"error"}.kind != JNull and rpcResponseObj{"error"}.getStr != "":
raise newException(CatchableError, rpcResponseObj{"error"}.getStr)
if rpcResponseObj{"response"}.kind != JArray:
raise newException(CatchableError, "invalid response")
data.profileShowcase.accounts = map(rpcResponseObj{"response"}.getElems(), proc(x: JsonNode): ProfileShowcaseAccount = toProfileShowcaseAccount(x))
except Exception as e:
error "onProfileShowcaseAccountsByAddressFetched", msg = e.msg
self.events.emit(SIGNAL_PROFILE_SHOWCASE_ACCOUNTS_BY_ADDRESS_FETCHED, data)
proc requestProfileShowcasePreferences*(self: Service) =
let arg = QObjectTaskArg(
tptr: cast[ByteAddress](asyncGetProfileShowcasePreferencesTask),

View File

@ -90,6 +90,7 @@ proc `%`*(x: WalletAccountDto): JsonNode =
result["path"] = % x.path
result["colorId"] = % x.colorId
result["publicKey"] = % x.publicKey
result["walletType"] = % x.walletType
result["isWallet"] = % x.isWallet
result["isChat"] = % x.isChat
result["emoji"] = % x.emoji

View File

@ -478,6 +478,10 @@ proc getProfileShowcaseForContact*(contactId: string): RpcResponse[JsonNode] {.r
let payload = %* [contactId]
result = callPrivateRPC("getProfileShowcaseForContact".prefix, payload)
proc getProfileShowcaseAccountsByAddress*(address: string): RpcResponse[JsonNode] {.raises: [Exception].} =
let payload = %* [address]
result = callPrivateRPC("getProfileShowcaseAccountsByAddress".prefix, payload)
proc getProfileShowcasePreferences*(): RpcResponse[JsonNode] {.raises: [Exception].} =
result = callPrivateRPC("getProfileShowcasePreferences".prefix, %*[])

View File

@ -49,7 +49,7 @@ Item {
property var viewAndPostHoldingsModel
readonly property var contactDetails: rootStore ? rootStore.oneToOneChatContact : null
readonly property bool isUserAdded: !!root.contactDetails && root.contactDetails.isAdded
readonly property bool isUserAdded: !!root.contactDetails && root.contactDetails.isContactRequestSent
property bool amISectionAdmin: false
signal openStickerPackPopup(string stickerPackId)

View File

@ -112,7 +112,7 @@ ItemDelegate {
isUntrustworthy: root.contactDetails.trustStatus === Constants.trustStatus.untrustworthy
isAdmin: root.contactDetails.memberRole === Constants.memberRole.owner
status: root.contactDetails.onlineStatus
asset.name: root.contactDetails.displayIcon
asset.name: root.contactDetails.thumbnailImage
asset.isImage: (asset.name !== "")
asset.isLetterIdenticon: (asset.name === "")
asset.color: Utils.colorForPubkey(root.contactId)

View File

@ -6,6 +6,7 @@ import QtQuick.Layouts 1.14
import utils 1.0
import shared.controls 1.0
import shared.panels 1.0
import shared.stores 1.0 as SharedStores
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
@ -62,7 +63,7 @@ StatusModal {
colorSelection.selectedColorIndex = ind
}
if (!!d.ens)
if (d.addressInputIsENS)
addressInput.setPlainText(d.ens)
else
addressInput.setPlainText("%1%2"
@ -72,6 +73,12 @@ StatusModal {
nameInput.input.edit.forceActiveFocus(Qt.MouseFocusReason)
}
enum CardType {
Contact,
WalletAccount,
SavedAddress
}
QtObject {
id: d
@ -90,27 +97,78 @@ StatusModal {
property string storedChainShortNames: ""
property bool chainShortNamesDirty: false
readonly property bool valid: addressInput.valid && nameInput.valid
property bool addressInputValid: d.editMode ||
addressInput.input.dirty &&
d.addressInputIsAddress &&
!d.minAddressLengthRequestError &&
!d.addressAlreadyAddedToWalletError &&
!d.addressAlreadyAddedToSavedAddressesError
readonly property bool valid: d.addressInputValid && nameInput.valid
readonly property bool dirty: nameInput.input.dirty && (!d.editMode || d.storedName !== d.name)
|| chainShortNamesDirty && (!d.editMode || d.storedChainShortNames !== d.chainShortNames)
|| d.colorId.toUpperCase() !== d.storedColorId.toUpperCase()
readonly property var chainPrefixRegexPattern: /[^:]+\:?|:/g
readonly property bool addressInputIsENS: !!d.ens
readonly property bool addressInputIsENS: !!d.ens &&
Utils.isValidEns(d.ens)
readonly property bool addressInputIsAddress: !!d.address &&
d.address != Constants.zeroAddress &&
(Utils.isAddress(d.address) || Utils.isValidAddressWithChainPrefix(d.address))
property ListModel cardsModel: ListModel {}
// possible errors/warnings
readonly property int minAddressLen: 1
property bool minAddressLengthRequestError: false
property bool addressAlreadyAddedToWalletError: false
property bool addressAlreadyAddedToSavedAddressesError: false
property bool checkingContactsAddressInProgress: false
property int contactsWithSameAddress: 0
property bool addressAlreadyAddedToWallet: false
function checkIfAddressIsAlreadyAdddedToWallet(address) {
let name = RootStore.getNameForWalletAddress(address)
d.addressAlreadyAddedToWallet = !!name
let account = RootStore.getWalletAccount(address)
d.cardsModel.clear()
d.addressAlreadyAddedToWalletError = !!account.name
if (!d.addressAlreadyAddedToWalletError) {
return
}
d.cardsModel.append({
type: AddEditSavedAddressPopup.CardType.WalletAccount,
address: account.mixedcaseAddress,
title: account.name,
icon: "",
emoji: account.emoji,
color: Utils.getColorForId(account.colorId).toString().toUpperCase()
})
}
property bool addressAlreadyAddedToSavedAddresses: false
function checkIfAddressIsAlreadyAdddedToSavedAddresses(address) {
let details = RootStore.getSavedAddress(address)
d.addressAlreadyAddedToSavedAddresses = !!details.address
let savedAddress = RootStore.getSavedAddress(address)
d.cardsModel.clear()
d.addressAlreadyAddedToSavedAddressesError = !!savedAddress.address
if (!d.addressAlreadyAddedToSavedAddressesError) {
return
}
d.cardsModel.append({
type: AddEditSavedAddressPopup.CardType.SavedAddress,
address: savedAddress.ens || savedAddress.address,
title: savedAddress.name,
icon: "",
emoji: "",
color: Utils.getColorForId(savedAddress.colorId).toString().toUpperCase()
})
}
property bool resolvingEnsNameInProgress: false
readonly property string uuid: Utils.uuid()
readonly property var validateEnsAsync: Backpressure.debounce(root, 500, function (value) {
var name = value.startsWith("@") ? value.substring(1) : value
mainModule.resolveENS(name, d.uuid)
});
property var profileModuleInst: SharedStores.RootStore.profileSectionModuleInst.profileModule
/// Ensures that the \c root.address and \c root.chainShortNames are not reset when the initial text is set
property bool initialized: false
@ -118,11 +176,81 @@ StatusModal {
return prefixStr.match(d.chainPrefixRegexPattern)
}
function resetAddressValues() {
d.ens = ""
d.address = Constants.zeroAddress
d.chainShortNames = ""
allNetworksModelCopy.setEnabledNetworks([])
function resetAddressValues(fullReset) {
if (fullReset) {
d.ens = ""
d.address = Constants.zeroAddress
d.chainShortNames = ""
allNetworksModelCopy.setEnabledNetworks([])
}
d.cardsModel.clear()
d.resolvingEnsNameInProgress = false
d.checkingContactsAddressInProgress = false
}
function checkForAddressInputOwningErrorsWarnings() {
d.addressAlreadyAddedToWalletError = false
d.addressAlreadyAddedToSavedAddressesError = false
if (d.addressInputIsAddress) {
d.checkIfAddressIsAlreadyAdddedToWallet(d.address)
if (d.addressAlreadyAddedToWalletError) {
addressInput.errorMessageCmp.text = qsTr("You cannot add your own account as a saved address")
addressInput.errorMessageCmp.visible = true
return
}
d.checkIfAddressIsAlreadyAdddedToSavedAddresses(d.address)
if (d.addressAlreadyAddedToSavedAddressesError) {
addressInput.errorMessageCmp.text = qsTr("This address is already saved")
addressInput.errorMessageCmp.visible = true
return
}
d.checkingContactsAddressInProgress = true
d.contactsWithSameAddress = 0
d.profileModuleInst.fetchProfileShowcaseAccountsByAddress(d.address)
return
}
addressInput.errorMessageCmp.text = qsTr("Not registered ens address")
addressInput.errorMessageCmp.visible = true
}
function checkForAddressInputErrorsWarnings() {
addressInput.errorMessageCmp.visible = false
addressInput.errorMessageCmp.color = Theme.palette.dangerColor1
addressInput.errorMessageCmp.text = ""
d.minAddressLengthRequestError = false
if (d.editMode || !addressInput.input.dirty) {
return
}
if (d.addressInputIsENS || d.addressInputIsAddress) {
let value = d.ens || d.address
if (value.trim().length < d.minAddressLen) {
d.minAddressLengthRequestError = true
addressInput.errorMessageCmp.text = qsTr("Please enter an ethereum address")
addressInput.errorMessageCmp.visible = true
return
}
}
if (d.addressInputIsENS) {
d.resolvingEnsNameInProgress = true
d.validateEnsAsync(d.ens)
return
}
if (d.addressInputIsAddress) {
d.checkForAddressInputOwningErrorsWarnings()
return
}
addressInput.errorMessageCmp.text = qsTr("Ethereum address invalid")
addressInput.errorMessageCmp.visible = true
}
function submit(event) {
@ -134,13 +262,56 @@ StatusModal {
RootStore.createOrUpdateSavedAddress(d.name, d.address, d.ens, d.colorId, d.chainShortNames)
root.close()
}
}
property bool resolvingEnsName: false
readonly property string uuid: Utils.uuid()
readonly property var validateEnsAsync: Backpressure.debounce(root, 500, function (value) {
var name = value.startsWith("@") ? value.substring(1) : value
mainModule.resolveENS(name, d.uuid)
});
Connections {
target: mainModule
function onResolvedENS(resolvedPubKey: string, resolvedAddress: string, uuid: string) {
if (uuid !== d.uuid) {
return
}
d.resolvingEnsNameInProgress = false
d.address = resolvedAddress
d.checkForAddressInputOwningErrorsWarnings()
}
}
Connections {
target: d.profileModuleInst
function onProfileShowcaseAccountsByAddressFetched(accounts: string) {
d.cardsModel.clear()
d.checkingContactsAddressInProgress = false
try {
let accountsJson = JSON.parse(accounts)
d.contactsWithSameAddress = accountsJson.length
addressInput.errorMessageCmp.visible = d.contactsWithSameAddress > 0
addressInput.errorMessageCmp.color = Theme.palette.warningColor1
addressInput.errorMessageCmp.text = ""
if (d.contactsWithSameAddress === 1)
addressInput.errorMessageCmp.text = qsTr("This address belongs to a contact")
if (d.contactsWithSameAddress > 1)
addressInput.errorMessageCmp.text = qsTr("This address belongs to the following contacts")
for (let i = 0; i < accountsJson.length; ++i) {
let contact = Utils.getContactDetailsAsJson(accountsJson[i].contactId, true, true, true)
d.cardsModel.append({
type: AddEditSavedAddressPopup.CardType.Contact,
address: accountsJson[i].address,
title: ProfileUtils.displayName(contact.localNickname, contact.name, contact.displayName, contact.alias),
icon: contact.icon,
emoji: "",
color: Utils.colorForColorId(contact.colorId),
onlineStatus: contact.onlineStatus,
colorHash: contact.colorHash
})
}
}
catch (e) {
console.warn("error parsing fetched accounts for contact: ", e.message)
}
}
}
StatusScrollView {
@ -219,96 +390,21 @@ StatusModal {
maximumHeight: 66
input.implicitHeight: Math.min(Math.max(input.edit.contentHeight + topPadding + bottomPadding, minimumHeight), maximumHeight) // setting height instead does not work
enabled: !(d.editMode || d.addAddress)
validators: [
StatusMinLengthValidator {
minLength: 1
errorMessage: qsTr("Please enter an ethereum address")
},
StatusValidator {
errorMessage: d.addressAlreadyAddedToWallet? qsTr("This address is already added to Wallet") :
d.addressAlreadyAddedToSavedAddresses? qsTr("This address is already saved") : qsTr("Ethereum address invalid")
validate: function (value) {
if (value !== Constants.zeroAddress) {
if (Utils.isValidEns(value)) {
return true
}
if (Utils.isValidAddressWithChainPrefix(value)) {
if (d.editMode) {
return true
}
const prefixAndAddress = Utils.splitToChainPrefixAndAddress(value)
d.checkIfAddressIsAlreadyAdddedToWallet(prefixAndAddress.address)
if (d.addressAlreadyAddedToWallet) {
return false
}
d.checkIfAddressIsAlreadyAdddedToSavedAddresses(prefixAndAddress.address)
return !d.addressAlreadyAddedToSavedAddresses
}
}
return false
}
}
]
asyncValidators: [
StatusAsyncValidator {
id: resolvingEnsName
name: "resolving-ens-name"
errorMessage: d.addressAlreadyAddedToWallet? qsTr("This address is already added to Wallet") :
d.addressAlreadyAddedToSavedAddresses? qsTr("This address is already saved") : qsTr("Ethereum address invalid")
asyncOperation: (value) => {
if (!Utils.isValidEns(value)) {
resolvingEnsName.asyncComplete("not-ens")
return
}
d.resolvingEnsName = true
d.validateEnsAsync(value)
}
validate: (value) => {
if (d.editMode || value === "not-ens") {
return true
}
if (!!value) {
d.checkIfAddressIsAlreadyAdddedToWallet(prefixAndAddress.address)
if (d.addressAlreadyAddedToWallet) {
return false
}
d.checkIfAddressIsAlreadyAdddedToSavedAddresses(value)
return !d.addressAlreadyAddedToSavedAddresses
}
return false
}
Connections {
target: mainModule
function onResolvedENS(resolvedPubKey: string, resolvedAddress: string, uuid: string) {
if (uuid !== d.uuid) {
return
}
d.resolvingEnsName = false
d.address = resolvedAddress
resolvingEnsName.asyncComplete(resolvedAddress)
}
}
}
]
input.edit.textFormat: TextEdit.RichText
input.asset.name: addressInput.valid && !d.editMode ? "checkbox" : ""
input.asset.name: d.addressInputValid && !d.editMode ? "checkbox" : ""
input.asset.color: enabled ? Theme.palette.primaryColor1 : Theme.palette.baseColor1
input.rightPadding: 16
input.leftIcon: false
multiline: true
property string plainText: input.edit.getText(0, text.length)
property string plainText: input.edit.getText(0, text.length).trim()
onTextChanged: {
if (skipTextUpdate || !d.initialized)
return
d.addressAlreadyAddedToSavedAddresses = false
plainText = input.edit.getText(0, text.length)
plainText = input.edit.getText(0, text.length).trim()
if (input.edit.previousText != plainText) {
let newText = plainText
@ -322,30 +418,29 @@ StatusModal {
setRichText(newText)
// Reset
if (plainText.length == 0) {
d.resetAddressValues()
return
d.resetAddressValues(plainText.length == 0)
if (plainText.length > 0) {
// Update root values
if (Utils.isLikelyEnsName(plainText)) {
d.ens = plainText
d.address = Constants.zeroAddress
d.chainShortNames = ""
}
else {
d.ens = ""
d.address = prefixAndAddress.address
d.chainShortNames = prefixAndAddress.prefix
let prefixArrWithColumn = d.getPrefixArrayWithColumns(prefixAndAddress.prefix)
if (!prefixArrWithColumn)
prefixArrWithColumn = []
allNetworksModelCopy.setEnabledNetworks(prefixArrWithColumn)
}
}
// Update root values
if (Utils.isLikelyEnsName(plainText)) {
d.resolvingEnsName = true
d.ens = plainText
d.address = Constants.zeroAddress
d.chainShortNames = ""
}
else {
d.resolvingEnsName = false
d.ens = ""
d.address = prefixAndAddress.address
d.chainShortNames = prefixAndAddress.prefix
let prefixArrWithColumn = d.getPrefixArrayWithColumns(prefixAndAddress.prefix)
if (!prefixArrWithColumn)
prefixArrWithColumn = []
allNetworksModelCopy.setEnabledNetworks(prefixArrWithColumn)
}
d.checkForAddressInputErrorsWarnings()
}
}
@ -403,6 +498,51 @@ StatusModal {
}
}
Column {
width: scrollView.availableWidth
visible: d.cardsModel.count > 0
spacing: Style.current.halfPadding
Repeater {
model: d.cardsModel
StatusListItem {
width: d.componentWidth
border.width: 1
border.color: Theme.palette.baseColor2
anchors.horizontalCenter: parent.horizontalCenter
title: model.title
subTitle: model.address
statusListItemSubTitle.font.pixelSize: 12
sensor.hoverEnabled: false
statusListItemIcon.badge.visible: model.type === AddEditSavedAddressPopup.CardType.Contact
statusListItemIcon.badge.color: model.type === AddEditSavedAddressPopup.CardType.Contact && model.onlineStatus === 1?
Theme.palette.successColor1
: Theme.palette.baseColor1
statusListItemIcon.hoverEnabled: false
ringSettings.ringSpecModel: model.type === AddEditSavedAddressPopup.CardType.Contact? model.colorHash : ""
asset {
width: 40
height: 40
name: model.icon
isImage: model.icon !== ""
emoji: model.emoji
color: model.color
isLetterIdenticon: !model.icon
useAcronymForLetterIdenticon: model.type === AddEditSavedAddressPopup.CardType.SavedAddress
charactersLen: {
if (model.type === AddEditSavedAddressPopup.CardType.SavedAddress && model.title.split(" ").length == 1) {
return 1
}
return 2
}
}
}
}
}
StatusColorSelectorGrid {
id: colorSelection
objectName: "addSavedAddressColor"
@ -426,7 +566,7 @@ StatusModal {
implicitWidth: d.componentWidth
anchors.horizontalCenter: parent.horizontalCenter
enabled: addressInput.valid && !d.addressInputIsENS
enabled: d.addressInputValid && !d.addressInputIsENS
defaultItemText: "Add networks"
defaultItemImageSource: "add"
rightButtonVisible: true
@ -515,8 +655,8 @@ StatusModal {
rightButtons: [
StatusButton {
text: d.editMode? qsTr("Save") : qsTr("Add address")
enabled: d.valid && d.dirty && !d.resolvingEnsName
loading: d.resolvingEnsName
enabled: d.valid && d.dirty
loading: d.resolvingEnsNameInProgress || d.checkingContactsAddressInProgress
onClicked: {
d.submit()
}

View File

@ -253,6 +253,44 @@ QtObject {
return walletSectionAccounts.getNameByAddress(address)
}
function getWalletAccount(address) {
const defaultValue = {
name: "",
address: "",
mixedcaseAddress: "",
keyUid: "",
path: "",
colorId: Constants.walletAccountColors.primary,
publicKey: "",
walletType: "",
isWallet: false,
isChat: false,
emoji: "",
ens: "",
assetsLoading: false,
removed: "",
operable: "",
createdAt: -1,
position: -1,
prodPreferredChainIds: "",
testPreferredChainIds: "",
hideFromTotalBalance: false
}
const jsonObj = walletSectionAccounts.getWalletAccountAsJson(address)
try {
if (jsonObj === "null" || jsonObj === undefined) {
return defaultValue
}
return JSON.parse(jsonObj)
}
catch (e) {
console.warn("error parsing wallet account for address: ", address, " error: ", e.message)
return defaultValue
}
}
function getSavedAddress(address) {
const defaultValue = {
name: "",

View File

@ -20,7 +20,7 @@ ActivityNotificationMessage {
contactDetails: notification ? Utils.getContactDetailsAsJson(notification.author, false) : null
messageDetails.messageText: qsTr("Wants to join")
messageDetails.sender.profileImage.name: contactDetails ? contactDetails.displayIcon : ""
messageDetails.sender.profileImage.name: contactDetails ? contactDetails.thumbnailImage : ""
messageDetails.sender.profileImage.assetSettings.isImage: true
messageDetails.sender.profileImage.pubkey: notification ? notification.author : ""
messageDetails.sender.profileImage.colorId: Utils.colorIdForPubkey(notification ? notification.author : "")

View File

@ -46,7 +46,7 @@ ActivityNotificationMessage {
}
ctaComponent: StatusFlatButton {
enabled: root.contactDetails && !root.contactDetails.added && !root.contactDetails.hasAddedUs
enabled: root.contactDetails && !root.contactDetails.added && !root.contactDetails.isContactRequestReceived
size: StatusBaseButton.Size.Small
text: qsTr("Send Contact Request")
onClicked: Global.openContactRequestPopup(root.contactId, null)

View File

@ -35,7 +35,7 @@ ActivityNotificationBase {
sender.profileImage {
width: 40
height: 40
name: contactDetails ? contactDetails.displayIcon : ""
name: contactDetails ? contactDetails.thumbnailImage : ""
pubkey: contactId
colorId: Utils.colorIdForPubkey(contactId)
colorHash: Utils.getColorHashAsJson(contactId, sender.isEnsVerified)

View File

@ -104,7 +104,7 @@ Item {
asset.height: 32
asset.isImage: true
asset.name: (!!selectedContact && !!selectedContact.displayIcon) ? selectedContact.displayIcon : ""
active: !!selectedContact && !!selectedContact.displayIcon
active: !!selectedContact && !!selectedContact.thumbnailImage
}
StatusBaseText {
id: selectedTextField
@ -142,7 +142,7 @@ Item {
StatusSmartIdenticon {
asset.isImage: true
asset.name: currentContact.displayIcon
asset.name: currentContact.thumbnailImage
}
ColumnLayout {
Layout.fillWidth: true

View File

@ -52,13 +52,13 @@ Item {
color: d.isContact ? Utils.colorForPubkey(root.contactPubKey) : d.walletAddressColor
name: {
if (d.isContact) {
return isImage ? d.contactData.displayIcon : nameText.text
return isImage ? d.contactData.thumbnailImage : nameText.text
} else if (d.isWallet && !d.walletAddressEmoji) {
return "filled-account"
}
return ""
}
isImage: d.isContact && statusAssetSettings.isImgSrc(d.contactData.displayIcon)
isImage: d.isContact && statusAssetSettings.isImgSrc(d.contactData.thumbnailImage)
emoji: d.isWallet && !!d.walletAddressEmoji ? d.walletAddressEmoji : ""
isLetterIdenticon: d.isContact && !isImage
charactersLen: 2

View File

@ -43,7 +43,7 @@ Item {
}
function isUserAdded() {
return root.pubKey != "" ? Utils.getContactDetailsAsJson(root.pubKey).isAdded : false
return root.pubKey != "" ? Utils.getContactDetailsAsJson(root.pubKey).isContactRequestSent : false
}
onPubKeyChanged: {

View File

@ -406,10 +406,15 @@ QtObject {
/* Validation section end */
function getContactDetailsAsJson(publicKey, getVerificationRequest=true, getOnlineStatus=false) {
function getContactDetailsAsJson(publicKey, getVerificationRequest=true, getOnlineStatus=false, includeDetails=false) {
const defaultValue = {
defaultDisplayName: "",
optionalName: "",
icon: "",
isCurrentUser: "",
colorId: "",
colorHash: "",
displayName: "",
displayIcon: "",
publicKey: publicKey,
name: "",
ensVerified: false,
@ -420,21 +425,24 @@ QtObject {
thumbnailImage: "",
largeImage: "",
isContact: false,
isAdded: false,
isBlocked: false,
requestReceived: false,
isContactRequestReceived: false,
isContactRequestSent: false,
isSyncing: false,
removed: false,
trustStatus: Constants.trustStatus.unknown,
contactRequestState: Constants.ContactRequestState.None,
verificationStatus: Constants.verificationStatus.unverified,
incomingVerificationStatus: Constants.verificationStatus.unverified,
socialLinks: [],
bio: "",
onlineStatus: Constants.onlineStatus.inactive
}
if (!mainModuleInst || !publicKey)
return defaultValue
const jsonObj = mainModuleInst.getContactDetailsAsJson(publicKey, getVerificationRequest, getOnlineStatus)
const jsonObj = mainModuleInst.getContactDetailsAsJson(publicKey, getVerificationRequest, getOnlineStatus, includeDetails)
try {
return JSON.parse(jsonObj)

2
vendor/status-go vendored

@ -1 +1 @@
Subproject commit 7c9977b780675ab0fa00fb14b10321e4d078b2c4
Subproject commit 6403a7413bd1c560d1beada7b4af1698ddc570d8