diff --git a/src/app/chat/view.nim b/src/app/chat/view.nim
index d32bb19902..d7d9fab1e7 100644
--- a/src/app/chat/view.nim
+++ b/src/app/chat/view.nim
@@ -324,6 +324,9 @@ QtObject:
return
self.messageList[id].clear(not channel.isNil and channel.chatType != ChatType.Profile)
self.messagesCleared()
+
+ proc isAddedContact*(self: ChatsView, id: string): bool {.slot.} =
+ result = self.status.contacts.isAdded(id)
proc pushMessages*(self:ChatsView, messages: var seq[Message]) =
for msg in messages.mitems:
@@ -351,7 +354,7 @@ QtObject:
self.newMessagePushed()
if not channel.muted:
- let isAddedContact = channel.chatType.isOneToOne and self.status.contacts.isAdded(channel.id)
+ let isAddedContact = channel.chatType.isOneToOne and self.isAddedContact(channel.id)
self.messageNotificationPushed(
msg.chatId,
escape_html(msg.text),
@@ -848,4 +851,5 @@ QtObject:
self.status.chat.removeUserFromCommunity(self.activeCommunity.id(), pubKey)
self.activeCommunity.removeMember(pubKey)
except Exception as e:
- error "Error removing user from the community", msg = e.msg
\ No newline at end of file
+ error "Error removing user from the community", msg = e.msg
+
diff --git a/src/app/profile/core.nim b/src/app/profile/core.nim
index bf223ac0d1..3901719487 100644
--- a/src/app/profile/core.nim
+++ b/src/app/profile/core.nim
@@ -94,6 +94,7 @@ proc init*(self: ProfileController, account: Account) =
self.status.events.on("contactAdded") do(e: Args):
let contacts = self.status.contacts.getContacts()
self.view.contacts.setContactList(contacts)
+ self.view.contactsChanged()
self.status.events.on("contactBlocked") do(e: Args):
let contacts = self.status.contacts.getContacts()
diff --git a/src/app/profile/view.nim b/src/app/profile/view.nim
index afa4570caf..264bb88f18 100644
--- a/src/app/profile/view.nim
+++ b/src/app/profile/view.nim
@@ -195,11 +195,14 @@ QtObject:
self.mutedChatsListChanged()
self.mutedContactsListChanged()
+ proc contactsChanged*(self: ProfileView) {.signal.}
+
proc getContacts*(self: ProfileView): QVariant {.slot.} =
newQVariant(self.contacts)
QtProperty[QVariant] contacts:
read = getContacts
+ notify = contactsChanged
proc getDevices*(self: ProfileView): QVariant {.slot.} =
newQVariant(self.devices)
diff --git a/src/app/profile/views/contact_list.nim b/src/app/profile/views/contact_list.nim
index e23f69b5a3..69dc7355f0 100644
--- a/src/app/profile/views/contact_list.nim
+++ b/src/app/profile/views/contact_list.nim
@@ -38,7 +38,7 @@ QtObject:
method rowCount(self: ContactList, index: QModelIndex = nil): int =
return self.contacts.len
- proc userName(self: ContactList, pubKey: string, defaultValue: string = ""): string {.slot.} =
+ proc userName*(self: ContactList, pubKey: string, defaultValue: string = ""): string {.slot.} =
for contact in self.contacts:
if(contact.id != pubKey): continue
return ens.userNameOrAlias(contact)
diff --git a/src/app/utilsView/view.nim b/src/app/utilsView/view.nim
index 3e44401b6b..2368c60c77 100644
--- a/src/app/utilsView/view.nim
+++ b/src/app/utilsView/view.nim
@@ -91,8 +91,11 @@ QtObject:
weiValue = fromHex(Stuint[256], weiValue).toString()
return status_utils.wei2Eth(weiValue, decimals)
+ proc generateAlias*(self: UtilsView, pk: string): string {.slot.} =
+ result = status_accounts.generateAlias(pk)
+
proc generateIdenticon*(self: UtilsView, pk: string): string {.slot.} =
result = status_accounts.generateIdenticon(pk)
proc getNetworkName*(self: UtilsView): string {.slot.} =
- getCurrentNetworkDetails().name
\ No newline at end of file
+ getCurrentNetworkDetails().name
diff --git a/ui/app/AppLayouts/Chat/components/Contact.qml b/ui/app/AppLayouts/Chat/components/Contact.qml
index 4757fe8320..4862733c86 100644
--- a/ui/app/AppLayouts/Chat/components/Contact.qml
+++ b/ui/app/AppLayouts/Chat/components/Contact.qml
@@ -22,6 +22,8 @@ Rectangle {
property bool isHovered: false
property var onItemChecked: (function(pubKey, itemChecked) { console.log(pubKey, itemChecked) })
+ property var onContactClicked
+
id: root
visible: isVisible && (isContact || isUser)
height: visible ? 64 : 0
@@ -85,6 +87,11 @@ Rectangle {
hoverEnabled: root.clickable || root.showCheckbox
onEntered: root.isHovered = true
onExited: root.isHovered = false
- onClicked: assetCheck.clicked()
+ onClicked: {
+ if (typeof root.onContactClicked !== "function") {
+ return assetCheck.clicked()
+ }
+ root.onContactClicked()
+ }
}
}
diff --git a/ui/app/AppLayouts/Chat/components/PrivateChatPopup.qml b/ui/app/AppLayouts/Chat/components/PrivateChatPopup.qml
index 4201e75f09..ac0a4e769e 100644
--- a/ui/app/AppLayouts/Chat/components/PrivateChatPopup.qml
+++ b/ui/app/AppLayouts/Chat/components/PrivateChatPopup.qml
@@ -12,11 +12,10 @@ ModalPopup {
property string pubKey : "";
property string ensUsername : "";
- property bool loading: false;
-
function validate() {
if (!Utils.isChatKey(chatKey.text) && !Utils.isValidETHNamePrefix(chatKey.text)) {
- validationError = "This needs to be a valid chat key or ENS username";
+ validationError = qsTr("Enter a valid chat key or ENS username");
+ pubKey = ""
ensUsername.text = "";
} else if (profileModel.profile.pubKey === chatKey.text) {
validationError = qsTr("Can't chat with yourself");
@@ -27,32 +26,45 @@ ModalPopup {
}
property var resolveENS: Backpressure.debounce(popup, 500, function (ensName){
+ noContactsRect.visible = false
+ searchResults.loading = true
+ searchResults.showProfileNotFoundMessage = false
chatsModel.resolveENS(ensName)
- loading = true
});
function onKeyReleased(){
+ searchResults.pubKey = ""
if (!validate()) {
+ searchResults.showProfileNotFoundMessage = false
+ noContactsRect.visible = false
return;
}
chatKey.text = chatKey.text.trim();
- if(Utils.isChatKey(chatKey.text)){
+ if (Utils.isChatKey(chatKey.text)){
pubKey = chatKey.text;
- ensUsername.text = "";
+ if (!profileModel.contacts.isAdded(pubKey)) {
+ searchResults.username = utilsModel.generateAlias(pubKey)
+ searchResults.userAlias = Utils.compactAddress(pubKey, 4)
+ searchResults.pubKey = pubKey
+ }
+ noContactsRect.visible = false
return;
}
Qt.callLater(resolveENS, chatKey.text)
}
- function doJoin() {
- if (!validate() || pubKey.trim() === "" || validationError !== "") return;
- if(Utils.isChatKey(chatKey.text)){
- chatsModel.joinChat(pubKey, Constants.chatTypeOneToOne);
+ function validateAndJoin(pk, ensName) {
+ if (!validate() || pk.trim() === "" || validationError !== "") return;
+ doJoin(pk, ensName)
+ }
+ function doJoin(pk, ensName) {
+ if(Utils.isChatKey(pk)){
+ chatsModel.joinChat(pk, Constants.chatTypeOneToOne);
} else {
- chatsModel.joinChatWithENS(pubKey, chatKey.text);
+ chatsModel.joinChatWithENS(pk, ensName);
}
popup.close();
@@ -67,107 +79,111 @@ ModalPopup {
pubKey = "";
ensUsername.text = "";
chatKey.forceActiveFocus(Qt.MouseFocusReason)
- noContactsRect.visible = !profileModel.contacts.list.hasAddedContacts()
+ existingContacts.visible = profileModel.contacts.list.hasAddedContacts()
+ noContactsRect.visible = !existingContacts.visible
}
Input {
id: chatKey
//% "Enter ENS username or chat key"
placeholderText: qsTrId("enter-contact-code")
- Keys.onEnterPressed: doJoin()
- Keys.onReturnPressed: doJoin()
- validationError: popup.validationError
+ Keys.onEnterPressed: validateAndJoin(popup.pubKey, chatKey.text)
+ Keys.onReturnPressed: validateAndJoin(popup.pubKey, chatKey.text)
Keys.onReleased: {
onKeyReleased();
}
+ textField.anchors.rightMargin: clearBtn.width + Style.current.padding + 2
Connections {
target: chatsModel
onEnsWasResolved: {
if(chatKey.text == ""){
- ensUsername.text == "";
+ ensUsername.text = "";
pubKey = "";
} else if(resolvedPubKey == ""){
- //% "User not found"
- ensUsername.text = qsTrId("user-not-found");
- pubKey = "";
+ ensUsername.text = "";
+ searchResults.pubKey = pubKey = "";
+ searchResults.showProfileNotFoundMessage = true
} else {
if (profileModel.profile.pubKey === resolvedPubKey) {
- validationError = qsTr("Can't chat with yourself");
+ popup.validationError = qsTr("Can't chat with yourself");
} else {
- ensUsername.text = chatsModel.formatENSUsername(chatKey.text) + " • " + Utils.compactAddress(resolvedPubKey, 4)
- pubKey = resolvedPubKey;
+ searchResults.username = chatsModel.formatENSUsername(chatKey.text)
+ let userAlias = utilsModel.generateAlias(resolvedPubKey)
+ userAlias = userAlias.length > 20 ? userAlias.substring(0, 19) + "..." : userAlias
+ searchResults.userAlias = userAlias + " • " + Utils.compactAddress(resolvedPubKey, 4)
+ searchResults.pubKey = pubKey = resolvedPubKey;
}
+ searchResults.showProfileNotFoundMessage = false
}
- loading = false;
+ searchResults.loading = false;
+ noContactsRect.visible = pubKey === "" && ensUsername.text === "" && !profileModel.contacts.list.hasAddedContacts() && !profileNotFoundMessage.visible
+ }
+ }
+
+ StatusIconButton {
+ id: clearBtn
+ icon.name: "close-icon"
+ type: "secondary"
+ visible: chatKey.text !== ""
+ icon.width: 14
+ icon.height: 14
+ width: 14
+ height: 14
+ anchors.right: parent.right
+ anchors.rightMargin: Style.current.padding
+ anchors.verticalCenter: parent.verticalCenter
+ onClicked: {
+ chatKey.text = ""
+ chatKey.forceActiveFocus(Qt.MouseFocusReason)
+ searchResults.showProfileNotFoundMessage = false
+ searchResults.pubKey = popup.pubKey = ""
+ noContactsRect.visible = false
}
}
}
-
+
StyledText {
- id: ensUsername
+ id: validationErrorMessage
+ text: popup.validationError
+ visible: popup.validationError !== ""
+ font.pixelSize: 13
+ color: Style.current.danger
anchors.top: chatKey.bottom
- anchors.topMargin: Style.current.padding
- color: Style.current.darkGrey
- font.pixelSize: 12
+ anchors.topMargin: Style.current.smallPadding
+ anchors.horizontalCenter: parent.horizontalCenter
}
- Item {
- anchors.top: ensUsername.bottom
- anchors.topMargin: 90
- anchors.fill: parent
-
- ScrollView {
- anchors.fill: parent
- Layout.fillWidth: true
- Layout.fillHeight: true
-
- ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
- ScrollBar.vertical.policy: contactListView.contentHeight > contactListView.height ? ScrollBar.AlwaysOn : ScrollBar.AlwaysOff
-
- ListView {
- anchors.fill: parent
- spacing: 0
- clip: true
- id: contactListView
- model: profileModel.contacts.list
- delegate: Contact {
- showCheckbox: false
- pubKey: model.pubKey
- isContact: model.isContact
- isUser: false
- name: model.name
- address: model.address
- identicon: model.thumbnailImage || model.identicon
- onItemChecked: function(pubKey, itemChecked){
- chatsModel.joinChat(pubKey, Constants.chatTypeOneToOne);
- popup.close()
- }
- visible: model.isContact && (chatKey.text === "" ||
- model.name.toLowerCase().includes(chatKey.text.toLowerCase()) ||
- model.address.toLowerCase().includes(chatKey.text.toLowerCase()))
- }
- }
-
-
- NoFriendsRectangle {
- id: noContactsRect
- visible: profileModel.contacts.addedContacts.rowCount() === 0
- text: qsTr("You don’t have any contacts yet. Invite your friends to start chatting.")
- width: parent.width
- anchors.verticalCenter: parent.verticalCenter
- }
+ PrivateChatPopupExistingContacts {
+ id: existingContacts
+ anchors.topMargin: this.height > 0 ? Style.current.xlPadding : 0
+ anchors.top: chatKey.bottom
+ filterText: chatKey.text
+ onContactClicked: function (contact) {
+ doJoin(contact.pubKey, profileModel.contacts.addedContacts.userName(contact.pubKey, contact.name))
}
+ expanded: !searchResults.loading && popup.pubKey === "" && !searchResults.showProfileNotFoundMessage
}
- footer: StatusButton {
- anchors.right: parent.right
- id: submitBtn
- state: loading ? "pending" : "default"
- text: qsTr("Start chat")
- enabled: pubKey !== ""
- onClicked : doJoin()
+ PrivateChatPopupSearchResults {
+ id: searchResults
+ anchors.top: existingContacts.visible ? existingContacts.bottom : chatKey.bottom
+ anchors.topMargin: Style.current.padding
+ hasExistingContacts: existingContacts.visible
+ loading: false
+
+ onResultClicked: validateAndJoin(popup.pubKey, chatKey.text)
+ onAddToContactsButtonClicked: profileModel.contacts.addContact(popup.pubKey)
}
+
+ NoFriendsRectangle {
+ id: noContactsRect
+ anchors.top: chatKey.bottom
+ anchors.topMargin: Style.current.xlPadding * 3
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.verticalCenter: parent.verticalCenter
+ }
+
}
/*##^##
diff --git a/ui/app/AppLayouts/Chat/components/PrivateChatPopupExistingContacts.qml b/ui/app/AppLayouts/Chat/components/PrivateChatPopupExistingContacts.qml
new file mode 100644
index 0000000000..819d51c81a
--- /dev/null
+++ b/ui/app/AppLayouts/Chat/components/PrivateChatPopupExistingContacts.qml
@@ -0,0 +1,56 @@
+import QtQuick 2.13
+import QtQuick.Controls 2.13
+import QtQuick.Layouts 1.13
+import "../../../../imports"
+import "../../../../shared"
+import "../../../../shared/status"
+import "./"
+
+Item {
+ id: root
+ anchors.left: parent.left
+ anchors.right: parent.right
+ property string filterText: ""
+ property bool expanded: true
+ signal contactClicked(var contact)
+
+ function matchesAlias(name, filter) {
+ let parts = name.split(" ")
+ return parts.some(p => p.startsWith(filter))
+ }
+
+ height: Math.min(contactListView.contentHeight, (expanded ? 320 : 192))
+ ScrollView {
+ anchors.fill: parent
+
+ ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
+ ScrollBar.vertical.policy: contactListView.contentHeight > contactListView.height ? ScrollBar.AlwaysOn : ScrollBar.AlwaysOff
+
+ ListView {
+ anchors.fill: parent
+ spacing: 0
+ clip: true
+ id: contactListView
+ model: profileModel.contacts.list
+ delegate: Contact {
+ showCheckbox: false
+ pubKey: model.pubKey
+ isContact: model.isContact
+ isUser: false
+ name: model.name
+ address: model.address
+ identicon: model.thumbnailImage || model.identicon
+ visible: model.isContact && (root.filterText === "" ||
+ root.matchesAlias(model.name.toLowerCase(), root.filterText.toLowerCase()) ||
+ model.name.toLowerCase().includes(root.filterText.toLowerCase()) ||
+ model.address.toLowerCase().includes(root.filterText.toLowerCase()))
+ onContactClicked: function () {
+ root.contactClicked(model)
+ }
+ }
+ }
+ }
+
+}
+
+
diff --git a/ui/app/AppLayouts/Chat/components/PrivateChatPopupSearchResults.qml b/ui/app/AppLayouts/Chat/components/PrivateChatPopupSearchResults.qml
new file mode 100644
index 0000000000..9c55b110aa
--- /dev/null
+++ b/ui/app/AppLayouts/Chat/components/PrivateChatPopupSearchResults.qml
@@ -0,0 +1,144 @@
+import QtQuick 2.13
+import QtQuick.Controls 2.13
+import QtQuick.Layouts 1.13
+import "../../../../imports"
+import "../../../../shared"
+import "../../../../shared/status"
+import "./"
+
+
+Item {
+ id: root
+ height: 64
+ property bool hasExistingContacts: false
+ property bool showProfileNotFoundMessage: false
+ property bool loading: false
+ property string username: ""
+ property string userAlias: ""
+ property string pubKey: ""
+
+ signal resultClicked(string pubKey)
+ signal addToContactsButtonClicked(string pubKey)
+ width: parent.width
+
+ StyledText {
+ id: nonContactsLabel
+ text: qsTr("Non contacts")
+ anchors.top: parent.top
+ color: Style.current.secondaryText
+ font.pixelSize: 15
+ visible: root.hasExistingContacts && (root.loading || root.pubKey !== "" || root.showProfileNotFoundMessage)
+ }
+
+ Loader {
+ active: root.loading
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.verticalCenter: parent.verticalCenter
+ sourceComponent: Component {
+ LoadingAnimation {
+ width: 18
+ height: 18
+ }
+ }
+ }
+
+ Rectangle {
+ id: foundContact
+ property bool hovered: false
+ anchors.top: nonContactsLabel.visible ? nonContactsLabel.bottom : parent.top
+ color: hovered ? Style.current.backgroundHover : Style.current.background
+ radius: Style.current.radius
+ width: parent.width
+ height: 64
+ visible: root.pubKey !== "" && !root.loading
+
+ StatusImageIdenticon {
+ id: contactIdenticon
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.left: parent.left
+ anchors.leftMargin: Style.current.padding
+ source: utilsModel.generateIdenticon(root.pubKey)
+ }
+
+ StyledText {
+ id: ensUsername
+ font.pixelSize: 17
+ color: Style.current.textColor
+ anchors.top: contactIdenticon.top
+ anchors.left: contactIdenticon.right
+ anchors.leftMargin: Style.current.padding
+ text: root.username
+ }
+
+ StyledText {
+ id: contactAlias
+ font.pixelSize: 15
+ color: Style.current.secondaryText
+ anchors.top: ensUsername.bottom
+ anchors.topMargin: 2
+ anchors.left: ensUsername.left
+ text: root.userAlias
+ }
+
+ MouseArea {
+ cursorShape: Qt.PointingHandCursor
+ anchors.fill: parent
+ hoverEnabled: true
+ onEntered: foundContact.hovered = true
+ onExited: foundContact.hovered = false
+ onClicked: root.resultClicked(root.pubKey)
+ }
+
+ StatusIconButton {
+ id: addContactBtn
+ icon.name: "add-contact"
+ highlightedBackgroundColor: Utils.setColorAlpha(Style.current.buttonHoveredBackgroundColor, 0.2)
+ iconColor: Style.current.primary
+ icon.width: 24
+ icon.height: 24
+ width: 32
+ height: 32
+ anchors.right: parent.right
+ anchors.rightMargin: Style.current.padding
+ anchors.verticalCenter: parent.verticalCenter
+ visible: !chatsModel.isAddedContact(root.pubKey) && !checkIcon.visible
+ MouseArea {
+ anchors.fill: parent
+ hoverEnabled: true
+ cursorShape: Qt.PointingHandCursor
+ onEntered: {
+ foundContact.hovered = true
+ }
+ onExited: {
+ foundContact.hovered = false
+ }
+ onClicked: {
+ root.addToContactsButtonClicked(root.pubKey)
+ mouse.accepted = false
+ }
+ }
+ }
+
+ SVGImage {
+ id: checkIcon
+ source: "../../../../app/img/check-2.svg"
+ width: 19
+ height: 19
+ anchors.right: parent.right
+ anchors.rightMargin: Style.current.smallPadding * 2
+ anchors.verticalCenter: parent.verticalCenter
+ visible: foundContact.hovered && chatsModel.isAddedContact(root.pubKey)
+ }
+ }
+
+ StyledText {
+ id: profileNotFoundMessage
+ color: Style.current.darkGrey
+ visible: root.showProfileNotFoundMessage
+ font.pixelSize: 15
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.verticalCenter: parent.verticalCenter
+ text: qsTr("No profile found")
+ }
+
+}
diff --git a/ui/app/img/add-contact.svg b/ui/app/img/add-contact.svg
new file mode 100644
index 0000000000..7d8fdc6b4a
--- /dev/null
+++ b/ui/app/img/add-contact.svg
@@ -0,0 +1,5 @@
+
diff --git a/ui/app/img/check-2.svg b/ui/app/img/check-2.svg
new file mode 100644
index 0000000000..9c59d0b42b
--- /dev/null
+++ b/ui/app/img/check-2.svg
@@ -0,0 +1,3 @@
+
diff --git a/ui/app/img/close-icon.svg b/ui/app/img/close-icon.svg
new file mode 100644
index 0000000000..849b1f8f07
--- /dev/null
+++ b/ui/app/img/close-icon.svg
@@ -0,0 +1,3 @@
+