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"
2021-06-30 18:46:26 +00:00
import "../ContactsColumn"
2021-07-06 22:41:26 +00:00
import "../CommunityComponents"
2020-05-27 22:59:17 +00:00
2021-06-30 18:46:26 +00:00
SplitView {
id: svRoot
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
2021-06-30 18:46:26 +00:00
property var currentTime
2020-05-28 22:22:51 +00:00
2020-05-27 22:59:17 +00:00
Layout.fillWidth: true
Layout.fillHeight: true
2021-06-30 18:46:26 +00:00
handle: SplitViewHandle { implicitWidth: 5 }
2021-05-26 17:36:24 +00:00
2021-06-30 18:46:26 +00:00
ScrollView {
id: root
2021-05-26 17:36:24 +00:00
2021-06-30 18:46:26 +00:00
contentItem: chatLogView
SplitView.fillWidth: true
SplitView.minimumWidth: 200
2021-06-10 19:20:43 +00:00
2021-06-30 18:46:26 +00:00
ScrollBar.vertical.policy: chatLogView . contentHeight > chatLogView . height ? ScrollBar.AlwaysOn : ScrollBar . AlwaysOff
ScrollBar.horizontal.policy: ScrollBar . AlwaysOff
2020-07-22 17:46:17 +00:00
2021-06-30 18:46:26 +00:00
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
2020-09-24 15:05:17 +00:00
}
2021-06-30 18:46:26 +00:00
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
2020-09-24 15:05:17 +00:00
}
2021-06-30 18:46:26 +00:00
function checkHeaderHeight ( ) {
if ( ! chatLogView . headerItem ) {
return
}
2020-07-23 20:22:45 +00:00
2021-06-30 18:46:26 +00:00
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
}
2020-07-23 20:22:45 +00:00
}
2020-09-24 15:05:17 +00:00
2021-06-30 18:46:26 +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
}
2020-09-24 15:05:17 +00:00
}
2020-07-23 20:22:45 +00:00
}
2020-09-24 15:05:17 +00:00
2021-06-30 18:46:26 +00:00
Connections {
id: contentHeightConnection
enabled: true
target: chatLogView
onContentHeightChanged: {
chatLogView . checkHeaderHeight ( )
}
onHeightChanged: {
chatLogView . checkHeaderHeight ( )
}
2020-09-25 19:44:40 +00:00
}
2020-07-23 20:22:45 +00:00
2021-06-30 18:46:26 +00:00
Timer {
id: timer
2020-07-22 18:37:43 +00:00
}
2021-06-30 18:46:26 +00:00
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 )
}
2021-06-23 17:30:57 +00:00
2021-06-30 18:46:26 +00:00
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
}
2021-06-23 17:30:57 +00:00
2021-06-30 18:46:26 +00:00
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
}
}
2020-06-08 19:25:46 +00:00
2021-06-30 18:46:26 +00:00
MouseArea {
cursorShape: Qt . PointingHandCursor
anchors.fill: parent
onPressed: mouse . accepted = false
}
2020-07-22 15:12:24 +00:00
}
2021-06-30 18:46:26 +00:00
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
2020-11-30 21:24:01 +00:00
}
2021-06-30 18:46:26 +00:00
Connections {
target: chatsModel . messageView
onMessagesLoaded: {
loadingMessages = false ;
2020-07-23 20:22:45 +00:00
}
2020-07-10 21:47:31 +00:00
2021-06-30 18:46:26 +00:00
onSendingMessage: {
chatLogView . scrollToBottom ( true )
}
2020-12-28 15:45:46 +00:00
2021-06-30 18:46:26 +00:00
onSendingMessageFailed: {
sendingMsgFailedPopup . open ( ) ;
}
2020-12-07 17:37:39 +00:00
2021-06-30 18:46:26 +00:00
onNewMessagePushed: {
if ( ! chatLogView . scrollToBottom ( ) ) {
root . newMessages ++
2020-12-07 17:37:39 +00:00
}
2021-06-30 18:46:26 +00:00
}
onAppReady: {
chatLogView . scrollToBottom ( true )
}
2020-12-07 17:37:39 +00:00
2021-06-30 18:46:26 +00:00
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
2020-11-18 12:50:38 +00:00
}
2020-12-07 17:37:39 +00:00
2021-06-30 18:46:26 +00:00
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 )
}
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
2021-06-30 18:46:26 +00:00
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 )
}
2021-02-10 20:37:17 +00:00
2021-06-30 18:46:26 +00:00
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 )
}
2021-02-10 20:37:17 +00:00
}
2020-06-08 19:25:46 +00:00
2021-06-30 18:46:26 +00:00
property var loadMsgs : Backpressure . oneInTime ( chatLogView , 500 , function ( ) {
if ( loadingMessages ) return ;
loadingMessages = true ;
chatsModel . messageView . loadMoreMessages ( ) ;
} ) ;
2020-07-22 15:12:24 +00:00
2021-06-30 18:46:26 +00:00
onContentYChanged: {
scrollDownButton . visible = ( contentHeight - ( scrollY + height ) > 400 )
if ( scrollY < 500 ) {
loadMsgs ( ) ;
}
2020-06-08 19:25:46 +00:00
}
2021-06-30 18:46:26 +00:00
model: messageListDelegate
section.property: "sectionIdentifier"
section.criteria: ViewSection . FullString
2020-06-08 17:29:28 +00:00
2021-06-30 18:46:26 +00:00
}
2021-06-22 18:30:51 +00:00
2021-06-30 18:46:26 +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
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
2021-06-22 18:30:51 +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-30 18:46:26 +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
2021-06-22 18:30:51 +00:00
}
2021-06-30 18:46:26 +00:00
scrollToBottom: chatLogView . scrollToBottom
timeout: model . timeout
2021-06-22 18:30:51 +00:00
}
2021-06-30 18:46:26 +00:00
}
}
2021-07-06 22:41:26 +00:00
Loader {
property int defaultWidth: 250
SplitView.preferredWidth: active ? defaultWidth : 0
SplitView.minimumWidth: active ? 50 : 0
active: showUsers && chatsModel . channelView . activeChannel . chatType !== Constants . chatTypeOneToOne
anchors.top: parent . top
anchors.bottom: parent . bottom
sourceComponent: appSettings . communitiesEnabled && chatsModel . communities . activeCommunity . active ? communityUserListComponent : userListComponent
2020-05-27 22:59:17 +00:00
}
2021-07-06 22:41:26 +00:00
Component {
id: communityUserListComponent
CommunityUserList { }
}
Component {
id: userListComponent
UserList { }
}
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 }
}
# # ^ # # * /