2020-06-17 19:18:31 +00:00
import QtQuick 2.13
2020-12-07 17:37:39 +00:00
import Qt . labs . platform 1.1
2020-06-17 19:18:31 +00:00
import QtQuick . Controls 2.13
2020-11-20 17:39:14 +00:00
import QtQuick . Window 2.13
2020-06-17 19:18:31 +00:00
import QtQuick . Layouts 1.13
import QtQml . Models 2.13
2020-09-24 15:05:17 +00:00
import QtGraphicalEffects 1.13
2020-11-30 21:24:01 +00:00
import QtQuick . Dialogs 1.3
2020-05-27 22:59:17 +00:00
import "../../../../shared"
2021-05-26 17:36:24 +00:00
import "../../../../shared/status"
2020-05-27 22:59:17 +00:00
import "../../../../imports"
2020-06-17 21:43:26 +00:00
import "../components"
2020-05-28 17:34:54 +00:00
import "./samples/"
2020-07-22 18:37:43 +00:00
import "./MessageComponents"
2020-05-27 22:59:17 +00:00
2020-05-28 22:22:51 +00:00
ScrollView {
2020-09-24 15:05:17 +00:00
id: root
2020-11-19 18:30:09 +00:00
property alias chatLogView: chatLogView
2021-06-10 19:20:43 +00:00
property alias scrollToMessage: chatLogView . scrollToMessage
2020-06-04 23:42:11 +00:00
2020-05-28 17:34:54 +00:00
property var messageList: MessagesData { }
2020-06-08 19:25:46 +00:00
property bool loadingMessages: false
2020-06-15 12:51:04 +00:00
property real scrollY: chatLogView . visibleArea . yPosition * chatLogView . contentHeight
2020-09-24 15:05:17 +00:00
property int newMessages: 0
2020-05-28 22:22:51 +00:00
2020-06-04 23:42:11 +00:00
contentItem: chatLogView
2020-05-27 22:59:17 +00:00
Layout.fillWidth: true
Layout.fillHeight: true
2021-04-07 16:28:49 +00:00
height: parent . height
2020-06-08 22:34:41 +00:00
ScrollBar.vertical.policy: chatLogView . contentHeight > chatLogView . height ? ScrollBar.AlwaysOn : ScrollBar . AlwaysOff
2020-05-28 22:22:51 +00:00
ScrollBar.horizontal.policy: ScrollBar . AlwaysOff
2020-06-04 23:42:11 +00:00
ListView {
2020-07-10 15:37:23 +00:00
id: chatLogView
2020-06-04 23:42:11 +00:00
anchors.fill: parent
2020-07-15 21:15:01 +00:00
anchors.bottomMargin: Style . current . bigPadding
2021-02-10 20:41:00 +00:00
spacing: appSettings . useCompactMode ? 0 : 4
2020-06-15 12:51:04 +00:00
boundsBehavior: Flickable . StopAtBounds
2021-03-30 14:35:53 +00:00
flickDeceleration: {
if ( utilsModel . getOs ( ) === Constants . windows ) {
return 5000
}
return 10000
}
2021-02-23 16:38:57 +00:00
verticalLayoutDirection: ListView . BottomToTop
2020-06-08 19:25:46 +00:00
2021-04-28 17:15:56 +00:00
// This header and Connections is to create an invisible padding so that the chat identifier is at the top
2021-05-26 17:36:24 +00:00
// The Connections is necessary, because doing the check inside the header created a binding loop (the contentHeight includes the header height
2021-04-28 17:15:56 +00:00
// 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
}
2021-05-26 17:36:24 +00:00
function checkHeaderHeight ( ) {
if ( ! chatLogView . headerItem ) {
return
}
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
}
}
2021-06-10 19:20:43 +00:00
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
}
}
}
2021-04-28 17:15:56 +00:00
Connections {
id: contentHeightConnection
enabled: true
target: chatLogView
onContentHeightChanged: {
2021-05-26 17:36:24 +00:00
chatLogView . checkHeaderHeight ( )
}
onHeightChanged: {
chatLogView . checkHeaderHeight ( )
2021-04-28 17:15:56 +00:00
}
}
2020-07-22 17:46:17 +00:00
Timer {
id: timer
}
2020-09-24 15:05:17 +00:00
Button {
2020-09-28 14:21:40 +00:00
readonly property int buttonPadding: 5
2020-09-29 16:52:23 +00:00
id: scrollDownButton
visible: false
2020-09-24 15:05:17 +00:00
height: 32
2020-09-29 16:52:23 +00:00
width: nbMessages . width + arrowImage . width + 2 * Style . current . halfPadding + ( nbMessages . visible ? scrollDownButton.buttonPadding : 0 )
2020-07-23 20:22:45 +00:00
anchors.bottom: parent . bottom
anchors.right: parent . right
anchors.rightMargin: Style . current . padding
2020-09-24 15:05:17 +00:00
background: Rectangle {
2020-09-28 14:21:40 +00:00
color: Style . current . buttonSecondaryColor
2020-09-24 15:05:17 +00:00
border.width: 0
radius: 16
}
onClicked: {
root . newMessages = 0
2020-09-29 16:52:23 +00:00
scrollDownButton . visible = false
2020-09-24 15:05:17 +00:00
chatLogView . scrollToBottom ( true )
}
2020-07-23 20:22:45 +00:00
StyledText {
2020-09-24 15:05:17 +00:00
id: nbMessages
visible: root . newMessages > 0
width: visible ? implicitWidth : 0
text: root . newMessages
anchors.verticalCenter: parent . verticalCenter
2020-07-23 20:22:45 +00:00
anchors.left: parent . left
2020-09-24 15:05:17 +00:00
color: Style . current . pillButtonTextColor
2020-07-23 20:22:45 +00:00
font.pixelSize: 15
2020-09-24 15:05:17 +00:00
anchors.leftMargin: Style . current . halfPadding
2020-07-23 20:22:45 +00:00
}
2020-09-24 15:05:17 +00:00
SVGImage {
id: arrowImage
width: 24
height: 24
anchors.verticalCenter: parent . verticalCenter
anchors.left: nbMessages . right
source: "../../../img/leave_chat.svg"
2020-09-29 16:52:23 +00:00
anchors.leftMargin: nbMessages . visible ? scrollDownButton.buttonPadding : 0
2020-09-24 15:05:17 +00:00
rotation: - 90
ColorOverlay {
anchors.fill: parent
source: parent
color: Style . current . pillButtonTextColor
}
2020-07-23 20:22:45 +00:00
}
2020-09-24 15:05:17 +00:00
MouseArea {
cursorShape: Qt . PointingHandCursor
anchors.fill: parent
onPressed: mouse . accepted = false
2020-09-25 19:44:40 +00:00
}
2020-07-23 20:22:45 +00:00
}
2020-07-22 18:37:43 +00:00
function scrollToBottom ( force , caller ) {
if ( ! force && ! chatLogView . atYEnd ) {
// User has scrolled up, we don't want to scroll back
2020-07-23 20:22:45 +00:00
return false
2020-07-22 18:37:43 +00:00
}
2020-10-20 14:15:04 +00:00
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
2020-07-22 18:37:43 +00:00
}
2020-10-20 14:15:04 +00:00
// 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
2021-02-23 16:38:57 +00:00
Qt . callLater ( chatLogView . positionViewAtBeginning )
2020-10-20 14:15:04 +00:00
timer . setTimeout ( function ( ) {
2021-02-23 16:38:57 +00:00
Qt . callLater ( chatLogView . positionViewAtBeginning )
2020-10-20 14:15:04 +00:00
} , 100 ) ;
2020-07-23 20:22:45 +00:00
return true
2020-07-22 18:37:43 +00:00
}
2021-06-23 17:30:57 +00:00
Connections {
target: chatsModel
onAppReady: {
chatLogView . scrollToBottom ( true )
}
}
2020-06-08 19:25:46 +00:00
Connections {
2021-06-21 19:35:32 +00:00
target: chatsModel . messageView
2020-06-08 19:25:46 +00:00
onMessagesLoaded: {
loadingMessages = false ;
}
2020-07-22 15:12:24 +00:00
onSendingMessage: {
2020-07-23 16:23:20 +00:00
chatLogView . scrollToBottom ( true )
2020-07-22 15:12:24 +00:00
}
2020-11-30 21:24:01 +00:00
onSendingMessageFailed: {
sendingMsgFailedPopup . open ( ) ;
}
2020-07-23 20:22:45 +00:00
onNewMessagePushed: {
if ( ! chatLogView . scrollToBottom ( ) ) {
2020-09-24 15:05:17 +00:00
root . newMessages ++
2020-07-23 20:22:45 +00:00
}
2020-06-08 19:25:46 +00:00
}
2020-07-10 21:47:31 +00:00
2020-11-16 19:25:46 +00:00
onMessageNotificationPushed: function ( chatId , msg , messageType , chatType , timestamp , identicon , username , hasMention , isAddedContact , channelName ) {
2021-07-01 20:58:57 +00:00
if ( messageType == Constants . editType ) return ;
2020-10-15 11:53:27 +00:00
if ( appSettings . notificationSetting == Constants . notifyAllMessages ||
( appSettings . notificationSetting == Constants . notifyJustMentions && hasMention ) ) {
2021-06-17 22:09:00 +00:00
if ( chatId === chatsModel . channelView . activeChannel . id && applicationWindow . active === true ) {
2020-12-28 15:45:46 +00:00
// Do not show the notif if we are in the channel already and the window is active and focused
return
}
2021-05-26 17:36:24 +00:00
chatColumnLayout . currentNotificationChatId = chatId
chatColumnLayout . currentNotificationCommunityId = null
2020-12-07 17:37:39 +00:00
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 ) {
2021-02-18 16:36:05 +00:00
//% "Image"
case Constants.imageType: message = qsTrId ( "image" ) ; break
//% "Sticker"
case Constants.stickerType: message = qsTrId ( "sticker" ) ; break
2021-04-20 01:10:01 +00:00
default: message = msg // don't parse emojis here as it emits HTML
2020-11-18 12:50:38 +00:00
}
2020-12-07 17:37:39 +00:00
} else {
2021-02-18 16:36:05 +00:00
//% "You have a new message"
message = qsTrId ( "you-have-a-new-message" )
2020-12-07 17:37:39 +00:00
}
2021-02-15 16:29:16 +00:00
currentlyHasANotification = true
2020-12-07 17:37:39 +00:00
if ( appSettings . useOSNotifications && systemTray . supportsMessages ) {
systemTray . showMessage ( name ,
message ,
SystemTrayIcon . NoIcon ,
Constants . notificationPopupTTL )
} else {
2021-05-26 17:36:24 +00:00
notificationWindow . notifyUser ( chatId , name , message , chatType , identicon , chatColumnLayout . clickOnNotification )
2020-12-07 17:37:39 +00:00
}
2020-10-15 11:53:27 +00:00
}
2020-07-10 21:47:31 +00:00
}
2021-02-11 20:37:31 +00:00
}
Connections {
target: chatsModel . communities
2021-02-10 20:37:17 +00:00
2021-05-26 17:36:24 +00:00
onMembershipRequestChanged: function ( communityId , communityName , accepted ) {
chatColumnLayout . currentNotificationChatId = null
chatColumnLayout . currentNotificationCommunityId = communityId
2021-02-10 20:37:17 +00:00
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 )
}
2021-05-26 17:36:24 +00:00
onMembershipRequestPushed: function ( communityId , communityName , pubKey ) {
chatColumnLayout . currentNotificationChatId = null
chatColumnLayout . currentNotificationCommunityId = communityId
2021-02-10 20:37:17 +00:00
systemTray . showMessage ( qsTr ( "New membership request" ) ,
qsTr ( "%1 asks to join ‘ %2’ " ) . arg ( Utils . getDisplayName ( pubKey ) ) . arg ( communityName ) ,
SystemTrayIcon . NoIcon ,
Constants . notificationPopupTTL )
}
2020-06-04 19:07:07 +00:00
}
2020-06-08 19:25:46 +00:00
2020-10-20 14:15:04 +00:00
property var loadMsgs : Backpressure . oneInTime ( chatLogView , 500 , function ( ) {
2020-07-22 15:12:24 +00:00
if ( loadingMessages ) return ;
loadingMessages = true ;
2021-06-21 19:35:32 +00:00
chatsModel . messageView . loadMoreMessages ( ) ;
2020-07-22 15:12:24 +00:00
} ) ;
2020-06-08 19:25:46 +00:00
onContentYChanged: {
2020-09-29 16:52:23 +00:00
scrollDownButton . visible = ( contentHeight - ( scrollY + height ) > 400 )
2020-07-22 15:12:24 +00:00
if ( scrollY < 500 ) {
loadMsgs ( ) ;
2020-06-08 19:25:46 +00:00
}
}
2021-06-22 18:30:51 +00:00
model: messageListDelegate
2020-06-10 18:23:18 +00:00
section.property: "sectionIdentifier"
2020-06-05 22:20:45 +00:00
section.criteria: ViewSection . FullString
2020-06-08 17:29:28 +00:00
2021-06-22 18:30:51 +00:00
}
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
2020-07-12 01:03:39 +00:00
delegate: Message {
2020-06-05 22:20:45 +00:00
id: msgDelegate
2020-06-16 21:24:43 +00:00
fromAuthor: model . fromAuthor
2020-06-08 17:29:28 +00:00
chatId: model . chatId
2020-06-05 22:20:45 +00:00
userName: model . userName
2020-12-08 15:45:43 +00:00
alias: model . alias
localName: model . localName
2020-06-05 22:20:45 +00:00
message: model . message
2020-07-10 15:24:52 +00:00
plainText: model . plainText
2020-06-05 22:20:45 +00:00
identicon: model . identicon
isCurrentUser: model . isCurrentUser
timestamp: model . timestamp
sticker: model . sticker
contentType: model . contentType
2020-07-01 18:24:13 +00:00
outgoingStatus: model . outgoingStatus
2021-06-29 14:49:32 +00:00
replaces: model . replaces
isEdited: model . isEdited
2020-07-09 15:50:38 +00:00
responseTo: model . responseTo
2020-06-05 22:20:45 +00:00
authorCurrentMsg: msgDelegate . ListView . section
2021-02-24 16:37:40 +00:00
// The previous message is actually the nextSection since we reversed the list order
authorPrevMsg: msgDelegate . ListView . nextSection
2020-09-12 18:22:07 +00:00
imageClick: imagePopup . openPopup . bind ( imagePopup )
2020-07-09 17:47:36 +00:00
messageId: model . messageId
2020-08-12 15:01:03 +00:00
emojiReactions: model . emojiReactions
2020-12-07 23:38:53 +00:00
linkUrls: model . linkUrls
2020-12-11 20:38:10 +00:00
communityId: model . communityId
2021-02-01 18:40:55 +00:00
hasMention: model . hasMention
2021-03-08 19:24:39 +00:00
stickerPackId: model . stickerPackId
2021-05-25 19:34:46 +00:00
pinnedMessage: model . isPinned
2021-05-25 19:38:18 +00:00
pinnedBy: model . pinnedBy
2021-05-10 19:12:26 +00:00
gapFrom: model . gapFrom
gapTo: model . gapTo
2021-06-10 21:07:41 +00:00
// 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
2021-06-22 18:30:51 +00:00
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 ;
}
nextMessageIndex: {
if ( msgDelegate . DelegateModel . itemsIndex <= 1 ) {
return - 1
}
return messageListDelegate . items . get ( msgDelegate . DelegateModel . itemsIndex - 1 ) . model . index
}
2020-07-22 18:37:43 +00:00
scrollToBottom: chatLogView . scrollToBottom
2020-07-14 15:35:21 +00:00
timeout: model . timeout
2020-06-05 22:20:45 +00:00
}
2020-05-27 22:59:17 +00:00
}
}
2020-05-28 15:55:52 +00:00
/ * # # ^ # #
Designer {
D { i: 0 ; autoSize: true ; height: 480 ; width: 640 }
}
# # ^ # # * /