mirror of
https://github.com/status-im/status-desktop.git
synced 2025-02-23 12:08:53 +00:00
feat: user list
This commit is contained in:
parent
189d761d20
commit
2fcbe4ac16
@ -7,6 +7,7 @@ import ../../../status/profile/profile
|
||||
import ../../../status/ens
|
||||
import strutils
|
||||
import message_format
|
||||
import user_list
|
||||
|
||||
type
|
||||
ChatMessageRoles {.pure.} = enum
|
||||
@ -55,6 +56,7 @@ QtObject:
|
||||
isEdited*: Table[string, bool]
|
||||
messageReactions*: Table[string, string]
|
||||
timedoutMessages: HashSet[string]
|
||||
userList: UserListView
|
||||
|
||||
proc delete(self: ChatMessageList) =
|
||||
self.messages = @[]
|
||||
@ -90,6 +92,7 @@ QtObject:
|
||||
result.messageIndex = initTable[string, int]()
|
||||
result.timedoutMessages = initHashSet[string]()
|
||||
result.isEdited = initTable[string, bool]()
|
||||
result.userList = newUserListView(status)
|
||||
result.status = status
|
||||
result.setup
|
||||
|
||||
@ -292,6 +295,7 @@ QtObject:
|
||||
self.beginInsertRows(newQModelIndex(), self.messages.len, self.messages.len)
|
||||
self.messageIndex[message.id] = self.messages.len
|
||||
self.messages.add(message)
|
||||
self.userList.add(message)
|
||||
self.countChanged()
|
||||
self.endInsertRows()
|
||||
|
||||
@ -301,6 +305,7 @@ QtObject:
|
||||
if self.messageIndex.hasKey(message.id): continue
|
||||
self.messageIndex[message.id] = self.messages.len
|
||||
self.messages.add(message)
|
||||
self.userList.add(message)
|
||||
self.countChanged()
|
||||
self.endInsertRows()
|
||||
|
||||
@ -364,6 +369,7 @@ QtObject:
|
||||
m.userName = userNameOrAlias(c)
|
||||
m.alias = c.alias
|
||||
m.localName = c.localNickname
|
||||
self.userList.updateUsernames(c.id, m.username, m.alias, m.localname)
|
||||
|
||||
self.dataChanged(topLeft, bottomRight, @[ChatMessageRoles.Username.int])
|
||||
|
||||
@ -376,4 +382,10 @@ QtObject:
|
||||
self.deleteMessage(m)
|
||||
|
||||
proc getID*(self: ChatMessageList):string {.slot.} =
|
||||
self.id
|
||||
self.id
|
||||
|
||||
proc getUserList*(self: ChatMessageList): QVariant {.slot.} =
|
||||
newQVariant(self.userList)
|
||||
|
||||
QtProperty[QVariant] userList:
|
||||
read = getUserList
|
118
src/app/chat/views/user_list.nim
Normal file
118
src/app/chat/views/user_list.nim
Normal file
@ -0,0 +1,118 @@
|
||||
import NimQml, Tables, json, chronicles, sequtils
|
||||
import ../../../status/status
|
||||
import ../../../status/accounts
|
||||
import ../../../status/chat
|
||||
import ../../../status/chat/[message]
|
||||
import strutils
|
||||
|
||||
type
|
||||
UserListRoles {.pure.} = enum
|
||||
UserName = UserRole + 1
|
||||
LastSeen = UserRole + 2
|
||||
PublicKey = UserRole + 3
|
||||
Alias = UserRole + 4
|
||||
LocalName = UserRole + 5
|
||||
Identicon = UserRole + 6
|
||||
|
||||
User = object
|
||||
username: string
|
||||
alias: string
|
||||
localName: string
|
||||
lastSeen: string
|
||||
identicon: string
|
||||
|
||||
QtObject:
|
||||
type
|
||||
UserListView* = ref object of QAbstractListModel
|
||||
status: Status
|
||||
users: seq[string]
|
||||
userDetails: OrderedTable[string, User]
|
||||
|
||||
proc delete(self: UserListView) =
|
||||
self.userDetails.clear()
|
||||
self.QAbstractListModel.delete
|
||||
|
||||
proc setup(self: UserListView) =
|
||||
self.QAbstractListModel.setup
|
||||
|
||||
proc newUserListView*(status: Status): UserListView =
|
||||
new(result, delete)
|
||||
result.userDetails = initOrderedTable[string, User]()
|
||||
result.users = @[]
|
||||
result.status = status
|
||||
result.setup
|
||||
|
||||
method rowCount*(self: UserListView, index: QModelIndex = nil): int = self.users.len
|
||||
|
||||
method data(self: UserListView, index: QModelIndex, role: int): QVariant =
|
||||
if not index.isValid:
|
||||
return
|
||||
if index.row < 0 or index.row >= self.users.len:
|
||||
return
|
||||
|
||||
let user = self.users[index.row]
|
||||
|
||||
case role.UserListRoles:
|
||||
of UserListRoles.UserName: result = newQVariant(self.userDetails[user].userName)
|
||||
of UserListRoles.LastSeen: result = newQVariant(self.userDetails[user].lastSeen)
|
||||
of UserListRoles.Alias: result = newQVariant(self.userDetails[user].alias)
|
||||
of UserListRoles.LocalName: result = newQVariant(self.userDetails[user].localName)
|
||||
of UserListRoles.PublicKey: result = newQVariant(user)
|
||||
of UserListRoles.Identicon: result = newQVariant(self.userdetails[user].identicon)
|
||||
|
||||
method roleNames(self: UserListView): Table[int, string] =
|
||||
{
|
||||
UserListRoles.UserName.int:"userName",
|
||||
UserListRoles.LastSeen.int:"lastSeen",
|
||||
UserListRoles.PublicKey.int:"publicKey",
|
||||
UserListRoles.Alias.int:"alias",
|
||||
UserListRoles.LocalName.int:"localName",
|
||||
UserListRoles.Identicon.int:"identicon"
|
||||
}.toTable
|
||||
|
||||
proc add*(self: UserListView, message: Message) =
|
||||
if self.userDetails.hasKey(message.fromAuthor):
|
||||
self.beginResetModel()
|
||||
self.userDetails[message.fromAuthor] = User(
|
||||
userName: message.userName,
|
||||
alias: message.alias,
|
||||
localName: message.localName,
|
||||
lastSeen: message.timestamp,
|
||||
identicon: message.identicon
|
||||
)
|
||||
self.endResetModel()
|
||||
else:
|
||||
self.beginInsertRows(newQModelIndex(), self.users.len, self.users.len)
|
||||
self.userDetails[message.fromAuthor] = User(
|
||||
userName: message.userName,
|
||||
alias: message.alias,
|
||||
localName: message.localName,
|
||||
lastSeen: message.timestamp,
|
||||
identicon: message.identicon
|
||||
)
|
||||
self.users.add(message.fromAuthor)
|
||||
self.endInsertRows()
|
||||
|
||||
proc triggerUpdate*(self: UserListView) {.slot.} =
|
||||
self.beginResetModel()
|
||||
self.endResetModel()
|
||||
|
||||
proc updateUsernames*(self: UserListView, publicKey, userName, alias, localName: string) =
|
||||
if not self.userDetails.hasKey(publicKey): return
|
||||
|
||||
var i = -1
|
||||
var found = -1
|
||||
for u in self.users:
|
||||
i = i + 1
|
||||
if u == publicKey:
|
||||
found = i
|
||||
|
||||
if found == -1: return
|
||||
|
||||
self.userDetails[publicKey].username = userName
|
||||
self.userDetails[publicKey].alias = alias
|
||||
self.userDetails[publicKey].localName = localName
|
||||
|
||||
let topLeft = self.createIndex(found, 0, nil)
|
||||
let bottomRight = self.createIndex(found, 0, nil)
|
||||
self.dataChanged(topLeft, bottomRight, @[UserListRoles.Username.int, UserListRoles.Alias.int, UserListRoles.Localname.int])
|
@ -25,6 +25,8 @@ StackLayout {
|
||||
|
||||
property bool isConnected: false
|
||||
property string contactToRemove: ""
|
||||
|
||||
property bool showUsers: false
|
||||
|
||||
property var doNotShowAddToContactBannerToThose: ([])
|
||||
|
||||
@ -64,6 +66,17 @@ StackLayout {
|
||||
chatInput.textInput.forceActiveFocus(Qt.MouseFocusReason)
|
||||
}
|
||||
|
||||
property var currentTime: 0
|
||||
|
||||
Timer {
|
||||
interval: 60000; // 1 min
|
||||
running: true;
|
||||
repeat: true
|
||||
onTriggered: {
|
||||
chatColumnLayout.currentTime = Date.now()
|
||||
}
|
||||
}
|
||||
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
Layout.minimumWidth: 300
|
||||
@ -266,6 +279,7 @@ StackLayout {
|
||||
sourceComponent: ChatMessages {
|
||||
id: chatMessages
|
||||
messageList: model.messages
|
||||
currentTime: chatColumnLayout.currentTime
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,10 +12,10 @@ import "../../../../imports"
|
||||
import "../components"
|
||||
import "./samples/"
|
||||
import "./MessageComponents"
|
||||
import "../ContactsColumn"
|
||||
|
||||
ScrollView {
|
||||
id: root
|
||||
|
||||
SplitView {
|
||||
id: svRoot
|
||||
property alias chatLogView: chatLogView
|
||||
property alias scrollToMessage: chatLogView.scrollToMessage
|
||||
|
||||
@ -23,337 +23,387 @@ ScrollView {
|
||||
property bool loadingMessages: false
|
||||
property real scrollY: chatLogView.visibleArea.yPosition * chatLogView.contentHeight
|
||||
property int newMessages: 0
|
||||
property var currentTime
|
||||
|
||||
contentItem: chatLogView
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
height: parent.height
|
||||
ScrollBar.vertical.policy: chatLogView.contentHeight > chatLogView.height ? ScrollBar.AlwaysOn : ScrollBar.AlwaysOff
|
||||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||
handle: SplitViewHandle { implicitWidth: 5}
|
||||
|
||||
ListView {
|
||||
id: chatLogView
|
||||
anchors.fill: parent
|
||||
anchors.bottomMargin: Style.current.bigPadding
|
||||
spacing: appSettings.useCompactMode ? 0 : 4
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
flickDeceleration: {
|
||||
if (utilsModel.getOs() === Constants.windows) {
|
||||
return 5000
|
||||
ScrollView {
|
||||
id: root
|
||||
|
||||
contentItem: chatLogView
|
||||
|
||||
SplitView.fillWidth: true
|
||||
SplitView.minimumWidth: 200
|
||||
|
||||
ScrollBar.vertical.policy: chatLogView.contentHeight > chatLogView.height ? ScrollBar.AlwaysOn : ScrollBar.AlwaysOff
|
||||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||
|
||||
ListView {
|
||||
id: chatLogView
|
||||
anchors.fill: parent
|
||||
anchors.bottomMargin: Style.current.bigPadding
|
||||
spacing: appSettings.useCompactMode ? 0 : 4
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
flickDeceleration: {
|
||||
if (utilsModel.getOs() === Constants.windows) {
|
||||
return 5000
|
||||
}
|
||||
return 10000
|
||||
}
|
||||
return 10000
|
||||
}
|
||||
verticalLayoutDirection: ListView.BottomToTop
|
||||
verticalLayoutDirection: ListView.BottomToTop
|
||||
|
||||
// This header and Connections is to create an invisible padding so that the chat identifier is at the top
|
||||
// The Connections is necessary, because doing the check inside the header created a binding loop (the contentHeight includes the header height
|
||||
// If the content height is smaller than the full height, we "show" the padding so that the chat identifier is at the top, otherwise we disable the Connections
|
||||
header: Item {
|
||||
height: 0
|
||||
width: chatLogView.width
|
||||
}
|
||||
function checkHeaderHeight() {
|
||||
if (!chatLogView.headerItem) {
|
||||
return
|
||||
// This header and Connections is to create an invisible padding so that the chat identifier is at the top
|
||||
// The Connections is necessary, because doing the check inside the header created a binding loop (the contentHeight includes the header height
|
||||
// If the content height is smaller than the full height, we "show" the padding so that the chat identifier is at the top, otherwise we disable the Connections
|
||||
header: Item {
|
||||
height: 0
|
||||
width: chatLogView.width
|
||||
}
|
||||
|
||||
if (chatLogView.contentItem.height - chatLogView.headerItem.height < chatLogView.height) {
|
||||
chatLogView.headerItem.height = chatLogView.height - (chatLogView.contentItem.height - chatLogView.headerItem.height) - 36
|
||||
} else {
|
||||
chatLogView.headerItem.height = 0
|
||||
}
|
||||
}
|
||||
|
||||
property var scrollToMessage: function (messageId) {
|
||||
let item
|
||||
for (let i = 0; i < messageListDelegate.count; i++) {
|
||||
item = messageListDelegate.items.get(i)
|
||||
if (item.model.messageId === messageId) {
|
||||
chatLogView.positionViewAtIndex(i, ListView.Center)
|
||||
function checkHeaderHeight() {
|
||||
if (!chatLogView.headerItem) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
id: contentHeightConnection
|
||||
enabled: true
|
||||
target: chatLogView
|
||||
onContentHeightChanged: {
|
||||
chatLogView.checkHeaderHeight()
|
||||
}
|
||||
onHeightChanged: {
|
||||
chatLogView.checkHeaderHeight()
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: timer
|
||||
}
|
||||
|
||||
Button {
|
||||
readonly property int buttonPadding: 5
|
||||
|
||||
id: scrollDownButton
|
||||
visible: false
|
||||
height: 32
|
||||
width: nbMessages.width + arrowImage.width + 2 * Style.current.halfPadding + (nbMessages.visible ? scrollDownButton.buttonPadding : 0)
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Style.current.padding
|
||||
background: Rectangle {
|
||||
color: Style.current.buttonSecondaryColor
|
||||
border.width: 0
|
||||
radius: 16
|
||||
}
|
||||
onClicked: {
|
||||
root.newMessages = 0
|
||||
scrollDownButton.visible = false
|
||||
chatLogView.scrollToBottom(true)
|
||||
}
|
||||
|
||||
StyledText {
|
||||
id: nbMessages
|
||||
visible: root.newMessages > 0
|
||||
width: visible ? implicitWidth : 0
|
||||
text: root.newMessages
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: parent.left
|
||||
color: Style.current.pillButtonTextColor
|
||||
font.pixelSize: 15
|
||||
anchors.leftMargin: Style.current.halfPadding
|
||||
}
|
||||
|
||||
SVGImage {
|
||||
id: arrowImage
|
||||
width: 24
|
||||
height: 24
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: nbMessages.right
|
||||
source: "../../../img/leave_chat.svg"
|
||||
anchors.leftMargin: nbMessages.visible ? scrollDownButton.buttonPadding : 0
|
||||
rotation: -90
|
||||
|
||||
ColorOverlay {
|
||||
anchors.fill: parent
|
||||
source: parent
|
||||
color: Style.current.pillButtonTextColor
|
||||
if (chatLogView.contentItem.height - chatLogView.headerItem.height < chatLogView.height) {
|
||||
chatLogView.headerItem.height = chatLogView.height - (chatLogView.contentItem.height - chatLogView.headerItem.height) - 36
|
||||
} else {
|
||||
chatLogView.headerItem.height = 0
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
anchors.fill: parent
|
||||
onPressed: mouse.accepted = false
|
||||
}
|
||||
}
|
||||
|
||||
function scrollToBottom(force, caller) {
|
||||
if (!force && !chatLogView.atYEnd) {
|
||||
// User has scrolled up, we don't want to scroll back
|
||||
return false
|
||||
}
|
||||
if (caller && caller !== chatLogView.itemAtIndex(chatLogView.count - 1)) {
|
||||
// If we have a caller, only accept its request if it's the last message
|
||||
return false
|
||||
}
|
||||
// Call this twice and with a timer since the first scroll to bottom might have happened before some stuff loads
|
||||
// meaning that the scroll will not actually be at the bottom on switch
|
||||
// Add a small delay because images, even though they say they say they are loaed, they aren't shown yet
|
||||
Qt.callLater(chatLogView.positionViewAtBeginning)
|
||||
timer.setTimeout(function() {
|
||||
Qt.callLater(chatLogView.positionViewAtBeginning)
|
||||
}, 100);
|
||||
return true
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: chatsModel
|
||||
|
||||
onAppReady: {
|
||||
chatLogView.scrollToBottom(true)
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: chatsModel.messageView
|
||||
onMessagesLoaded: {
|
||||
loadingMessages = false;
|
||||
}
|
||||
|
||||
onSendingMessage: {
|
||||
chatLogView.scrollToBottom(true)
|
||||
}
|
||||
|
||||
onSendingMessageFailed: {
|
||||
sendingMsgFailedPopup.open();
|
||||
}
|
||||
|
||||
onNewMessagePushed: {
|
||||
if (!chatLogView.scrollToBottom()) {
|
||||
root.newMessages++
|
||||
}
|
||||
}
|
||||
|
||||
onMessageNotificationPushed: function(chatId, msg, messageType, chatType, timestamp, identicon, username, hasMention, isAddedContact, channelName) {
|
||||
if (messageType == Constants.editType) return;
|
||||
if (appSettings.notificationSetting == Constants.notifyAllMessages ||
|
||||
(appSettings.notificationSetting == Constants.notifyJustMentions && hasMention)) {
|
||||
if (chatId === chatsModel.channelView.activeChannel.id && applicationWindow.active === true) {
|
||||
// Do not show the notif if we are in the channel already and the window is active and focused
|
||||
property var scrollToMessage: function (messageId) {
|
||||
let item
|
||||
for (let i = 0; i < messageListDelegate.count; i++) {
|
||||
item = messageListDelegate.items.get(i)
|
||||
if (item.model.messageId === messageId) {
|
||||
chatLogView.positionViewAtIndex(i, ListView.Center)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
chatColumnLayout.currentNotificationChatId = chatId
|
||||
chatColumnLayout.currentNotificationCommunityId = null
|
||||
Connections {
|
||||
id: contentHeightConnection
|
||||
enabled: true
|
||||
target: chatLogView
|
||||
onContentHeightChanged: {
|
||||
chatLogView.checkHeaderHeight()
|
||||
}
|
||||
onHeightChanged: {
|
||||
chatLogView.checkHeaderHeight()
|
||||
}
|
||||
}
|
||||
|
||||
let name;
|
||||
if (appSettings.notificationMessagePreviewSetting === Constants.notificationPreviewAnonymous) {
|
||||
name = "Status"
|
||||
} else if (chatType === Constants.chatTypePublic) {
|
||||
name = chatId
|
||||
} else {
|
||||
name = chatType === Constants.chatTypePrivateGroupChat ? Utils.filterXSS(channelName) : Utils.removeStatusEns(username)
|
||||
Timer {
|
||||
id: timer
|
||||
}
|
||||
|
||||
Button {
|
||||
readonly property int buttonPadding: 5
|
||||
|
||||
id: scrollDownButton
|
||||
visible: false
|
||||
height: 32
|
||||
width: nbMessages.width + arrowImage.width + 2 * Style.current.halfPadding + (nbMessages.visible ? scrollDownButton.buttonPadding : 0)
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Style.current.padding
|
||||
background: Rectangle {
|
||||
color: Style.current.buttonSecondaryColor
|
||||
border.width: 0
|
||||
radius: 16
|
||||
}
|
||||
onClicked: {
|
||||
root.newMessages = 0
|
||||
scrollDownButton.visible = false
|
||||
chatLogView.scrollToBottom(true)
|
||||
}
|
||||
|
||||
StyledText {
|
||||
id: nbMessages
|
||||
visible: root.newMessages > 0
|
||||
width: visible ? implicitWidth : 0
|
||||
text: root.newMessages
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: parent.left
|
||||
color: Style.current.pillButtonTextColor
|
||||
font.pixelSize: 15
|
||||
anchors.leftMargin: Style.current.halfPadding
|
||||
}
|
||||
|
||||
SVGImage {
|
||||
id: arrowImage
|
||||
width: 24
|
||||
height: 24
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: nbMessages.right
|
||||
source: "../../../img/leave_chat.svg"
|
||||
anchors.leftMargin: nbMessages.visible ? scrollDownButton.buttonPadding : 0
|
||||
rotation: -90
|
||||
|
||||
ColorOverlay {
|
||||
anchors.fill: parent
|
||||
source: parent
|
||||
color: Style.current.pillButtonTextColor
|
||||
}
|
||||
}
|
||||
|
||||
let message;
|
||||
if (appSettings.notificationMessagePreviewSetting > Constants.notificationPreviewNameOnly) {
|
||||
switch(messageType){
|
||||
//% "Image"
|
||||
case Constants.imageType: message = qsTrId("image"); break
|
||||
//% "Sticker"
|
||||
case Constants.stickerType: message = qsTrId("sticker"); break
|
||||
default: message = msg // don't parse emojis here as it emits HTML
|
||||
MouseArea {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
anchors.fill: parent
|
||||
onPressed: mouse.accepted = false
|
||||
}
|
||||
}
|
||||
|
||||
function scrollToBottom(force, caller) {
|
||||
if (!force && !chatLogView.atYEnd) {
|
||||
// User has scrolled up, we don't want to scroll back
|
||||
return false
|
||||
}
|
||||
if (caller && caller !== chatLogView.itemAtIndex(chatLogView.count - 1)) {
|
||||
// If we have a caller, only accept its request if it's the last message
|
||||
return false
|
||||
}
|
||||
// Call this twice and with a timer since the first scroll to bottom might have happened before some stuff loads
|
||||
// meaning that the scroll will not actually be at the bottom on switch
|
||||
// Add a small delay because images, even though they say they say they are loaed, they aren't shown yet
|
||||
Qt.callLater(chatLogView.positionViewAtBeginning)
|
||||
timer.setTimeout(function() {
|
||||
Qt.callLater(chatLogView.positionViewAtBeginning)
|
||||
}, 100);
|
||||
return true
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: chatsModel.messageView
|
||||
onMessagesLoaded: {
|
||||
loadingMessages = false;
|
||||
}
|
||||
|
||||
onSendingMessage: {
|
||||
chatLogView.scrollToBottom(true)
|
||||
}
|
||||
|
||||
onSendingMessageFailed: {
|
||||
sendingMsgFailedPopup.open();
|
||||
}
|
||||
|
||||
onNewMessagePushed: {
|
||||
if (!chatLogView.scrollToBottom()) {
|
||||
root.newMessages++
|
||||
}
|
||||
}
|
||||
|
||||
onAppReady: {
|
||||
chatLogView.scrollToBottom(true)
|
||||
}
|
||||
|
||||
onMessageNotificationPushed: function(chatId, msg, messageType, chatType, timestamp, identicon, username, hasMention, isAddedContact, channelName) {
|
||||
if (messageType == Constants.editType) return;
|
||||
if (appSettings.notificationSetting == Constants.notifyAllMessages ||
|
||||
(appSettings.notificationSetting == Constants.notifyJustMentions && hasMention)) {
|
||||
if (chatId === chatsModel.channelView.activeChannel.id && applicationWindow.active === true) {
|
||||
// Do not show the notif if we are in the channel already and the window is active and focused
|
||||
return
|
||||
}
|
||||
} else {
|
||||
//% "You have a new message"
|
||||
message = qsTrId("you-have-a-new-message")
|
||||
}
|
||||
|
||||
currentlyHasANotification = true
|
||||
if (appSettings.useOSNotifications && systemTray.supportsMessages) {
|
||||
systemTray.showMessage(name,
|
||||
message,
|
||||
SystemTrayIcon.NoIcon,
|
||||
Constants.notificationPopupTTL)
|
||||
} else {
|
||||
notificationWindow.notifyUser(chatId, name, message, chatType, identicon, chatColumnLayout.clickOnNotification)
|
||||
chatColumnLayout.currentNotificationChatId = chatId
|
||||
chatColumnLayout.currentNotificationCommunityId = null
|
||||
|
||||
let name;
|
||||
if (appSettings.notificationMessagePreviewSetting === Constants.notificationPreviewAnonymous) {
|
||||
name = "Status"
|
||||
} else if (chatType === Constants.chatTypePublic) {
|
||||
name = chatId
|
||||
} else {
|
||||
name = chatType === Constants.chatTypePrivateGroupChat ? Utils.filterXSS(channelName) : Utils.removeStatusEns(username)
|
||||
}
|
||||
|
||||
let message;
|
||||
if (appSettings.notificationMessagePreviewSetting > Constants.notificationPreviewNameOnly) {
|
||||
switch(messageType){
|
||||
//% "Image"
|
||||
case Constants.imageType: message = qsTrId("image"); break
|
||||
//% "Sticker"
|
||||
case Constants.stickerType: message = qsTrId("sticker"); break
|
||||
default: message = msg // don't parse emojis here as it emits HTML
|
||||
}
|
||||
} else {
|
||||
//% "You have a new message"
|
||||
message = qsTrId("you-have-a-new-message")
|
||||
}
|
||||
|
||||
currentlyHasANotification = true
|
||||
if (appSettings.useOSNotifications && systemTray.supportsMessages) {
|
||||
systemTray.showMessage(name,
|
||||
message,
|
||||
SystemTrayIcon.NoIcon,
|
||||
Constants.notificationPopupTTL)
|
||||
} else {
|
||||
notificationWindow.notifyUser(chatId, name, message, chatType, identicon, chatColumnLayout.clickOnNotification)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: chatsModel.communities
|
||||
Connections {
|
||||
target: chatsModel.communities
|
||||
|
||||
onMembershipRequestChanged: function (communityId, communityName, accepted) {
|
||||
chatColumnLayout.currentNotificationChatId = null
|
||||
chatColumnLayout.currentNotificationCommunityId = communityId
|
||||
systemTray.showMessage("Status",
|
||||
accepted ? qsTr("You have been accepted into the ‘%1’ community").arg(communityName) :
|
||||
qsTr("Your request to join the ‘%1’ community was declined").arg(communityName),
|
||||
SystemTrayIcon.NoIcon,
|
||||
Constants.notificationPopupTTL)
|
||||
onMembershipRequestChanged: function (communityId, communityName, accepted) {
|
||||
chatColumnLayout.currentNotificationChatId = null
|
||||
chatColumnLayout.currentNotificationCommunityId = communityId
|
||||
systemTray.showMessage("Status",
|
||||
accepted ? qsTr("You have been accepted into the ‘%1’ community").arg(communityName) :
|
||||
qsTr("Your request to join the ‘%1’ community was declined").arg(communityName),
|
||||
SystemTrayIcon.NoIcon,
|
||||
Constants.notificationPopupTTL)
|
||||
}
|
||||
|
||||
onMembershipRequestPushed: function (communityId, communityName, pubKey) {
|
||||
chatColumnLayout.currentNotificationChatId = null
|
||||
chatColumnLayout.currentNotificationCommunityId = communityId
|
||||
systemTray.showMessage(qsTr("New membership request"),
|
||||
qsTr("%1 asks to join ‘%2’").arg(Utils.getDisplayName(pubKey)).arg(communityName),
|
||||
SystemTrayIcon.NoIcon,
|
||||
Constants.notificationPopupTTL)
|
||||
}
|
||||
}
|
||||
|
||||
onMembershipRequestPushed: function (communityId, communityName, pubKey) {
|
||||
chatColumnLayout.currentNotificationChatId = null
|
||||
chatColumnLayout.currentNotificationCommunityId = communityId
|
||||
systemTray.showMessage(qsTr("New membership request"),
|
||||
qsTr("%1 asks to join ‘%2’").arg(Utils.getDisplayName(pubKey)).arg(communityName),
|
||||
SystemTrayIcon.NoIcon,
|
||||
Constants.notificationPopupTTL)
|
||||
property var loadMsgs : Backpressure.oneInTime(chatLogView, 500, function() {
|
||||
if(loadingMessages) return;
|
||||
loadingMessages = true;
|
||||
chatsModel.messageView.loadMoreMessages();
|
||||
});
|
||||
|
||||
onContentYChanged: {
|
||||
scrollDownButton.visible = (contentHeight - (scrollY + height) > 400)
|
||||
if(scrollY < 500){
|
||||
loadMsgs();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
model: messageListDelegate
|
||||
section.property: "sectionIdentifier"
|
||||
section.criteria: ViewSection.FullString
|
||||
|
||||
}
|
||||
|
||||
property var loadMsgs : Backpressure.oneInTime(chatLogView, 500, function() {
|
||||
if(loadingMessages) return;
|
||||
loadingMessages = true;
|
||||
chatsModel.messageView.loadMoreMessages();
|
||||
});
|
||||
|
||||
onContentYChanged: {
|
||||
scrollDownButton.visible = (contentHeight - (scrollY + height) > 400)
|
||||
if(scrollY < 500){
|
||||
loadMsgs();
|
||||
}
|
||||
MessageDialog {
|
||||
id: sendingMsgFailedPopup
|
||||
standardButtons: StandardButton.Ok
|
||||
//% "Failed to send message."
|
||||
text: qsTrId("failed-to-send-message-")
|
||||
icon: StandardIcon.Critical
|
||||
}
|
||||
|
||||
DelegateModelGeneralized {
|
||||
id: messageListDelegate
|
||||
lessThan: [
|
||||
function(left, right) { return left.clock > right.clock }
|
||||
]
|
||||
|
||||
model: messageListDelegate
|
||||
section.property: "sectionIdentifier"
|
||||
section.criteria: ViewSection.FullString
|
||||
model: messageList
|
||||
|
||||
}
|
||||
delegate: Message {
|
||||
id: msgDelegate
|
||||
fromAuthor: model.fromAuthor
|
||||
chatId: model.chatId
|
||||
userName: model.userName
|
||||
alias: model.alias
|
||||
localName: model.localName
|
||||
message: model.message
|
||||
plainText: model.plainText
|
||||
identicon: model.identicon
|
||||
isCurrentUser: model.isCurrentUser
|
||||
timestamp: model.timestamp
|
||||
sticker: model.sticker
|
||||
contentType: model.contentType
|
||||
replaces: model.replaces
|
||||
isEdited: model.isEdited
|
||||
outgoingStatus: model.outgoingStatus
|
||||
responseTo: model.responseTo
|
||||
authorCurrentMsg: msgDelegate.ListView.section
|
||||
// The previous message is actually the nextSection since we reversed the list order
|
||||
authorPrevMsg: msgDelegate.ListView.nextSection
|
||||
imageClick: imagePopup.openPopup.bind(imagePopup)
|
||||
messageId: model.messageId
|
||||
emojiReactions: model.emojiReactions
|
||||
linkUrls: model.linkUrls
|
||||
communityId: model.communityId
|
||||
hasMention: model.hasMention
|
||||
stickerPackId: model.stickerPackId
|
||||
pinnedMessage: model.isPinned
|
||||
pinnedBy: model.pinnedBy
|
||||
gapFrom: model.gapFrom
|
||||
gapTo: model.gapTo
|
||||
|
||||
MessageDialog {
|
||||
id: sendingMsgFailedPopup
|
||||
standardButtons: StandardButton.Ok
|
||||
//% "Failed to send message."
|
||||
text: qsTrId("failed-to-send-message-")
|
||||
icon: StandardIcon.Critical
|
||||
}
|
||||
|
||||
DelegateModelGeneralized {
|
||||
id: messageListDelegate
|
||||
lessThan: [
|
||||
function(left, right) { return left.clock > right.clock }
|
||||
]
|
||||
|
||||
model: messageList
|
||||
|
||||
delegate: Message {
|
||||
id: msgDelegate
|
||||
fromAuthor: model.fromAuthor
|
||||
chatId: model.chatId
|
||||
userName: model.userName
|
||||
alias: model.alias
|
||||
localName: model.localName
|
||||
message: model.message
|
||||
plainText: model.plainText
|
||||
identicon: model.identicon
|
||||
isCurrentUser: model.isCurrentUser
|
||||
timestamp: model.timestamp
|
||||
sticker: model.sticker
|
||||
contentType: model.contentType
|
||||
outgoingStatus: model.outgoingStatus
|
||||
replaces: model.replaces
|
||||
isEdited: model.isEdited
|
||||
responseTo: model.responseTo
|
||||
authorCurrentMsg: msgDelegate.ListView.section
|
||||
// The previous message is actually the nextSection since we reversed the list order
|
||||
authorPrevMsg: msgDelegate.ListView.nextSection
|
||||
imageClick: imagePopup.openPopup.bind(imagePopup)
|
||||
messageId: model.messageId
|
||||
emojiReactions: model.emojiReactions
|
||||
linkUrls: model.linkUrls
|
||||
communityId: model.communityId
|
||||
hasMention: model.hasMention
|
||||
stickerPackId: model.stickerPackId
|
||||
pinnedMessage: model.isPinned
|
||||
pinnedBy: model.pinnedBy
|
||||
gapFrom: model.gapFrom
|
||||
gapTo: model.gapTo
|
||||
|
||||
// This is used in order to have access to the previous message and determine the timestamp
|
||||
// we can't rely on the index because the sequence of messages is not ordered on the nim side
|
||||
prevMessageIndex: {
|
||||
// This is used in order to have access to the previous message and determine the timestamp
|
||||
// we can't rely on the index because the sequence of messages is not ordered on the nim side
|
||||
if (msgDelegate.DelegateModel.itemsIndex < messageListDelegate.items.count - 1) {
|
||||
return messageListDelegate.items.get(msgDelegate.DelegateModel.itemsIndex + 1).model.index
|
||||
prevMessageIndex: {
|
||||
// This is used in order to have access to the previous message and determine the timestamp
|
||||
// we can't rely on the index because the sequence of messages is not ordered on the nim side
|
||||
if (msgDelegate.DelegateModel.itemsIndex < messageListDelegate.items.count - 1) {
|
||||
return messageListDelegate.items.get(msgDelegate.DelegateModel.itemsIndex + 1).model.index
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
nextMessageIndex: {
|
||||
if (msgDelegate.DelegateModel.itemsIndex <= 1) {
|
||||
return -1
|
||||
nextMessageIndex: {
|
||||
if (msgDelegate.DelegateModel.itemsIndex <= 1) {
|
||||
return -1
|
||||
}
|
||||
return messageListDelegate.items.get(msgDelegate.DelegateModel.itemsIndex - 1).model.index
|
||||
}
|
||||
return messageListDelegate.items.get(msgDelegate.DelegateModel.itemsIndex - 1).model.index
|
||||
scrollToBottom: chatLogView.scrollToBottom
|
||||
timeout: model.timeout
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Rectangle {
|
||||
id: userList
|
||||
visible: showUsers && chatsModel.channelView.activeChannel.chatType !== Constants.chatTypeOneToOne
|
||||
|
||||
property int defaultWidth: 250
|
||||
|
||||
SplitView.preferredWidth: visible ? defaultWidth : 0
|
||||
SplitView.minimumWidth: 50
|
||||
|
||||
color: Style.current.secondaryMenuBackground
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
|
||||
height: childrenRect.height
|
||||
|
||||
ListView {
|
||||
id: userListView
|
||||
anchors.fill: parent
|
||||
anchors.bottomMargin: Style.current.bigPadding
|
||||
spacing: 0
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
model: userListDelegate
|
||||
}
|
||||
|
||||
DelegateModelGeneralized {
|
||||
id: userListDelegate
|
||||
lessThan: [
|
||||
function(left, right) {
|
||||
return left.lastSeen > right.lastSeen
|
||||
}
|
||||
]
|
||||
model: messageList.userList
|
||||
delegate: User {
|
||||
publicKey: model.publicKey
|
||||
name: model.userName
|
||||
identicon: model.identicon
|
||||
lastSeen: model.lastSeen
|
||||
currentTime: svRoot.currentTime
|
||||
}
|
||||
scrollToBottom: chatLogView.scrollToBottom
|
||||
timeout: model.timeout
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -70,6 +70,19 @@ Item {
|
||||
anchors.rightMargin: Style.current.smallPadding
|
||||
spacing: 12
|
||||
|
||||
StatusIconButton {
|
||||
id: showUsersBtn
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
icon.name: "channel-icon-group"
|
||||
iconColor: showUsers ? Style.current.contextMenuButtonForegroundColor : Style.current.contextMenuButtonBackgroundHoverColor
|
||||
hoveredIconColor: Style.current.contextMenuButtonForegroundColor
|
||||
highlightedBackgroundColor: Style.current.contextMenuButtonBackgroundHoverColor
|
||||
onClicked: {
|
||||
showUsers = !showUsers
|
||||
}
|
||||
visible: appSettings.showOnlineUsers && chatsModel.channelView.activeChannel.chatType !== Constants.chatTypeOneToOne
|
||||
}
|
||||
|
||||
StatusContextMenuButton {
|
||||
id: moreActionsBtn
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
125
ui/app/AppLayouts/Chat/ChatColumn/User.qml
Normal file
125
ui/app/AppLayouts/Chat/ChatColumn/User.qml
Normal file
@ -0,0 +1,125 @@
|
||||
import QtQuick 2.13
|
||||
import QtQuick.Controls 2.13
|
||||
import "../../../../shared"
|
||||
import "../../../../shared/status"
|
||||
import "../../../../imports"
|
||||
import "../components"
|
||||
|
||||
Item {
|
||||
property string publicKey: ""
|
||||
property string name: "channelName"
|
||||
property string lastSeen: ""
|
||||
property string identicon
|
||||
property bool hovered: false
|
||||
property bool enableMouseArea: true
|
||||
property var currentTime
|
||||
property color color: {
|
||||
if (wrapper.hovered) {
|
||||
return Style.current.menuBackgroundHover
|
||||
}
|
||||
return Style.current.transparent
|
||||
}
|
||||
|
||||
property string profileImage: appMain.getProfileImage(publicKey) || ""
|
||||
property bool isCurrentUser: publicKey === profileModel.profile.pubKey
|
||||
id: wrapper
|
||||
anchors.right: parent.right
|
||||
anchors.top: applicationWindow.top
|
||||
anchors.left: parent.left
|
||||
height: rectangle.height + 4
|
||||
|
||||
Rectangle {
|
||||
Connections {
|
||||
target: profileModel.contacts.list
|
||||
onContactChanged: {
|
||||
if (pubkey === wrapper.publicKey) {
|
||||
wrapper.profileImage = appMain.getProfileImage(wrapper.publicKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
id: rectangle
|
||||
color: wrapper.color
|
||||
radius: 8
|
||||
height: 40
|
||||
width: parent.width
|
||||
|
||||
StatusIdenticon {
|
||||
id: contactImage
|
||||
height: 28
|
||||
width: 28
|
||||
chatId: wrapper.publicKey
|
||||
chatName: wrapper.name
|
||||
chatType: Constants.chatTypeOneToOne
|
||||
identicon: wrapper.profileImage || wrapper.identicon
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Style.current.smallPadding
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
|
||||
StyledText {
|
||||
id: contactInfo
|
||||
text: Emoji.parse(Utils.removeStatusEns(Utils.filterXSS(wrapper.name))) + (isCurrentUser ? " " + qsTrId("(you)") : "")
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Style.current.smallPadding
|
||||
elide: Text.ElideRight
|
||||
color: Style.current.textColor
|
||||
font.weight: Font.Medium
|
||||
font.pixelSize: 15
|
||||
anchors.left: contactImage.right
|
||||
anchors.leftMargin: Style.current.halfPadding
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.left: contactImage.right
|
||||
anchors.leftMargin: -10
|
||||
anchors.bottom: contactImage.bottom
|
||||
height: 10
|
||||
width: 10
|
||||
radius: 20
|
||||
color: {
|
||||
let lastSeenMinutesAgo = (currentTime - parseInt(lastSeen)) / 1000 / 60
|
||||
|
||||
if (!chatsModel.isOnline) {
|
||||
return Style.current.darkGrey
|
||||
}
|
||||
|
||||
if (isCurrentUser || lastSeenMinutesAgo < 5){
|
||||
return Style.current.green;
|
||||
} else if (lastSeenMinutesAgo < 20) {
|
||||
return Style.current.orange
|
||||
}
|
||||
|
||||
return Style.current.darkGrey
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
enabled: enableMouseArea
|
||||
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: {
|
||||
console.log("TODO: do something")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*##^##
|
||||
Designer {
|
||||
D{i:0;formeditorColor:"#ffffff";height:64;width:640}
|
||||
}
|
||||
##^##*/
|
@ -123,6 +123,15 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
StatusSettingsLineButton {
|
||||
text: qsTr("Online users")
|
||||
isSwitch: true
|
||||
switchChecked: appSettings.showOnlineUsers
|
||||
onClicked: {
|
||||
appSettings.showOnlineUsers = !appSettings.showOnlineUsers
|
||||
}
|
||||
}
|
||||
|
||||
// StatusSettingsLineButton {
|
||||
// //% "Node Management"
|
||||
// text: qsTrId("node-management")
|
||||
|
@ -326,6 +326,7 @@ StatusAppLayout {
|
||||
property bool nodeManagementEnabled: false
|
||||
property bool isBrowserEnabled: false
|
||||
property bool isActivityCenterEnabled: false
|
||||
property bool showOnlineUsers: false
|
||||
property bool displayChatImages: false
|
||||
property bool useCompactMode: true
|
||||
property bool timelineEnabled: true
|
||||
|
@ -31,6 +31,7 @@ Theme {
|
||||
property color tenPercentWhite: Qt.rgba(255, 255, 255, 0.1)
|
||||
property color fivePercentBlack: "#E5E5E5"
|
||||
property color tenPercentBlue: Qt.rgba(67, 96, 223, 0.1)
|
||||
property color orange: "#FFA500"
|
||||
|
||||
property color background: "#2C2C2C"
|
||||
property color appBarDividerColor: darkGrey
|
||||
|
@ -31,6 +31,7 @@ Theme {
|
||||
property color tenPercentBlack: Qt.rgba(0, 0, 0, 0.1)
|
||||
property color fivePercentBlack: "#E5E5E5"
|
||||
property color tenPercentBlue: Qt.rgba(67, 96, 223, 0.1)
|
||||
property color orange: "#FFA500"
|
||||
|
||||
property color background: white
|
||||
property color appBarDividerColor: darkGrey
|
||||
|
Loading…
x
Reference in New Issue
Block a user