diff --git a/storybook/main.qml b/storybook/main.qml index afdaefa847..38a2202db1 100644 --- a/storybook/main.qml +++ b/storybook/main.qml @@ -68,6 +68,9 @@ ApplicationWindow { ListElement { title: "ProfileFetchingView" } + ListElement { + title: "MembersSelector" + } } SplitView { diff --git a/storybook/pages/MembersSelectorModelEditor.qml b/storybook/pages/MembersSelectorModelEditor.qml new file mode 100644 index 0000000000..62dea1bdb3 --- /dev/null +++ b/storybook/pages/MembersSelectorModelEditor.qml @@ -0,0 +1,120 @@ +import QtQuick 2.14 +import QtQuick.Controls 2.14 +import QtQuick.Layouts 1.14 + +Item { + id: root + + property alias model: listView.model + + implicitWidth: layout.implicitWidth + implicitHeight: layout.implicitHeight + + signal removeClicked(int index) + signal removeAllClicked + signal addClicked + + ColumnLayout { + id: layout + + anchors.fill: parent + + ListView { + id: listView + + Layout.fillWidth: true + Layout.fillHeight: true + + clip: true + spacing: 32 + + delegate: ColumnLayout { + id: delegate + + spacing: 0 + width: ListView.view.width + + Row { + Label { + width: delegate.width / 2 + anchors.verticalCenter: parent.verticalCenter + text: "displayName:\t" + } + TextField { + width: delegate.width / 2 + text: model.displayName + onTextChanged: model.displayName = text + } + } + Row { + Label { + width: delegate.width / 2 + anchors.verticalCenter: parent.verticalCenter + text: "localNickname:\t" + } + TextField { + width: delegate.width / 2 + text: model.localNickname + onTextChanged: model.localNickname = text + } + } + Row { + Label { + width: delegate.width / 2 + anchors.verticalCenter: parent.verticalCenter + text: "isVerified:\t" + } + Switch { + width: delegate.width / 2 + checked: model.isVerified + onCheckedChanged: model.isVerified = checked + } + } + Row { + Label { + width: delegate.width / 2 + anchors.verticalCenter: parent.verticalCenter + text: "isUntrustworthy:\t" + } + Switch { + width: delegate.width / 2 + checked: model.isUntrustworthy + onCheckedChanged: model.isUntrustworthy = checked + } + } + Row { + Label { + width: delegate.width / 2 + anchors.verticalCenter: parent.verticalCenter + text: "onlineStatus:\t" + } + SpinBox { + width: delegate.width / 2 + from: 0 + to: 1 + value: model.onlineStatus + onValueChanged: model.onlineStatus = value + } + } + Button { + text: "remove" + onClicked: root.removeClicked(index) + } + } + + ScrollBar.vertical: ScrollBar {} + } + + Button { + Layout.fillWidth: true + text: "remove all" + onClicked: root.removeAllClicked() + } + + Button { + Layout.fillWidth: true + text: "add" + onClicked: root.addClicked() + } + } +} diff --git a/storybook/pages/MembersSelectorPage.qml b/storybook/pages/MembersSelectorPage.qml new file mode 100644 index 0000000000..422fb3602b --- /dev/null +++ b/storybook/pages/MembersSelectorPage.qml @@ -0,0 +1,267 @@ +import QtQuick 2.14 +import QtQuick.Controls 2.14 +import QtQuick.Layouts 1.14 + +import AppLayouts.Chat.views 1.0 + +import Storybook 1.0 +import utils 1.0 + +SplitView { + id: root + + Logs { id: logs } + + property bool globalUtilsReady: false + property bool mainModuleReady: false + + QtObject { + function isCompressedPubKey(publicKey) { + return true + } + + function getCompressedPk(publicKey) { + return "123456789" + } + + function getColorHashAsJson(publicKey) { + return JSON.stringify([{colorId: 0, segmentLength: 1}, + {colorId: 19, segmentLength: 2}]) + } + + function getColorId(publicKey) { + return Math.floor(Math.random() * 10) + } + + function isEnsVerified(publicKey) { + return false + } + + Component.onCompleted: { + Utils.globalUtilsInst = this + root.globalUtilsReady = true + } + + Component.onDestruction: { + root.globalUtilsReady = false + Utils.globalUtilsInst = {} + } + } + + QtObject { + function getContactDetailsAsJson() { + return JSON.stringify({ ensVerified: false }) + } + + Component.onCompleted: { + Utils.mainModuleInst = this + root.mainModuleReady = true + } + Component.onDestruction: { + root.mainModuleReady = false + Utils.mainModuleInst = {} + } + } + + QtObject { + id: rootStoreMock + + readonly property var contactsModel: ListModel { + id: contactsModel + + Component.onCompleted: { + for(let i=0; i < 20; i++) { + append(d.createUserDict(i)) + } + } + } + + readonly property var contactsStore: QtObject { + readonly property var mainModuleInst: null + } + + function amIChatAdmin() { + return chatAdminSwitch.checked + } + } + + QtObject { + id: usersStoreMock + + readonly property var usersModel: ListModel { + Component.onCompleted: { + for(let i=0; i < 4; i++) { + append(d.createMemberDict(i)) + } + } + } + + readonly property var temporaryModel: ListModel { + Component.onCompleted: usersStoreMock.resetTemporaryModel() + } + + function appendTemporaryModel(pubKey, displayName) { + temporaryModel.append({ + pubKey: pubKey, + displayName: displayName, + }) + } + + function removeFromTemporaryModel(pubKey) { + for(let i = 0; i < temporaryModel.count; i++) { + if (temporaryModel.get(i).pubKey === pubKey) { + temporaryModel.remove(i, 1) + return + } + } + } + + function resetTemporaryModel() { + temporaryModel.clear() + for(let i = 0; i < usersModel.count; i++) { + const obj = usersModel.get(i) + temporaryModel.append(obj) + } + } + + function updateGroupMembers() { + const users = [] + for(let i = 0; i < temporaryModel.count; i++) { + const obj = temporaryModel.get(i) + users.push({ + pubKey: obj.pubKey, + displayName: obj.displayName, + localNickname: "", + alias: "three word name(%1)".arg(obj.pubKey), + isVerified: false, + isUntrustworthy: false, + isContact: true, + icon: "", + color: "red", + onlineStatus: 0, + isAdmin: i == 0 ? true : false + }) + } + usersModel.clear() + usersModel.append(users) + + logs.logEvent("UsersStore::updateGroupMembers") + } + } + + QtObject { + id: d + + function createUserDict(seed: int) { + const pubKey = "0x%1".arg(seed) + return { + pubKey: pubKey, + displayName: seed%8 ? "user%1".arg(seed) : "", + localNickname: seed%3 ? "" : "nickname%1".arg(seed), + alias: "three word name(%1)".arg(pubKey), + isVerified: seed%3 ? false : true, + isUntrustworthy: seed%5 ? false : true, + isContact: true, + icon: "", + color: seed%2 ? "white" : "red", + onlineStatus: seed%2, + } + } + + function createMemberDict(seed: int) { + var member = createUserDict(seed) + member["isAdmin"] = seed === 0 + return member + } + } + + SplitView { + orientation: Qt.Vertical + + SplitView.fillWidth: true + SplitView.fillHeight: true + + SwipeView { + id: swipeView + + SplitView.fillWidth: true + SplitView.fillHeight: true + + interactive: false + currentIndex: selectorsSwitch.checked + + Item { + Loader { + active: root.globalUtilsReady && root.mainModuleReady + + anchors { + top: parent.top + left: parent.left + right: parent.right + margins: 64 + } + + sourceComponent: MembersSelectorView { + rootStore: rootStoreMock + } + } + } + + Item { + Loader { + active: root.globalUtilsReady && root.mainModuleReady + + anchors { + top: parent.top + left: parent.left + right: parent.right + margins: 64 + } + + sourceComponent: MembersEditSelectorView { + rootStore: rootStoreMock + usersStore: usersStoreMock + } + } + } + } + + LogsAndControlsPanel { + id: logsAndControlsPanel + + SplitView.minimumHeight: 100 + SplitView.preferredHeight: 200 + + logsView.logText: logs.logText + + ColumnLayout { + Switch { + id: selectorsSwitch + text: "members editor" + } + + Switch { + id: chatAdminSwitch + visible: selectorsSwitch.checked + text: "chat admin" + onCheckedChanged: usersStore.resetTemporaryModel() + } + } + } + } + + Pane { + SplitView.minimumWidth: 300 + SplitView.preferredWidth: 300 + + MembersSelectorModelEditor { + anchors.fill: parent + model: contactsModel + + onRemoveClicked: contactsModel.remove(index, 1) + onRemoveAllClicked: contactsModel.clear() + onAddClicked: contactsModel.append(d.createUserDict(contactsModel.count)) + } + } +} + diff --git a/storybook/pages/ProfileDialogViewPage.qml b/storybook/pages/ProfileDialogViewPage.qml index 1687299b37..5af4a60de8 100644 --- a/storybook/pages/ProfileDialogViewPage.qml +++ b/storybook/pages/ProfileDialogViewPage.qml @@ -70,8 +70,8 @@ SplitView { }) } Component.onCompleted: { - root.mainModuleReady = true Utils.mainModuleInst = this + root.mainModuleReady = true } Component.onDestruction: { root.mainModuleReady = false diff --git a/ui/StatusQ/src/StatusQ/Core/StatusListView.qml b/ui/StatusQ/src/StatusQ/Core/StatusListView.qml index 83e9c2a8ba..f77b103058 100644 --- a/ui/StatusQ/src/StatusQ/Core/StatusListView.qml +++ b/ui/StatusQ/src/StatusQ/Core/StatusListView.qml @@ -33,6 +33,8 @@ ListView { readonly property int availableWidth: width - leftMargin - rightMargin readonly property int availableHeight: height - topMargin - bottomMargin + readonly property alias horizontalScrollBar: horizontalScrollBar + readonly property alias verticalScrollBar: verticalScrollBar clip: true boundsBehavior: Flickable.StopAtBounds @@ -40,11 +42,13 @@ ListView { synchronousDrag: true ScrollBar.horizontal: StatusScrollBar { + id: horizontalScrollBar policy: ScrollBar.AsNeeded visible: resolveVisibility(policy, root.width, root.contentWidth) } ScrollBar.vertical: StatusScrollBar { + id: verticalScrollBar policy: ScrollBar.AsNeeded visible: resolveVisibility(policy, root.height, root.contentHeight) } diff --git a/ui/app/AppLayouts/Chat/panels/InlineSelectorPanel.qml b/ui/app/AppLayouts/Chat/panels/InlineSelectorPanel.qml index 844f56ffd1..c5a1d144de 100644 --- a/ui/app/AppLayouts/Chat/panels/InlineSelectorPanel.qml +++ b/ui/app/AppLayouts/Chat/panels/InlineSelectorPanel.qml @@ -19,13 +19,12 @@ Item { property alias suggestionsModel: suggestionsListView.model property alias suggestionsDelegate: suggestionsListView.delegate + property size suggestionsDelegateSize: Qt.size(344, 64) readonly property alias label: label readonly property alias warningLabel: warningLabel readonly property alias edit: edit - property bool confirmBtnEnabled: (listView.count > 0) - signal confirmed() signal rejected() @@ -66,105 +65,109 @@ Item { Item { Layout.fillWidth: true Layout.fillHeight: true - onWidthChanged: { - listView.positionViewAtEnd(); - } + StatusScrollView { + id: scrollView + + function positionViewAtEnd() { + if (scrollView.contentWidth > scrollView.width) { + scrollView.contentX = scrollView.contentWidth - scrollView.width + } else { + scrollView.contentX = 0 + } + } - RowLayout { anchors.fill: parent - Item { - //40 px least space for input - Layout.preferredWidth: (listView.contentWidth < (parent.width - 40)) ? - listView.contentWidth : (parent.width - 40) - Layout.preferredHeight: 44 + padding: 0 + + onContentWidthChanged: positionViewAtEnd() + onWidthChanged: positionViewAtEnd() + + RowLayout { + height: scrollView.height StatusListView { - clip: true id: listView - width: parent.width - height: 30 - anchors.verticalCenter: parent.verticalCenter + Layout.fillWidth: true + Layout.preferredHeight: 30 + implicitWidth: contentWidth orientation: ListView.Horizontal spacing: Style.current.halfPadding - ScrollBar.horizontal: scrollBar - onCountChanged: { - positionViewAtEnd(); - } - } - StatusScrollBar { - id: scrollBar - parent: listView.parent - anchors.top: listView.bottom - anchors.left: listView.left - anchors.right: listView.right - policy: ScrollBar.AsNeeded - visible: resolveVisibility(policy, listView.width, listView.contentWidth) - } - } - TextInput { - id: edit - Layout.fillWidth: true - Layout.fillHeight: true - verticalAlignment: Text.AlignVCenter - font.pixelSize: 15 - color: Theme.palette.directColor1 - clip: true - - selectByMouse: true - selectionColor: Theme.palette.primaryColor2 - selectedTextColor: color - - cursorDelegate: Rectangle { - color: Theme.palette.primaryColor1 - implicitWidth: 2 - radius: 1 - visible: edit.cursorVisible - SequentialAnimation on visible { - loops: Animation.Infinite - running: edit.cursorVisible - PropertyAnimation { to: false; duration: 600; } - PropertyAnimation { to: true; duration: 600; } - } } - Keys.onPressed: { - if (event.matches(StandardKey.Paste)) { - event.accepted = true - const previousText = text; - const previousSelectedText = selectedText; - paste() - if (previousText === "" || previousSelectedText.length === previousText.length) - root.textPasted(text) - return; - } + TextInput { + id: edit + Layout.minimumWidth: 4 + Layout.fillHeight: true + verticalAlignment: Text.AlignVCenter + font.pixelSize: 15 + color: Theme.palette.directColor1 - if (suggestionsDialog.visible) { - if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { - root.entryAccepted(suggestionsListView.itemAtIndex(suggestionsListView.currentIndex)) - } else if (event.key === Qt.Key_Up) { - suggestionsListView.decrementCurrentIndex() - } else if (event.key === Qt.Key_Down) { - suggestionsListView.incrementCurrentIndex() + selectByMouse: true + selectionColor: Theme.palette.primaryColor2 + selectedTextColor: color + + cursorDelegate: Rectangle { + color: Theme.palette.primaryColor1 + implicitWidth: 2 + radius: 1 + visible: edit.cursorVisible + SequentialAnimation on visible { + loops: Animation.Infinite + running: edit.cursorVisible + PropertyAnimation { to: false; duration: 600; } + PropertyAnimation { to: true; duration: 600; } } - } else { - if (event.key === Qt.Key_Backspace && edit.text === "" && listView.count > 0) { - root.entryRemoved(listView.itemAtIndex(listView.count - 1)) - } else if (event.key === Qt.Key_Return || event.key === Qt.Enter) { - root.enterKeyPressed() - } else if (event.key === Qt.Key_Escape) { - root.rejected() - } else if (event.key === Qt.Key_Up) { - root.upKeyPressed(); - } else if (event.key === Qt.Key_Down) { - root.downKeyPressed(); + } + + Keys.onPressed: { + if (event.matches(StandardKey.Paste)) { + event.accepted = true + const previousText = text; + const previousSelectedText = selectedText; + paste() + if (previousText === "" || previousSelectedText.length === previousText.length) + root.textPasted(text) + return; + } + + if (suggestionsDialog.visible) { + if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { + root.entryAccepted(suggestionsListView.itemAtIndex(suggestionsListView.currentIndex)) + } else if (event.key === Qt.Key_Up) { + suggestionsListView.decrementCurrentIndex() + } else if (event.key === Qt.Key_Down) { + suggestionsListView.incrementCurrentIndex() + } + } else { + if (event.key === Qt.Key_Backspace && edit.text === "") { + root.entryRemoved(listView.itemAtIndex(listView.count - 1)) + } else if (event.key === Qt.Key_Return || event.key === Qt.Enter) { + root.enterKeyPressed() + } else if (event.key === Qt.Key_Escape) { + root.rejected() + } else if (event.key === Qt.Key_Up) { + root.upKeyPressed() + } else if (event.key === Qt.Key_Down) { + root.downKeyPressed() + } } } } + + // ensure edit cursor is visible + Item { + Layout.fillHeight: true + implicitWidth: 1 + } } - // ensure edit cursor is visible - Item { - Layout.fillHeight: true - implicitWidth: 1 + ScrollBar.horizontal: StatusScrollBar { + id: scrollBar + parent: scrollView.parent + anchors.top: scrollView.bottom + anchors.left: scrollView.left + anchors.right: scrollView.right + policy: ScrollBar.AsNeeded + visible: resolveVisibility(policy, scrollView.width, scrollView.contentWidth) } } } @@ -192,7 +195,7 @@ Item { StatusButton { objectName: "inlineSelectorConfirmButton" Layout.alignment: Qt.AlignVCenter - enabled: root.confirmBtnEnabled + enabled: (listView.count > 0) text: qsTr("Confirm") onClicked: root.confirmed() } @@ -207,9 +210,9 @@ Item { Popup { id: suggestionsDialog - parent: edit - x: (parent.contentWidth - Style.current.halfPadding) - y: (parent.height + Style.current.halfPadding) + parent: scrollView + x: Math.min(parent.width, parent.contentWidth) + y: parent.height + Style.current.halfPadding visible: edit.text !== "" padding: Style.current.halfPadding background: StatusDialogBackground { @@ -226,10 +229,19 @@ Item { } } + height: suggestionsListView.count ? + Math.min(400, suggestionsListView.count * suggestionsDelegateSize.height + 2 * padding) : + noResultsFoundText.height + 2 * padding + width: suggestionsDelegateSize.width + ColumnLayout { anchors.fill: parent StatusBaseText { + id: noResultsFoundText + + Layout.fillWidth: true + visible: root.suggestionsModel.count === 0 text: qsTr("No results found") color: Theme.palette.baseColor1 @@ -237,11 +249,22 @@ Item { StatusListView { id: suggestionsListView + + Layout.fillWidth: true + Layout.fillHeight: true + visible: root.suggestionsModel.count - implicitWidth: contentItem.childrenRect.width - implicitHeight: contentItem.childrenRect.height + highlightMoveDuration: 0 + highlightMoveVelocity: -1 + + verticalScrollBar { + visible: contentHeight > height + policy: ScrollBar.AlwaysOn + } + onVisibleChanged: currentIndex = 0 + onCountChanged: currentIndex = 0 } } } diff --git a/ui/app/AppLayouts/Chat/stores/RootStore.qml b/ui/app/AppLayouts/Chat/stores/RootStore.qml index bb69cbf425..efa1fbfad9 100644 --- a/ui/app/AppLayouts/Chat/stores/RootStore.qml +++ b/ui/app/AppLayouts/Chat/stores/RootStore.qml @@ -63,6 +63,10 @@ QtObject { return chatCommunitySectionModule.getMySectionId() } + function amIChatAdmin() { + return currentChatContentModule().amIChatAdmin() + } + function acceptContactRequest(pubKey) { chatCommunitySectionModule.acceptContactRequest(pubKey) } diff --git a/ui/app/AppLayouts/Chat/stores/UsersStore.qml b/ui/app/AppLayouts/Chat/stores/UsersStore.qml index cbc6e8ae21..0e64fce5f2 100644 --- a/ui/app/AppLayouts/Chat/stores/UsersStore.qml +++ b/ui/app/AppLayouts/Chat/stores/UsersStore.qml @@ -4,11 +4,20 @@ QtObject { id: root property var usersModule - property var usersModel - onUsersModuleChanged: { - if(!usersModule) - return - root.usersModel = usersModule.model + readonly property var usersModel: usersModule ? usersModule.model : null + readonly property var temporaryModel: usersModule ? usersModule.temporaryModel : null + + function appendTemporaryModel(pubKey, displayName) { + usersModule.appendTemporaryModel(pubKey, displayName) + } + function removeFromTemporaryModel(pubKey) { + usersModule.removeFromTemporaryModel(pubKey) + } + function resetTemporaryModel() { + usersModule.resetTemporaryModel() + } + function updateGroupMembers() { + usersModule.updateGroupMembers() } } diff --git a/ui/app/AppLayouts/Chat/views/ChatHeaderContentView.qml b/ui/app/AppLayouts/Chat/views/ChatHeaderContentView.qml index 7ea9d3b4b8..38131785aa 100644 --- a/ui/app/AppLayouts/Chat/views/ChatHeaderContentView.qml +++ b/ui/app/AppLayouts/Chat/views/ChatHeaderContentView.qml @@ -345,9 +345,10 @@ Item { id: membersSelector MembersEditSelectorView { - sectionModule: root.chatSectionModule - chatContentModule: root.chatContentModule rootStore: root.rootStore + usersStore: UsersStore { + usersModule: root.chatContentModule.usersModule + } onConfirmed: root.state = d.stateInfoButtonContent onRejected: root.state = d.stateInfoButtonContent diff --git a/ui/app/AppLayouts/Chat/views/MembersEditSelectorView.qml b/ui/app/AppLayouts/Chat/views/MembersEditSelectorView.qml index 3bf9707835..57a70fbeee 100644 --- a/ui/app/AppLayouts/Chat/views/MembersEditSelectorView.qml +++ b/ui/app/AppLayouts/Chat/views/MembersEditSelectorView.qml @@ -19,35 +19,32 @@ import SortFilterProxyModel 0.2 MembersSelectorBase { id: root - // TODO: use stores instead of modules - property var sectionModule - property var chatContentModule + property var usersStore - confirmBtnEnabled: true onConfirmed: { - d.updateGroupMembers() - d.resetTemporaryModel() + usersStore.updateGroupMembers() + usersStore.resetTemporaryModel() } onRejected: { - d.resetTemporaryModel() + usersStore.resetTemporaryModel() } - limitReached: (model.count === membersLimit) - onEntryAccepted: { + + onEntryAccepted: if (suggestionsDelegate) { if (!root.limitReached) { - d.appendTemporaryModel(suggestionsDelegate._pubKey, suggestionsDelegate.userName) + usersStore.appendTemporaryModel(suggestionsDelegate._pubKey, suggestionsDelegate.userName) root.edit.clear() } } - onEntryRemoved: { + onEntryRemoved: if (delegate) { if (!delegate.isReadonly) { - d.removeFromTemporaryModel(delegate._pubKey) + usersStore.removeFromTemporaryModel(delegate._pubKey) } } model: SortFilterProxyModel { - sourceModel: root.chatContentModule.usersModule.temporaryModel + sourceModel: root.usersStore.temporaryModel sorters: RoleSorter { roleName: "isAdmin" sortOrder: Qt.DescendingOrder @@ -58,35 +55,19 @@ MembersSelectorBase { readonly property string _pubKey: model.pubKey height: ListView.view.height - text: model.displayName !== "" ? model.displayName : model.alias + text: root.tagText(model.localNickname, model.displayName, model.alias) + isReadonly: { if (model.isAdmin) return true - if (root.chatContentModule.amIChatAdmin()) return false - return index < root.chatContentModule.usersModule.model.count + if (root.rootStore.amIChatAdmin()) return false + return index < root.usersStore.usersModel.count } icon: model.isAdmin ? "crown" : "" onClicked: root.entryRemoved(this) } - QtObject { - id: d - - function appendTemporaryModel(pubKey, displayName) { - root.chatContentModule.usersModule.appendTemporaryModel(pubKey, displayName) - } - function removeFromTemporaryModel(pubKey) { - root.chatContentModule.usersModule.removeFromTemporaryModel(pubKey) - } - function resetTemporaryModel() { - root.chatContentModule.usersModule.resetTemporaryModel() - } - function updateGroupMembers() { - root.chatContentModule.usersModule.updateGroupMembers() - } - } - Component.onCompleted: { - d.resetTemporaryModel() + usersStore.resetTemporaryModel() } } diff --git a/ui/app/AppLayouts/Chat/views/MembersSelectorView.qml b/ui/app/AppLayouts/Chat/views/MembersSelectorView.qml index f530a1c82c..89f4028a67 100644 --- a/ui/app/AppLayouts/Chat/views/MembersSelectorView.qml +++ b/ui/app/AppLayouts/Chat/views/MembersSelectorView.qml @@ -14,19 +14,21 @@ import SortFilterProxyModel 0.2 MembersSelectorBase { id: root + limitReached: model.count >= membersLimit - 1 // -1 because creator is not on the list of members when creating chat + function cleanup() { - root.edit.clear(); - d.selectedMembers.clear(); + root.edit.clear() + d.selectedMembers.clear() } - onEntryAccepted: { + onEntryAccepted: if (suggestionsDelegate) { if (root.limitReached) return - if (d.addMember(suggestionsDelegate._pubKey, suggestionsDelegate.userName, suggestionsDelegate.isAdmin)) + if (d.addMember(suggestionsDelegate._pubKey, suggestionsDelegate.userName, suggestionsDelegate.nickName)) root.edit.clear() } - onEntryRemoved: { + onEntryRemoved: if (delegate) { d.removeMember(delegate._pubKey) } @@ -40,10 +42,10 @@ MembersSelectorBase { delegate: StatusTagItem { readonly property string _pubKey: model.pubKey + height: ListView.view.height - text: model.displayName - isReadonly: model.isAdmin - icon: model.isAdmin ? "crown" : "" + text: root.tagText(model.localNickname, model.displayName, model.alias) + onClicked: root.entryRemoved(this) } @@ -58,7 +60,7 @@ MembersSelectorBase { root.rootStore.contactsStore.resolveENS(value) } - function addMember(pubKey, displayName, isAdmin) { + function addMember(pubKey, displayName, localNickname) { for (let i = 0; i < d.selectedMembers.count; ++i) { if (d.selectedMembers.get(i).pubKey === pubKey) return false @@ -67,7 +69,7 @@ MembersSelectorBase { d.selectedMembers.append({ "pubKey": pubKey, "displayName": displayName, - "isAdmin": isAdmin + "localNickname": localNickname }) return true } diff --git a/ui/app/AppLayouts/Chat/views/private/MembersSelectorBase.qml b/ui/app/AppLayouts/Chat/views/private/MembersSelectorBase.qml index 1f437e07ec..e6e075903e 100644 --- a/ui/app/AppLayouts/Chat/views/private/MembersSelectorBase.qml +++ b/ui/app/AppLayouts/Chat/views/private/MembersSelectorBase.qml @@ -21,7 +21,11 @@ InlineSelectorPanel { property var rootStore readonly property int membersLimit: 20 // see: https://github.com/status-im/status-mobile/issues/13066 - property bool limitReached: (model.count === (membersLimit-1)) + property bool limitReached: model.count >= membersLimit + + function tagText(localNickname, displayName, aliasName) { + return localNickname || displayName || aliasName + } label.text: qsTr("To:") warningLabel.text: qsTr("%1 USER LIMIT REACHED").arg(membersLimit) @@ -32,8 +36,10 @@ InlineSelectorPanel { sourceModel: root.rootStore.contactsModel - function searchPredicate(displayName) { - return displayName.toLowerCase().includes(root.edit.text.toLowerCase()) + function searchPredicate(displayName, localNickname, nameAlias) { + return displayName.toLowerCase().includes(root.edit.text.toLowerCase()) || + localNickname.toLowerCase().includes(root.edit.text.toLowerCase()) || + (!displayName && nameAlias.toLowerCase().includes(root.edit.text.toLowerCase())) } function notAMemberPredicate(pubKey) { @@ -49,7 +55,7 @@ InlineSelectorPanel { enabled: root.edit.text !== "" expression: { root.edit.text // ensure expression is reevaluated when edit.text changes - return _suggestionsModel.searchPredicate(model.displayName) + return _suggestionsModel.searchPredicate(model.displayName, model.localNickname, model.alias) } }, ExpressionFilter { @@ -59,13 +65,22 @@ InlineSelectorPanel { } } ] + + proxyRoles: ExpressionRole { + name: "title" + expression: model.localNickname || model.displayName || model.alias + } + sorters: StringSorter { - roleName: "displayName" + roleName: "title" + numericMode: true } } suggestionsDelegate: ContactListItemDelegate { highlighted: ListView.isCurrentItem + height: root.suggestionsDelegateSize.height + width: root.suggestionsDelegateSize.width onClicked: root.entryAccepted(this) } diff --git a/ui/app/AppLayouts/Chat/views/qmldir b/ui/app/AppLayouts/Chat/views/qmldir index b14fdf1222..f142477de7 100644 --- a/ui/app/AppLayouts/Chat/views/qmldir +++ b/ui/app/AppLayouts/Chat/views/qmldir @@ -1 +1,3 @@ CreateChatView 1.0 CreateChatView.qml +MembersSelectorView 1.0 MembersSelectorView.qml +MembersEditSelectorView 1.0 MembersEditSelectorView.qml diff --git a/ui/imports/shared/controls/delegates/ContactListItemDelegate.qml b/ui/imports/shared/controls/delegates/ContactListItemDelegate.qml index 5770401d0c..9a7503a8ce 100644 --- a/ui/imports/shared/controls/delegates/ContactListItemDelegate.qml +++ b/ui/imports/shared/controls/delegates/ContactListItemDelegate.qml @@ -18,7 +18,7 @@ StatusMemberListItem { pubKey: hasEnsName ? "" : Utils.getCompressedPk(model.pubKey) nickName: model.localNickname - userName: model.displayName + userName: model.displayName || model.alias isVerified: model.isVerified isUntrustworthy: model.isUntrustworthy isContact: model.isContact