diff --git a/src/app/modules/main/profile_section/contacts/controller.nim b/src/app/modules/main/profile_section/contacts/controller.nim index c93b2392fc..3a67028877 100644 --- a/src/app/modules/main/profile_section/contacts/controller.nim +++ b/src/app/modules/main/profile_section/contacts/controller.nim @@ -89,6 +89,10 @@ proc init*(self: Controller) = var args = VerificationRequestArgs(e) self.delegate.onVerificationRequestUpdatedOrAdded(args.verificationRequest) + self.events.on(SIGNAL_CONTACT_INFO_REQUEST_FINISHED) do(e: Args): + let args = ContactInfoRequestArgs(e) + self.delegate.onContactInfoRequestFinished(args.publicKey, args.ok) + proc getContacts*(self: Controller, group: ContactsGroup): seq[ContactsDto] = return self.contactsService.getContactsByGroup(group) @@ -165,3 +169,5 @@ proc getReceivedVerificationRequests*(self: Controller): seq[VerificationRequest proc getStatusForContactWithId*(self: Controller, publicKey: string): StatusUpdateDto = return self.contactsService.getStatusForContactWithId(publicKey) +proc requestContactInfo*(self: Controller, publicKey: string) = + self.contactsService.requestContactInfo(publicKey) \ No newline at end of file diff --git a/src/app/modules/main/profile_section/contacts/io_interface.nim b/src/app/modules/main/profile_section/contacts/io_interface.nim index 5b328e077b..859966270d 100644 --- a/src/app/modules/main/profile_section/contacts/io_interface.nim +++ b/src/app/modules/main/profile_section/contacts/io_interface.nim @@ -123,4 +123,10 @@ method onVerificationRequestCanceled*(self: AccessInterface, publicKey: string) raise newException(ValueError, "No implementation available") method onVerificationRequestUpdatedOrAdded*(self: AccessInterface, VerificationRequest: VerificationRequest) {.base.} = + raise newException(ValueError, "No implementation available") + +method requestContactInfo*(self: AccessInterface, publicKey: string) {.base.} = + raise newException(ValueError, "No implementation available") + +method onContactInfoRequestFinished*(self: AccessInterface, publicKey: string, ok: bool) {.base.} = raise newException(ValueError, "No implementation available") \ No newline at end of file diff --git a/src/app/modules/main/profile_section/contacts/module.nim b/src/app/modules/main/profile_section/contacts/module.nim index bc0c4014d1..98c7c711e8 100644 --- a/src/app/modules/main/profile_section/contacts/module.nim +++ b/src/app/modules/main/profile_section/contacts/module.nim @@ -276,4 +276,10 @@ method onVerificationRequestUpdatedOrAdded*(self: Module, request: VerificationR item.incomingVerificationStatus ) return - self.view.receivedContactRequestsModel.addItem(item) \ No newline at end of file + self.view.receivedContactRequestsModel.addItem(item) + +method requestContactInfo*(self: Module, publicKey: string) = + self.controller.requestContactInfo(publicKey) + +method onContactInfoRequestFinished*(self: Module, publicKey: string, ok: bool) = + self.view.onContactInfoRequestFinished(publicKey, ok) \ No newline at end of file diff --git a/src/app/modules/main/profile_section/contacts/view.nim b/src/app/modules/main/profile_section/contacts/view.nim index 1410d0ab0c..9bea9ff49c 100644 --- a/src/app/modules/main/profile_section/contacts/view.nim +++ b/src/app/modules/main/profile_section/contacts/view.nim @@ -105,6 +105,8 @@ QtObject: read = getSentContactRequestsModel notify = sentContactRequestsModelChanged + proc contactInfoRequestFinished(self: View, publicKey: string, ok: bool) {.signal.} + # Temporary commented until we provide appropriate flags on the `status-go` side to cover all sections. # proc receivedButRejectedContactRequestsModelChanged(self: View) {.signal.} # proc getReceivedButRejectedContactRequestsModel(self: View): QVariant {.slot.} = @@ -186,3 +188,8 @@ QtObject: proc acceptVerificationRequest*(self: View, publicKey: string, response: string) {.slot.} = self.delegate.acceptVerificationRequest(publicKey, response) + proc requestContactInfo*(self: View, publicKey: string) {.slot.} = + self.delegate.requestContactInfo(publicKey) + + proc onContactInfoRequestFinished*(self: View, publicKey: string, ok: bool) {.slot.} = + self.contactInfoRequestFinished(publicKey, ok) \ No newline at end of file diff --git a/src/app_service/service/contacts/async_tasks.nim b/src/app_service/service/contacts/async_tasks.nim index 8ef9ca5457..4d780eb135 100644 --- a/src/app_service/service/contacts/async_tasks.nim +++ b/src/app_service/service/contacts/async_tasks.nim @@ -5,6 +5,8 @@ include ../../common/json_utils from ../../common/conversion import isCompressedPubKey include ../../../app/core/tasks/common +import ../../../backend/contacts as status_go + ################################################# # Async lookup ENS contact ################################################# @@ -39,4 +41,25 @@ const lookupContactTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} = "uuid": arg.uuid, "reason": arg.reason } - arg.finish(output) \ No newline at end of file + arg.finish(output) + +################################################# +# Async request contact info +################################################# + +type + AsyncRequestContactInfoTaskArg = ref object of QObjectTaskArg + pubkey: string + +const asyncRequestContactInfoTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} = + let arg = decode[AsyncRequestContactInfoTaskArg](argEncoded) + try: + let response = status_go.requestContactInfo(arg.pubkey) + arg.finish(%* { + "publicKey": arg.pubkey, + "response": response, + }) + except Exception as e: + arg.finish(%* { + "error": e.msg, + }) \ No newline at end of file diff --git a/src/app_service/service/contacts/service.nim b/src/app_service/service/contacts/service.nim index c8a0f2491d..b539b7fa92 100644 --- a/src/app_service/service/contacts/service.nim +++ b/src/app_service/service/contacts/service.nim @@ -47,6 +47,10 @@ type VerificationRequestArgs* = ref object of Args verificationRequest*: VerificationRequest + ContactInfoRequestArgs* = ref object of Args + publicKey*: string + ok*: bool + # Signals which may be emitted by this service: const SIGNAL_ENS_RESOLVED* = "ensResolved" const SIGNAL_CONTACT_ADDED* = "contactAdded" @@ -69,6 +73,7 @@ const SIGNAL_CONTACT_VERIFICATION_DECLINED* = "contactVerificationRequestDecline const SIGNAL_CONTACT_VERIFICATION_ACCEPTED* = "contactVerificationRequestAccepted" const SIGNAL_CONTACT_VERIFICATION_ADDED* = "contactVerificationRequestAdded" const SIGNAL_CONTACT_VERIFICATION_UPDATED* = "contactVerificationRequestUpdated" +const SIGNAL_CONTACT_INFO_REQUEST_FINISHED* = "contactInfoRequestFinished" type ContactsGroup* {.pure.} = enum @@ -761,3 +766,29 @@ QtObject: self.activityCenterService.parseActivityCenterResponse(response) except Exception as e: error "error declining contact verification request", msg=e.msg + + proc asyncContactInfoLoaded*(self: Service, pubkeyAndRpcResponse: string) {.slot.} = + let rpcResponseObj = pubkeyAndRpcResponse.parseJson + let publicKey = $rpcResponseObj{"publicKey"} + + if (rpcResponseObj{"response"}{"error"}.kind != JNull): + let error = Json.decode($rpcResponseObj["response"]["error"], RpcError) + error "Error requesting contact info", msg = error.message, publicKey + self.events.emit(SIGNAL_CONTACT_INFO_REQUEST_FINISHED, ContactInfoRequestArgs(publicKey: publicKey, ok: false)) + return + + let contact = rpcResponseObj{"response"}{"result"}.toContactsDto() + self.saveContact(contact) + self.events.emit(SIGNAL_CONTACT_INFO_REQUEST_FINISHED, ContactInfoRequestArgs(publicKey: publicKey, ok: true)) + + proc requestContactInfo*(self: Service, pubkey: string) = + try: + let arg = AsyncRequestContactInfoTaskArg( + tptr: cast[ByteAddress](asyncRequestContactInfoTask), + vptr: cast[ByteAddress](self.vptr), + slot: "asyncContactInfoLoaded", + pubkey: pubkey, + ) + self.threadpool.start(arg) + except Exception as e: + error "Error requesting contact info", msg = e.msg, pubkey \ No newline at end of file diff --git a/src/backend/contacts.nim b/src/backend/contacts.nim index 38e71588e0..da74b0e4bd 100644 --- a/src/backend/contacts.nim +++ b/src/backend/contacts.nim @@ -112,3 +112,6 @@ proc retractContactRequest*(pubkey: string): RpcResponse[JsonNode] {.raises: [Ex "id": pubkey }] result = callPrivateRPC("retractContactRequest".prefix, payload) + +proc requestContactInfo*(pubkey: string): RpcResponse[JsonNode] {.raises: [Exception].} = + result = callPrivateRPC("requestContactInfoFromMailserver".prefix, %*[pubkey]) \ No newline at end of file diff --git a/ui/app/AppLayouts/Profile/stores/ContactsStore.qml b/ui/app/AppLayouts/Profile/stores/ContactsStore.qml index d098530242..7ccd68febc 100644 --- a/ui/app/AppLayouts/Profile/stores/ContactsStore.qml +++ b/ui/app/AppLayouts/Profile/stores/ContactsStore.qml @@ -122,4 +122,8 @@ QtObject { function verifiedUntrustworthy(pubKey) { root.contactsModule.verifiedUntrustworthy(pubKey); } + + function requestContactInfo(publicKey) { + root.contactsModule.requestContactInfo(publicKey) + } } diff --git a/ui/app/mainui/Popups.qml b/ui/app/mainui/Popups.qml index f5188f50ca..77e6abfad6 100644 --- a/ui/app/mainui/Popups.qml +++ b/ui/app/mainui/Popups.qml @@ -216,6 +216,7 @@ QtObject { id: sendIDRequestPopupComponent SendContactRequestModal { anchors.centerIn: parent + rootStore: root.rootStore onAccepted: root.rootStore.profileSectionStore.contactsStore.sendVerificationRequest(userPublicKey, message) onClosed: destroy() } @@ -237,6 +238,7 @@ QtObject { SendContactRequestModal { anchors.centerIn: parent + rootStore: root.rootStore onAccepted: root.rootStore.profileSectionStore.contactsStore.sendContactRequest(userPublicKey, message) onClosed: destroy() } diff --git a/ui/imports/shared/controls/chat/ProfileHeader.qml b/ui/imports/shared/controls/chat/ProfileHeader.qml index 5227d08fbc..d30b80862e 100644 --- a/ui/imports/shared/controls/chat/ProfileHeader.qml +++ b/ui/imports/shared/controls/chat/ProfileHeader.qml @@ -38,6 +38,7 @@ Item { property bool emojiHashVisible: true property bool editImageButtonVisible: false property bool editButtonVisible: displayNamePlusIconsVisible + property bool loading: false readonly property bool compact: root.imageSize === ProfileHeader.ImageSize.Compact signal clicked() @@ -116,6 +117,7 @@ Item { imageWidth: d.getSize(36, 64, 170) imageHeight: imageWidth ensVerified: root.userIsEnsVerified + loading: root.loading } StatusRoundButton { @@ -150,17 +152,36 @@ Item { } } - StyledText { + Item { Layout.fillWidth: true - visible: root.displayNameVisible - text: root.displayName - horizontalAlignment: Text.AlignHCenter - elide: Text.ElideRight - maximumLineCount: 3 - wrapMode: Text.Wrap - font { - bold: true - pixelSize: 17 + implicitHeight: displayNameLabel.implicitHeight + + StyledText { + id: displayNameLabel + width: parent.width + height: parent.height + text: root.displayName + visible: !root.loading + horizontalAlignment: Text.AlignHCenter + elide: Text.ElideRight + maximumLineCount: 3 + wrapMode: Text.Wrap + font { + bold: true + pixelSize: 17 + } + } + + Loader { + anchors.centerIn: parent + height: parent.height + width: 100 + visible: root.loading + active: visible + + sourceComponent: LoadingComponent { + radius: 4 + } } } @@ -169,6 +190,7 @@ Item { Layout.fillWidth: true Layout.alignment: Qt.AlignHCenter visible: root.displayNamePlusIconsVisible + StyledText { objectName: "ProfileHeader_displayName" Layout.maximumWidth: root.width - Style.current.xlPadding diff --git a/ui/imports/shared/controls/chat/UserImage.qml b/ui/imports/shared/controls/chat/UserImage.qml index 2be09f3a1e..7eefb339ac 100644 --- a/ui/imports/shared/controls/chat/UserImage.qml +++ b/ui/imports/shared/controls/chat/UserImage.qml @@ -20,6 +20,7 @@ Loader { property bool interactive: true property bool disabled: false property bool ensVerified: false + property bool loading: false property int colorId: Utils.colorIdForPubkey(pubkey) property var colorHash: Utils.getColorHashAsJson(pubkey, ensVerified) @@ -39,6 +40,7 @@ Loader { ringSettings { ringSpecModel: root.showRing ? root.colorHash : undefined } + loading: root.loading Loader { anchors.fill: parent diff --git a/ui/imports/shared/popups/SendContactRequestModal.qml b/ui/imports/shared/popups/SendContactRequestModal.qml index 5b30ceb710..8187d968c9 100644 --- a/ui/imports/shared/popups/SendContactRequestModal.qml +++ b/ui/imports/shared/popups/SendContactRequestModal.qml @@ -14,6 +14,8 @@ import StatusQ.Popups 0.1 StatusModal { id: root + property var rootStore + property string userPublicKey: "" property string userDisplayName: "" property string userIcon: "" @@ -27,7 +29,22 @@ StatusModal { width: 480 height: 548 - header.title: qsTr("Send Contact Request to %1").arg(userDisplayName) + header.title: d.loadingContactDetails ? qsTr("Send Contact Request") + : qsTr("Send Contact Request to %1").arg(d.userDisplayName) + + onAboutToShow: { + messageInput.input.edit.forceActiveFocus() + + const contactDetails = Utils.getContactDetailsAsJson(userPublicKey, false) + + if (contactDetails.displayName !== "") { + d.updateContactDetails(contactDetails) + return + } + + root.rootStore.contactStore.requestContactInfo(root.userPublicKey) + d.loadingContactDetails = true + } QtObject { id: d @@ -36,10 +53,30 @@ StatusModal { readonly property int minMsgLength: 1 readonly property int msgHeight: 152 readonly property int contentSpacing: Style.current.halfPadding + + property bool loadingContactDetails: false + + property string userDisplayName: "" + property string userIcon: "" + property bool userIsEnsVerified + + function updateContactDetails(contactDetails) { + d.userDisplayName = contactDetails.displayName + d.userIcon = contactDetails.largeImage + d.userIsEnsVerified = contactDetails.ensVerified + } } - onAboutToShow: { - messageInput.input.edit.forceActiveFocus() + Connections { + target: root.rootStore.contactStore.contactsModule + + function onContactInfoRequestFinished(publicKey, ok) { + if (ok) { + const details = Utils.getContactDetailsAsJson(userPublicKey, false) + d.updateContactDetails(details) + } + d.loadingContactDetails = false + } } ColumnLayout { @@ -53,14 +90,15 @@ StatusModal { ProfileHeader { Layout.fillWidth: true - displayName: root.userDisplayName + displayName: d.userDisplayName pubkey: root.userPublicKey - icon: root.userIcon - userIsEnsVerified: root.userIsEnsVerified + icon: d.userIcon + userIsEnsVerified: d.userIsEnsVerified displayNameVisible: true pubkeyVisible: true imageSize: ProfileHeader.ImageSize.Middle + loading: d.loadingContactDetails } StatusInput { @@ -78,8 +116,6 @@ StatusModal { errorMessage: Utils.getErrorMessage(messageInput.errors, qsTr("who are you")) } } - - Item { Layout.fillHeight: true } } rightButtons: StatusButton { diff --git a/vendor/status-go b/vendor/status-go index db6c05a8e8..fb36298e37 160000 --- a/vendor/status-go +++ b/vendor/status-go @@ -1 +1 @@ -Subproject commit db6c05a8e8645868c45b05664b3397a262f9c07c +Subproject commit fb36298e37ae2cfc7fa4d4c3abd61f313ad142ce