From 08aced147f515982ae54a2c36503bc6fff17eec4 Mon Sep 17 00:00:00 2001
From: Alexandra Betouni <31625338+alexandraB99@users.noreply.github.com>
Date: Thu, 20 Jan 2022 00:33:29 +0200
Subject: [PATCH] feat(StatusQ.Components): Adding StatusTagSelector component
Added StatusTagSelector component needed for
creating new chat channels, either ono on
one or group based on updated designs on
Figma
Also added corresponding page in API Documentation
Closes #526
---
.../sandbox/demoapp/ChatChannelView.qml | 103 ++++++++
ui/StatusQ/sandbox/demoapp/CreateChatView.qml | 232 ++++++++++++++++++
.../sandbox/demoapp/StatusAppChatView.qml | 225 +++--------------
ui/StatusQ/sandbox/demoapp/data/Models.qml | 52 ++++
ui/StatusQ/sandbox/main.qml | 5 +
.../sandbox/pages/StatusTagSelectorPage.qml | 56 +++++
.../StatusQ/Components/StatusTagSelector.qml | 126 ++++++++++
ui/StatusQ/src/StatusQ/Components/qmldir | 1 +
ui/StatusQ/statusq.qrc | 1 +
9 files changed, 613 insertions(+), 188 deletions(-)
create mode 100644 ui/StatusQ/sandbox/demoapp/ChatChannelView.qml
create mode 100644 ui/StatusQ/sandbox/demoapp/CreateChatView.qml
create mode 100644 ui/StatusQ/sandbox/pages/StatusTagSelectorPage.qml
create mode 100644 ui/StatusQ/src/StatusQ/Components/StatusTagSelector.qml
diff --git a/ui/StatusQ/sandbox/demoapp/ChatChannelView.qml b/ui/StatusQ/sandbox/demoapp/ChatChannelView.qml
new file mode 100644
index 0000000000..25f41203cd
--- /dev/null
+++ b/ui/StatusQ/sandbox/demoapp/ChatChannelView.qml
@@ -0,0 +1,103 @@
+import QtQuick 2.12
+import QtQuick.Controls 2.12
+import QtQuick.Layouts 1.12
+import QtQml.Models 2.2
+
+import StatusQ.Controls 0.1
+import StatusQ.Popups 0.1
+import StatusQ.Components 0.1
+import StatusQ.Core 0.1
+import StatusQ.Core.Theme 0.1
+
+ListView {
+ id: messageList
+ anchors.fill: parent
+ anchors.margins: 15
+ clip: true
+ delegate: StatusMessage {
+ id: delegate
+ width: parent.width
+
+ audioMessageInfoText: "Audio Message"
+ cancelButtonText: "Cancel"
+ saveButtonText: "Save"
+ loadingImageText: "Loading image..."
+ errorLoadingImageText: "Error loading the image"
+ resendText: "Resend"
+ pinnedMsgInfoText: "Pinned by"
+
+ messageDetails: StatusMessageDetails {
+ contentType: model.contentType
+ messageContent: model.messageContent
+ amISender: model.amIsender
+ displayName: model.userName
+ secondaryName: model.localName !== "" && model.ensName.startsWith("@") ? model.ensName: ""
+ chatID: model.chatKey
+ profileImage: StatusImageSettings {
+ width: 40
+ height: 40
+ source: model.profileImage
+ isIdenticon: model.isIdenticon
+ }
+ messageText: model.message
+ hasMention: model.hasMention
+ contactType: model.contactType
+ isPinned: model.isPinned
+ pinnedBy: model.pinnedBy
+ hasExpired: model.hasExpired
+ }
+ timestamp.text: "10:00 am"
+ timestamp.tooltip.text: "10:01 am"
+ // reply related data
+ isAReply: model.isReply
+ replyDetails: StatusMessageDetails {
+ amISender: model.isReply ? model.replyAmISender : ""
+ displayName: model.isReply ? model.replySenderName: ""
+ profileImage: StatusImageSettings {
+ width: 20
+ height: 20
+ source: model.isReply ? model.replyProfileImage: ""
+ isIdenticon: model.isReply ? model.replyIsIdenticon: ""
+ }
+ messageText: model.isReply ? model.replyMessageText: ""
+ contentType: model.replyContentType
+ messageContent: model.replyMessageContent
+ }
+ quickActions: [
+ StatusFlatRoundButton {
+ id: emojiBtn
+ width: 32
+ height: 32
+ icon.name: "reaction-b"
+ type: StatusFlatRoundButton.Type.Tertiary
+ tooltip.text: "Add reaction"
+ },
+ StatusFlatRoundButton {
+ id: replyBtn
+ width: 32
+ height: 32
+ icon.name: "reply"
+ type: StatusFlatRoundButton.Type.Tertiary
+ tooltip.text: "Reply"
+ },
+ StatusFlatRoundButton {
+ width: 32
+ height: 32
+ icon.name: "tiny/edit"
+ type: StatusFlatRoundButton.Type.Tertiary
+ tooltip.text: "Edit"
+ onClicked: {
+ delegate.editMode = !delegate.editMode
+ }
+ },
+ StatusFlatRoundButton {
+ id: otherBtn
+ width: 32
+ height: 32
+ icon.name: "more"
+ type: StatusFlatRoundButton.Type.Tertiary
+ tooltip.text: "More"
+ }
+ ]
+ }
+}
diff --git a/ui/StatusQ/sandbox/demoapp/CreateChatView.qml b/ui/StatusQ/sandbox/demoapp/CreateChatView.qml
new file mode 100644
index 0000000000..b8de5912e5
--- /dev/null
+++ b/ui/StatusQ/sandbox/demoapp/CreateChatView.qml
@@ -0,0 +1,232 @@
+import QtQuick 2.12
+import QtQuick.Controls 2.12
+import QtQuick.Layouts 1.12
+import QtQml.Models 2.2
+import QtGraphicalEffects 1.0
+
+import StatusQ.Controls 0.1
+import StatusQ.Components 0.1
+import StatusQ.Core 0.1
+import StatusQ.Core.Theme 0.1
+
+Page {
+ id: root
+ anchors.fill: parent
+ anchors.margins: 16
+ property ListModel contactsModel: null
+ background: null
+
+ header: RowLayout {
+ id: headerRow
+ width: parent.width
+ height: tagSelector.height
+ anchors.right: parent.right
+ anchors.rightMargin: 8
+
+ StatusTagSelector {
+ id: tagSelector
+ Layout.fillWidth: true
+ Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
+ Layout.leftMargin: 17
+ implicitHeight: 44
+ toLabelText: qsTr("To: ")
+ warningText: qsTr("5 USER LIMIT REACHED")
+ //simulate model filtering, TODO this
+ //makes more sense to be provided by the backend
+ //figure how real implementation should look like
+ property ListModel sortedList: ListModel { }
+ onTextChanged: {
+ sortedList.clear();
+ if (text !== "") {
+ for (var i = 0; i < contactsModel.count; i++ ) {
+ var entry = contactsModel.get(i);
+ if (entry.name.toLowerCase().includes(text.toLowerCase())) {
+ sortedList.insert(sortedList.count, {"publicId": entry.publicId, "name": entry.name,
+ "icon": entry.icon, "isIdenticon": entry.isIdenticon,
+ "onlineStatus": entry.onlineStatus});
+ userListView.model = sortedList;
+ }
+ }
+ } else {
+ userListView.model = contactsModel;
+ }
+ }
+ }
+
+ StatusButton {
+ implicitHeight: 44
+ enabled: (tagSelector.namesModel.count > 0)
+ text: "Confirm"
+ }
+ }
+
+ contentItem: Item {
+ anchors.fill: parent
+ anchors.topMargin: headerRow.height + 16
+
+ Item {
+ anchors.fill: parent
+ visible: (contactsModel.count > 0)
+
+ StatusBaseText {
+ id: contactsLabel
+ font.pixelSize: 15
+ color: Theme.palette.baseColor1
+ text: "Contacts"
+ }
+ Control {
+ width: 360
+ anchors {
+ top: contactsLabel.bottom
+ topMargin: 8//Style.current.padding
+ bottom: parent.bottom
+ bottomMargin: 20//Style.current.bigPadding
+ }
+ background: Rectangle {
+ id: statusPopupMenuBackgroundContent
+ anchors.left: parent.left
+ anchors.right: parent.right
+ height: (userListView.height + 8)
+ visible: (tagSelector.sortedList.count > 0)
+ color: Theme.palette.statusPopupMenu.backgroundColor
+ radius: 8
+ layer.enabled: true
+ layer.effect: DropShadow {
+ width: statusPopupMenuBackgroundContent.width
+ height: statusPopupMenuBackgroundContent.height
+ x: statusPopupMenuBackgroundContent.x
+ visible: statusPopupMenuBackgroundContent.visible
+ source: statusPopupMenuBackgroundContent
+ horizontalOffset: 0
+ verticalOffset: 4
+ radius: 12
+ samples: 25
+ spread: 0.2
+ color: Theme.palette.dropShadow
+ }
+ }
+ contentItem: ListView {
+ id: userListView
+ anchors.left: parent.left
+ anchors.right: parent.right
+ height: (count * 64) > parent.height ? parent.height : (count * 64)
+ clip: true
+ model: contactsModel
+ ScrollBar.vertical: ScrollBar {
+ policy: ScrollBar.AsNeeded
+ }
+ boundsBehavior: Flickable.StopAtBounds
+ delegate: Item {
+ id: wrapper
+ anchors.right: parent.right
+ anchors.left: parent.left
+ height: 64
+ property bool hovered: false
+ Rectangle {
+ id: rectangle
+ anchors.fill: parent
+ anchors.topMargin: 8
+ anchors.rightMargin: 8
+ anchors.leftMargin: 8
+ radius: 8
+ visible: (tagSelector.sortedList.count > 0)
+ color: (wrapper.hovered) ? Theme.palette.baseColor2 : "transparent"
+ }
+
+ StatusSmartIdenticon {
+ id: contactImage
+ anchors.left: parent.left
+ anchors.leftMargin: 16//Style.current.padding
+ anchors.verticalCenter: parent.verticalCenter
+ name: model.name
+ icon: StatusIconSettings {
+ width: 28
+ height: 28
+ letterSize: 15
+ }
+ image: StatusImageSettings {
+ width: 28
+ height: 28
+ source: model.icon
+ isIdenticon: model.isIdenticon
+ }
+ }
+
+ StatusBaseText {
+ id: contactInfo
+ text: model.name
+ anchors.right: parent.right
+ anchors.rightMargin: 8
+ anchors.left: contactImage.right
+ anchors.leftMargin: 16
+ anchors.verticalCenter: parent.verticalCenter
+ elide: Text.ElideRight
+ color: Theme.palette.directColor1
+ font.weight: Font.Medium
+ font.pixelSize: 15
+ }
+
+ StatusBadge {
+ id: statusBadge
+ width: 15
+ height: 15
+ anchors.left: contactImage.right
+ anchors.leftMargin: -8
+ anchors.bottom: contactImage.bottom
+ border.width: 3
+ border.color: Theme.palette.statusAppNavBar.backgroundColor
+ color: {
+ if (model.onlineStatus === 1)
+ return Theme.palette.successColor1;
+ else if (model.onlineStatus === 2)
+ return Theme.palette.pinColor1;
+ else if (model.onlineStatus === 3)
+ return Theme.palette.dangerColor1;
+
+ return "transparent"
+ }
+ }
+
+ MouseArea {
+ cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
+ acceptedButtons: Qt.LeftButton | Qt.RightButton
+ anchors.fill: parent
+ hoverEnabled: true
+ onEntered: {
+ wrapper.hovered = true;
+ }
+ onExited: {
+ wrapper.hovered = false;
+ }
+ onClicked: {
+ tagSelector.insertTag(model.name, model.publicId);
+ }
+ }
+ }
+ }
+ }
+ Component.onCompleted: {
+ if (visible) {
+ tagSelector.textEdit.forceActiveFocus();
+ }
+ }
+ }
+
+ StatusBaseText {
+ visible: (contactsModel.count === 0)
+ anchors.centerIn: parent
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ font.pixelSize: 15
+ color: Theme.palette.baseColor1
+ text: qsTr("You can only send direct messages to your Contacts. \n\n
+Send a contact request to the person you would like to chat with, you will be\n able to
+chat with them once they have accepted your contact request.")
+ Component.onCompleted: {
+ if (visible) {
+ tagSelector.enabled = false;
+ }
+ }
+ }
+ }
+}
diff --git a/ui/StatusQ/sandbox/demoapp/StatusAppChatView.qml b/ui/StatusQ/sandbox/demoapp/StatusAppChatView.qml
index 19cf4a06c6..a59a2c46d7 100644
--- a/ui/StatusQ/sandbox/demoapp/StatusAppChatView.qml
+++ b/ui/StatusQ/sandbox/demoapp/StatusAppChatView.qml
@@ -1,4 +1,5 @@
import QtQuick 2.12
+import QtQuick.Layouts 1.12
import StatusQ.Controls 0.1
import StatusQ.Popups 0.1
@@ -11,6 +12,7 @@ import "data" 1.0
StatusAppThreePanelLayout {
id: root
+ property bool createChat: false
leftPanel: Item {
anchors.fill: parent
@@ -23,113 +25,35 @@ StatusAppThreePanelLayout {
text: "Chat"
}
- Item {
+ RowLayout {
id: searchInputWrapper
- anchors.top: headline.bottom
- anchors.topMargin: 16
width: parent.width
height: searchInput.height
+ anchors.top: headline.bottom
+ anchors.topMargin: 16
+ anchors.right: parent.right
+ anchors.rightMargin: 8
StatusBaseInput {
id: searchInput
-
- anchors.verticalCenter: parent.verticalCenter
- anchors.left: parent.left
- anchors.right: actionButton.left
- anchors.leftMargin: 16
- anchors.rightMargin: 16
-
- height: 36
+ Layout.fillWidth: true
+ Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
+ Layout.leftMargin: 17
+ implicitHeight: 36
topPadding: 8
bottomPadding: 0
placeholderText: "Search"
icon.name: "search"
}
- StatusRoundButton {
- id: actionButton
- anchors.verticalCenter: parent.verticalCenter
- anchors.right: parent.right
- anchors.rightMargin: 8
- width: 32
- height: 32
+ StatusIconTabButton {
+ icon.name: "public-chat"
+ }
- type: StatusRoundButton.Type.Secondary
- icon.name: "add"
- state: "default"
-
- onClicked: chatContextMenu.popup(actionButton.width-chatContextMenu.width, actionButton.height + 4)
- states: [
- State {
- name: "default"
- PropertyChanges {
- target: actionButton
- icon.rotation: 0
- highlighted: false
- }
- },
- State {
- name: "pressed"
- PropertyChanges {
- target: actionButton
- icon.rotation: 45
- highlighted: true
- }
- }
- ]
-
- transitions: [
- Transition {
- from: "default"
- to: "pressed"
-
- RotationAnimation {
- duration: 150
- direction: RotationAnimation.Clockwise
- easing.type: Easing.InCubic
- }
- },
- Transition {
- from: "pressed"
- to: "default"
- RotationAnimation {
- duration: 150
- direction: RotationAnimation.Counterclockwise
- easing.type: Easing.OutCubic
- }
- }
- ]
-
- StatusPopupMenu {
- id: chatContextMenu
-
- onOpened: {
- actionButton.state = "pressed"
- }
-
- onClosed: {
- actionButton.state = "default"
- }
-
- StatusMenuItem {
- text: "Start new chat"
- icon.name: "private-chat"
- }
-
- StatusMenuItem {
- text: "Start group chat"
- icon.name: "group-chat"
- }
-
- StatusMenuItem {
- text: "Join public chat"
- icon.name: "public-chat"
- }
-
- StatusMenuItem {
- text: "Communities"
- icon.name: "communities"
- }
+ StatusIconTabButton {
+ icon.name: "edit"
+ onClicked: {
+ root.createChat = !root.createChat;
}
}
}
@@ -201,6 +125,25 @@ StatusAppThreePanelLayout {
}
}
+ centerPanel: Loader {
+ anchors.fill: parent
+ sourceComponent: root.createChat ? createChatView : chatChannelView
+ }
+
+ Component {
+ id: createChatView
+ CreateChatView {
+ contactsModel: Models.dummyContactsModel
+ }
+ }
+
+ Component {
+ id: chatChannelView
+ ChatChannelView {
+ model: Models.chatMessagesModel
+ }
+ }
+
rightPanel: Item {
anchors.fill: parent
@@ -248,98 +191,4 @@ StatusAppThreePanelLayout {
}
}
}
-
- centerPanel: ListView {
- id: messageList
- anchors.fill: parent
- anchors.margins: 15
- clip: true
- model: Models.chatMessagesModel
- delegate: StatusMessage {
- id: delegate
- width: parent.width
-
- audioMessageInfoText: "Audio Message"
- cancelButtonText: "Cancel"
- saveButtonText: "Save"
- loadingImageText: "Loading image..."
- errorLoadingImageText: "Error loading the image"
- resendText: "Resend"
- pinnedMsgInfoText: "Pinned by"
-
- messageDetails: StatusMessageDetails {
- contentType: model.contentType
- messageContent: model.messageContent
- amISender: model.amIsender
- displayName: model.userName
- secondaryName: model.localName !== "" && model.ensName.startsWith("@") ? model.ensName: ""
- chatID: model.chatKey
- profileImage: StatusImageSettings {
- width: 40
- height: 40
- source: model.profileImage
- isIdenticon: model.isIdenticon
- }
- messageText: model.message
- hasMention: model.hasMention
- contactType: model.contactType
- isPinned: model.isPinned
- pinnedBy: model.pinnedBy
- hasExpired: model.hasExpired
- }
- timestamp.text: "10:00 am"
- timestamp.tooltip.text: "10:01 am"
- // reply related data
- isAReply: model.isReply
- replyDetails: StatusMessageDetails {
- amISender: model.isReply ? model.replyAmISender : ""
- displayName: model.isReply ? model.replySenderName: ""
- profileImage: StatusImageSettings {
- width: 20
- height: 20
- source: model.isReply ? model.replyProfileImage: ""
- isIdenticon: model.isReply ? model.replyIsIdenticon: ""
- }
- messageText: model.isReply ? model.replyMessageText: ""
- contentType: model.replyContentType
- messageContent: model.replyMessageContent
- }
- quickActions: [
- StatusFlatRoundButton {
- id: emojiBtn
- width: 32
- height: 32
- icon.name: "reaction-b"
- type: StatusFlatRoundButton.Type.Tertiary
- tooltip.text: "Add reaction"
- },
- StatusFlatRoundButton {
- id: replyBtn
- width: 32
- height: 32
- icon.name: "reply"
- type: StatusFlatRoundButton.Type.Tertiary
- tooltip.text: "Reply"
- },
- StatusFlatRoundButton {
- width: 32
- height: 32
- icon.name: "tiny/edit"
- type: StatusFlatRoundButton.Type.Tertiary
- tooltip.text: "Edit"
- onClicked: {
- delegate.editMode = !delegate.editMode
- }
- },
- StatusFlatRoundButton {
- id: otherBtn
- width: 32
- height: 32
- icon.name: "more"
- type: StatusFlatRoundButton.Type.Tertiary
- tooltip.text: "More"
- }
- ]
- }
- }
}
diff --git a/ui/StatusQ/sandbox/demoapp/data/Models.qml b/ui/StatusQ/sandbox/demoapp/data/Models.qml
index 91695fd602..2ee36abae5 100644
--- a/ui/StatusQ/sandbox/demoapp/data/Models.qml
+++ b/ui/StatusQ/sandbox/demoapp/data/Models.qml
@@ -4,6 +4,58 @@ import StatusQ.Components 0.1
QtObject {
+ property ListModel dummyContactsModel: ListModel {
+ ListElement {
+ publicId: "0x0"
+ name: "Maria"
+ icon: ""
+ isIdenticon: false
+ onlineStatus: 3
+ }
+ ListElement {
+ publicId: "0x1"
+ name: "James"
+ icon: "https://pbs.twimg.com/profile_images/1369221718338895873/T_5fny6o_400x400.jpg"
+ isIdenticon: false
+ onlineStatus: 1
+ }
+ ListElement {
+ publicId: "0x2"
+ name: "Paul"
+ icon: ""
+ isIdenticon: false
+ onlineStatus: 2
+ }
+ ListElement {
+ publicId: "0x3"
+ name: "Tracy"
+ icon: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAAlklEQVR4nOzW0QmDQBAG4SSkl7SUQlJGCrElq9F3QdjjVhh/5nv3cFhY9vUIYQiNITSG0BhCExPynn1gWf9bx498P7/nzPcxEzGExhBdJGYihtAYQlO+tUZvqrPbqeudo5iJGEJjCE15a3VtodH3q2ImYgiNITTlTdG1nUZ5a92VITQxITFiJmIIjSE0htAYQrMHAAD//+wwFVpz+yqXAAAAAElFTkSuQmCC"
+ isIdenticon: true
+ onlineStatus: 3
+ }
+ ListElement {
+ publicId: "0x4"
+ name: "Nick"
+ icon: ""
+ isIdenticon: false
+ onlineStatus: 3
+ }
+ ListElement {
+ publicId: "0x5"
+ name: "Steven"
+ icon: ""
+ isIdenticon: false
+ onlineStatus: 2
+ }
+ ListElement {
+ publicId: "0x6"
+ name: "Helen"
+ icon: ""
+ isIdenticon: false
+ onlineStatus: 3
+ }
+ }
+
property var demoChatListItems: ListModel {
id: demoChatListItems
ListElement {
diff --git a/ui/StatusQ/sandbox/main.qml b/ui/StatusQ/sandbox/main.qml
index f9c5ce7b08..15acad15ca 100644
--- a/ui/StatusQ/sandbox/main.qml
+++ b/ui/StatusQ/sandbox/main.qml
@@ -255,6 +255,11 @@ StatusWindow {
selected: viewLoader.source.toString().includes(title)
onClicked: mainPageView.page("StatusExpandableSettingsItem");
}
+ StatusNavigationListItem {
+ title: "StatusTagSelector"
+ selected: viewLoader.source.toString().includes(title)
+ onClicked: mainPageView.page(title);
+ }
StatusListSectionHeadline { text: "StatusQ.Popup" }
StatusNavigationListItem {
title: "StatusPopupMenu"
diff --git a/ui/StatusQ/sandbox/pages/StatusTagSelectorPage.qml b/ui/StatusQ/sandbox/pages/StatusTagSelectorPage.qml
new file mode 100644
index 0000000000..3de2c69e1d
--- /dev/null
+++ b/ui/StatusQ/sandbox/pages/StatusTagSelectorPage.qml
@@ -0,0 +1,56 @@
+import QtQuick 2.14
+
+import StatusQ.Components 0.1
+
+Item {
+ id: root
+ anchors.fill: parent
+
+ property ListModel asortedContacts: ListModel {
+ ListElement {
+ publicId: "0x0"
+ name: "Maria"
+ icon: ""
+ isIdenticon: false
+ onlineStatus: 3
+ }
+ ListElement {
+ publicId: "0x1"
+ name: "James"
+ icon: "https://pbs.twimg.com/profile_images/1369221718338895873/T_5fny6o_400x400.jpg"
+ isIdenticon: false
+ onlineStatus: 1
+ }
+ ListElement {
+ publicId: "0x2"
+ name: "Paul"
+ icon: ""
+ isIdenticon: false
+ onlineStatus: 2
+ }
+ ListElement {
+ publicId: "0x3"
+ name: "Tracy"
+ icon: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAAlklEQVR4nOzW0QmDQBAG4SSkl7SUQlJGCrElq9F3QdjjVhh/5nv3cFhY9vUIYQiNITSG0BhCExPynn1gWf9bx498P7/nzPcxEzGExhBdJGYihtAYQlO+tUZvqrPbqeudo5iJGEJjCE15a3VtodH3q2ImYgiNITTlTdG1nUZ5a92VITQxITFiJmIIjSE0htAYQrMHAAD//+wwFVpz+yqXAAAAAElFTkSuQmCC"
+ isIdenticon: true
+ onlineStatus: 3
+ }
+ ListElement {
+ publicId: "0x4"
+ name: "Nick"
+ icon: ""
+ isIdenticon: false
+ onlineStatus: 3
+ }
+ }
+
+ StatusTagSelector {
+ id: tagSelector
+ width: 650
+ height: 44
+ anchors.centerIn: parent
+ namesModel: root.asortedContacts
+ toLabelText: qsTr("To: ")
+ warningText: qsTr("5 USER LIMIT REACHED")
+ }
+}
diff --git a/ui/StatusQ/src/StatusQ/Components/StatusTagSelector.qml b/ui/StatusQ/src/StatusQ/Components/StatusTagSelector.qml
new file mode 100644
index 0000000000..036349554f
--- /dev/null
+++ b/ui/StatusQ/src/StatusQ/Components/StatusTagSelector.qml
@@ -0,0 +1,126 @@
+import QtQuick 2.14
+import QtQuick.Layouts 1.12
+import QtQuick.Controls 2.14
+
+import StatusQ.Core 0.1
+import StatusQ.Core.Theme 0.1
+
+Item {
+ id: root
+
+ implicitWidth: 448
+ implicitHeight: 44
+
+ property alias textEdit: edit
+ property alias text: edit.text
+ property string warningText: ""
+ property string toLabelText: ""
+ property int nameCountLimit: 5
+ property ListModel namesModel: ListModel { }
+
+ function find(model, criteria) {
+ for (var i = 0; i < model.count; ++i) if (criteria(model.get(i))) return model.get(i);
+ return null;
+ }
+
+ function insertTag(name, id) {
+ if (!find(namesModel, function(item) { return item.publicId === id }) && namesModel.count < root.nameCountLimit) {
+ namesModel.insert(namesModel.count, {"name": name, "publicId": id});
+ edit.clear();
+ }
+ }
+
+ Rectangle {
+ anchors.fill: parent
+ radius: 8
+ color: Theme.palette.baseColor2
+
+ RowLayout {
+ anchors.fill: parent
+ anchors.leftMargin: 16
+ anchors.rightMargin: 16
+ spacing: 8
+ StatusBaseText {
+ Layout.preferredWidth: 22
+ Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
+ color: Theme.palette.baseColor1
+ text: root.toLabelText
+ }
+
+ ListView {
+ id: namesList
+ Layout.preferredWidth: (count >= 5) ? (parent.width - warningTextLabel.width - 30) : childrenRect.width
+ implicitHeight: 30
+ visible: (count > 0)
+ Layout.alignment: Qt.AlignVCenter
+ model: namesModel
+ orientation: ListView.Horizontal
+ spacing: 8
+ clip: true
+ onWidthChanged: {
+ positionViewAtEnd();
+ }
+
+ delegate: Rectangle {
+ id: nameDelegate
+ width: (nameText.contentWidth + 34)
+ height: 30
+ color: mouseArea.containsMouse ? Theme.palette.miscColor1 : Theme.palette.primaryColor1
+ radius: 8
+ StatusBaseText {
+ id: nameText
+ anchors.left: parent.left
+ anchors.leftMargin: 8
+ anchors.verticalCenter: parent.verticalCenter
+ color: Theme.palette.indirectColor1
+ text: name
+ }
+ StatusIcon {
+ anchors.left: nameText.right
+ anchors.verticalCenter: parent.verticalCenter
+ color: Theme.palette.indirectColor1
+ icon: "close"
+ }
+ MouseArea {
+ id: mouseArea
+ anchors.fill: parent
+ hoverEnabled: true
+ cursorShape: Qt.PointingHandCursor
+ onClicked: {
+ namesModel.remove(index, 1);
+ }
+ }
+ }
+ }
+
+ TextEdit {
+ id: edit
+ Layout.fillWidth: true
+ Layout.preferredHeight: 44
+ verticalAlignment: Text.AlignVCenter
+ visible: (namesModel.count < 5)
+ enabled: visible
+ focus: true
+ font.pixelSize: 15
+ font.family: Theme.palette.baseFont.name
+ color: Theme.palette.directColor1
+ Keys.onPressed: {
+ if ((event.key === Qt.Key_Backspace || event.key === Qt.Key_Escape)
+ && getText(cursorPosition, (cursorPosition-1)) === "") {
+ namesModel.remove((namesList.count-1), 1);
+ }
+ }
+ }
+
+ StatusBaseText {
+ id: warningTextLabel
+ visible: (namesModel.count === 5)
+ Layout.preferredWidth: 120
+ Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
+ font.pixelSize: 10
+ color: Theme.palette.dangerColor1
+ text: root.warningText
+ }
+ }
+ }
+}
diff --git a/ui/StatusQ/src/StatusQ/Components/qmldir b/ui/StatusQ/src/StatusQ/Components/qmldir
index 568befdd14..51c84c918e 100644
--- a/ui/StatusQ/src/StatusQ/Components/qmldir
+++ b/ui/StatusQ/src/StatusQ/Components/qmldir
@@ -26,3 +26,4 @@ StatusExpandableItem 0.1 StatusExpandableItem.qml
StatusSmartIdenticon 0.1 StatusSmartIdenticon.qml
StatusMessage 0.1 StatusMessage.qml
StatusMessageDetails 0.1 StatusMessageDetails.qml
+StatusTagSelector 0.1 StatusTagSelector.qml
diff --git a/ui/StatusQ/statusq.qrc b/ui/StatusQ/statusq.qrc
index 5834670378..3e118610f0 100644
--- a/ui/StatusQ/statusq.qrc
+++ b/ui/StatusQ/statusq.qrc
@@ -320,5 +320,6 @@
src/StatusQ/Controls/StatusProgressBar.qml
src/StatusQ/Controls/StatusPasswordStrengthIndicator.qml
src/StatusQ/Components/StatusMemberListItem.qml
+ src/StatusQ/Components/StatusTagSelector.qml