diff --git a/ui/app/AppLayouts/Chat/CommunityComponents/InviteFriendsToCommunityPopup.qml b/ui/app/AppLayouts/Chat/CommunityComponents/InviteFriendsToCommunityPopup.qml index 3c595c2908..77b2ad0c38 100644 --- a/ui/app/AppLayouts/Chat/CommunityComponents/InviteFriendsToCommunityPopup.qml +++ b/ui/app/AppLayouts/Chat/CommunityComponents/InviteFriendsToCommunityPopup.qml @@ -12,26 +12,36 @@ ModalPopup { id: popup property string communityId: chatsModel.communities.activeCommunity.id - property var pubKeys: [] property var goBack onOpened: { - pubKeys = []; - inviteBtn.enabled = false - contactList.membersData.clear(); - // TODO remove friends that are already members - getContactListObject(contactList.membersData) - noContactsRect.visible = !profileModel.contacts.list.hasAddedContacts(); - contactList.visible = !noContactsRect.visible; + contactFieldAndList.chatKey.text = "" + contactFieldAndList.pubKey = "" + contactFieldAndList.pubKeys = [] + contactFieldAndList.ensUsername = "" + contactFieldAndList.chatKey.forceActiveFocus(Qt.MouseFocusReason) + contactFieldAndList.existingContacts.visible = profileModel.contacts.list.hasAddedContacts() + contactFieldAndList.noContactsRect.visible = !contactFieldAndList.existingContacts.visible } //% "Invite friends" title: qsTrId("invite-friends") + height: 630 + + function sendInvites(pubKeys) { + const error = chatsModel.communities.inviteUsersToCommunityById(popup.communityId, JSON.stringify(pubKeys)) + if (error) { + console.error('Error inviting', error) + contactFieldAndList.validationError = error + return + } + contactFieldAndList.successMessage = qsTr("Invite successfully sent") + } + Item { anchors.fill: parent - TextWithLabel { id: shareCommunity anchors.top: parent.top @@ -51,40 +61,18 @@ ModalPopup { anchors.rightMargin: -Style.current.padding } - StyledText { - //% "Contacts" - text: qsTrId("contacts") - anchors.left: parent.left + ContactsListAndSearch { + id: contactFieldAndList anchors.top: sep.bottom anchors.topMargin: Style.current.smallPadding - font.pixelSize: 15 - font.weight: Font.Thin - color: Style.current.secondaryText - } - - NoFriendsRectangle { - id: noContactsRect - anchors.horizontalCenter: parent.horizontalCenter - anchors.verticalCenter: parent.verticalCenter - } - - ContactList { - id: contactList - selectMode: true - anchors.top: sep.bottom - anchors.topMargin: 100 - onItemChecked: function(pubKey, itemChecked) { - var idx = pubKeys.indexOf(pubKey) - if (itemChecked) { - if (idx === -1) { - pubKeys.push(pubKey) - } - } else { - if (idx > -1) { - pubKeys.splice(idx, 1); - } + anchors.bottom: parent.bottom + showCheckbox: true + onUserClicked: function (isContact, pubKey, ensName) { + if (isContact) { + // those are just added to the list to by added by the bunch + return } - inviteBtn.enabled = pubKeys.length > 0 + sendInvites([pubKey]) } } } @@ -111,16 +99,11 @@ ModalPopup { id: inviteBtn anchors.bottom: parent.bottom anchors.right: parent.right + enabled: contactFieldAndList.pubKeys.length > 0 //% "Invite" text: qsTrId("invite-button") onClicked : { - const error = chatsModel.communities.inviteUsersToCommunityById(popup.communityId, JSON.stringify(popup.pubKeys)) - // TODO show error to user also should we show success? - if (error) { - console.error('Error inviting', error) - return - } - popup.close() + sendInvites(contactFieldAndList.pubKeys) } } } diff --git a/ui/app/AppLayouts/Chat/components/PrivateChatPopup.qml b/ui/app/AppLayouts/Chat/components/PrivateChatPopup.qml index 29bf0951f0..cc72a8f8f1 100644 --- a/ui/app/AppLayouts/Chat/components/PrivateChatPopup.qml +++ b/ui/app/AppLayouts/Chat/components/PrivateChatPopup.qml @@ -157,7 +157,7 @@ ModalPopup { anchors.horizontalCenter: parent.horizontalCenter } - PrivateChatPopupExistingContacts { + ExistingContacts { id: existingContacts anchors.topMargin: this.height > 0 ? Style.current.xlPadding : 0 anchors.top: chatKey.bottom @@ -168,7 +168,7 @@ ModalPopup { expanded: !searchResults.loading && popup.pubKey === "" && !searchResults.showProfileNotFoundMessage } - PrivateChatPopupSearchResults { + SearchResults { id: searchResults anchors.top: existingContacts.visible ? existingContacts.bottom : chatKey.bottom anchors.topMargin: Style.current.padding diff --git a/ui/app/AppLayouts/Chat/components/qmldir b/ui/app/AppLayouts/Chat/components/qmldir index bb315bb22e..19f3dadba8 100644 --- a/ui/app/AppLayouts/Chat/components/qmldir +++ b/ui/app/AppLayouts/Chat/components/qmldir @@ -9,6 +9,5 @@ GroupChatPopup 1.0 GroupChatPopup.qml StickerButton 1.0 StickerButton.qml StickerMarket 1.0 StickerMarket.qml StickersPopup 1.0 StickersPopup.qml -InviteFriendsPopup 1.0 InviteFriendsPopup.qml StickerPackIconWithIndicator 1.0 StickerPackIconWithIndicator.qml SuggestedChannels 1.0 SuggestedChannels.qml diff --git a/ui/app/AppLayouts/Profile/Sections/ContactsContainer.qml b/ui/app/AppLayouts/Profile/Sections/ContactsContainer.qml index 3e57bf6d7d..8a3e1099dd 100644 --- a/ui/app/AppLayouts/Profile/Sections/ContactsContainer.qml +++ b/ui/app/AppLayouts/Profile/Sections/ContactsContainer.qml @@ -4,7 +4,6 @@ import QtQuick.Layouts 1.13 import "../../../../imports" import "../../../../shared" import "../../../../shared/status" -import "../../Chat/components" import "./Contacts" Item { @@ -195,7 +194,7 @@ Item { anchors.horizontalCenter: parent.horizontalCenter } - PrivateChatPopupSearchResults { + SearchResults { id: searchResults anchors.top: addContactSearchInput.bottom anchors.topMargin: Style.current.xlPadding diff --git a/ui/nim-status-client.pro b/ui/nim-status-client.pro index 24ce92f7b4..65443dacd2 100644 --- a/ui/nim-status-client.pro +++ b/ui/nim-status-client.pro @@ -364,6 +364,7 @@ DISTFILES += \ shared/AccountSelector.qml \ shared/AddButton.qml \ shared/Address.qml \ + shared/ContactsListAndSearch.qml \ shared/CropCornerRectangle.qml \ shared/DelegateModelGeneralized.qml \ shared/FormGroup.qml \ diff --git a/ui/shared/ContactsListAndSearch.qml b/ui/shared/ContactsListAndSearch.qml new file mode 100644 index 0000000000..2705e6b3f1 --- /dev/null +++ b/ui/shared/ContactsListAndSearch.qml @@ -0,0 +1,183 @@ +import QtQuick 2.13 +import QtQuick.Controls 2.13 +import QtGraphicalEffects 1.13 +import "../imports" +import "../shared/status" + +Item { + property string validationError: "" + property string successMessage: "" + property alias chatKey: chatKey + property alias existingContacts: existingContacts + property alias noContactsRect: noContactsRect + property string pubKey : "" + property string ensUsername : "" + property bool showCheckbox: false + signal userClicked(bool isContact, string pubKey, string ensName) + property var pubKeys: ([]) + + id: root + width: parent.width + + property var resolveENS: Backpressure.debounce(root, 500, function (ensName) { + noContactsRect.visible = false + searchResults.loading = true + searchResults.showProfileNotFoundMessage = false + chatsModel.resolveENS(ensName) + }); + + function validate() { + if (!Utils.isChatKey(chatKey.text) && !Utils.isValidETHNamePrefix(chatKey.text)) { + //% "Enter a valid chat key or ENS username" + validationError = qsTrId("enter-a-valid-chat-key-or-ens-username"); + pubKey = "" + ensUsername = ""; + } else if (profileModel.profile.pubKey === chatKey.text) { + //% "Can't chat with yourself" + validationError = qsTrId("can-t-chat-with-yourself"); + } else { + validationError = "" + } + return validationError === "" + } + + Input { + id: chatKey + //% "Enter ENS username or chat key" + placeholderText: qsTrId("enter-contact-code") + Keys.onReleased: { + searchResults.pubKey = "" + if (!validate()) { + searchResults.showProfileNotFoundMessage = false + noContactsRect.visible = false + return; + } + + chatKey.text = chatKey.text.trim(); + + if (Utils.isChatKey(chatKey.text)){ + pubKey = chatKey.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) + } + textField.anchors.rightMargin: clearBtn.width + Style.current.padding + 2 + + Connections { + target: chatsModel + onEnsWasResolved: { + if(chatKey.text == ""){ + ensUsername.text = ""; + pubKey = ""; + } else if(resolvedPubKey == ""){ + ensUsername.text = ""; + searchResults.pubKey = pubKey = ""; + searchResults.showProfileNotFoundMessage = true + } else { + if (profileModel.profile.pubKey === resolvedPubKey) { + //% "Can't chat with yourself" + validationError = qsTrId("can-t-chat-with-yourself"); + } else { + 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 + } + 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 = pubKey = "" + noContactsRect.visible = false + searchResults.loading = false + } + } + } + + StyledText { + id: message + text: validationError || successMessage + visible: validationError !== "" || successMessage !== "" + font.pixelSize: 13 + color: !!validationError ? Style.current.danger : Style.current.success + anchors.top: chatKey.bottom + anchors.topMargin: Style.current.smallPadding + anchors.horizontalCenter: parent.horizontalCenter + } + + ExistingContacts { + id: existingContacts + anchors.topMargin: this.height > 0 ? Style.current.xlPadding : 0 + anchors.top: chatKey.bottom + showCheckbox: root.showCheckbox + filterText: chatKey.text + pubKeys: root.pubKeys + onContactClicked: function (contact) { + if (!contact || typeof contact === "string") { + return + } + const index = root.pubKeys.indexOf(contact.pubKey) + const pubKeysCopy = Object.assign([], root.pubKeys) + if (index === -1) { + pubKeysCopy.push(contact.pubKey) + } else { + pubKeysCopy.splice(index, 1) + } + root.pubKeys = pubKeysCopy + + userClicked(true, contact.pubKey, profileModel.contacts.addedContacts.userName(contact.pubKey, contact.name)) + } + expanded: !searchResults.loading && pubKey === "" && !searchResults.showProfileNotFoundMessage + } + + SearchResults { + id: searchResults + anchors.top: existingContacts.visible ? existingContacts.bottom : chatKey.bottom + anchors.topMargin: Style.current.padding + hasExistingContacts: existingContacts.visible + loading: false + + onResultClicked: { + if (!validate()) { + return + } + userClicked(false, pubKey, chatKey.text) + } + onAddToContactsButtonClicked: profileModel.contacts.addContact(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/shared/ExistingContacts.qml similarity index 83% rename from ui/app/AppLayouts/Chat/components/PrivateChatPopupExistingContacts.qml rename to ui/shared/ExistingContacts.qml index 819d51c81a..0be64bc2c0 100644 --- a/ui/app/AppLayouts/Chat/components/PrivateChatPopupExistingContacts.qml +++ b/ui/shared/ExistingContacts.qml @@ -1,10 +1,11 @@ import QtQuick 2.13 import QtQuick.Controls 2.13 import QtQuick.Layouts 1.13 -import "../../../../imports" -import "../../../../shared" -import "../../../../shared/status" -import "./" +import "../imports" +import "./status" +// TODO move Contact into shared to get rid of that import +import "../app/AppLayouts/Chat/components" +import "." Item { id: root @@ -12,6 +13,8 @@ Item { anchors.right: parent.right property string filterText: "" property bool expanded: true + property bool showCheckbox: false + property var pubKeys: ([]) signal contactClicked(var contact) function matchesAlias(name, filter) { @@ -33,7 +36,8 @@ Item { id: contactListView model: profileModel.contacts.list delegate: Contact { - showCheckbox: false + showCheckbox: root.showCheckbox + isChecked: root.pubKeys.indexOf(model.pubKey) > -1 pubKey: model.pubKey isContact: model.isContact isUser: false @@ -50,7 +54,6 @@ Item { } } } - } diff --git a/ui/app/AppLayouts/Chat/components/InviteFriendsPopup.qml b/ui/shared/InviteFriendsPopup.qml similarity index 91% rename from ui/app/AppLayouts/Chat/components/InviteFriendsPopup.qml rename to ui/shared/InviteFriendsPopup.qml index de62b8d821..89b1374ed4 100644 --- a/ui/app/AppLayouts/Chat/components/InviteFriendsPopup.qml +++ b/ui/shared/InviteFriendsPopup.qml @@ -1,7 +1,7 @@ import QtQuick 2.12 import QtQuick.Controls 2.3 -import "../../../../imports" -import "../../../../shared" +import "../imports" +import "." ModalPopup { id: popup diff --git a/ui/app/AppLayouts/Chat/components/NoFriendsRectangle.qml b/ui/shared/NoFriendsRectangle.qml similarity index 91% rename from ui/app/AppLayouts/Chat/components/NoFriendsRectangle.qml rename to ui/shared/NoFriendsRectangle.qml index 1e5d7f5808..8572e45ce3 100644 --- a/ui/app/AppLayouts/Chat/components/NoFriendsRectangle.qml +++ b/ui/shared/NoFriendsRectangle.qml @@ -1,7 +1,7 @@ import QtQuick 2.13 -import "../../../../imports" -import "../../../../shared" -import "../../../../shared/status" +import "../imports" +import "." +import "./status" Item { id: noContactsRect diff --git a/ui/app/AppLayouts/Chat/components/PrivateChatPopupSearchResults.qml b/ui/shared/SearchResults.qml similarity index 97% rename from ui/app/AppLayouts/Chat/components/PrivateChatPopupSearchResults.qml rename to ui/shared/SearchResults.qml index 3ba2bab726..148910ddfa 100644 --- a/ui/app/AppLayouts/Chat/components/PrivateChatPopupSearchResults.qml +++ b/ui/shared/SearchResults.qml @@ -1,9 +1,10 @@ import QtQuick 2.13 import QtQuick.Controls 2.13 import QtQuick.Layouts 1.13 -import "../../../../imports" -import "../../../../shared" -import "../../../../shared/status" +import "../imports" +import "./status" +// TODO move Contact into shared to get rid of that import +import "../app/AppLayouts/Chat/components" import "./"