2020-06-17 19:18:31 +00:00
import QtQuick 2.13
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
2021-10-27 21:27:49 +00:00
import utils 1.0
import shared 1.0
2021-10-28 20:23:30 +00:00
import shared . views 1.0
2021-10-27 21:27:49 +00:00
import shared . panels 1.0
import shared . popups 1.0
import shared . status 1.0
import shared . controls 1.0
2021-10-28 20:23:30 +00:00
import shared . views . chat 1.0
2021-09-28 15:04:06 +00:00
2021-10-01 15:58:36 +00:00
import "../controls"
//TODO REMOVE
import "../stores"
2021-07-22 14:53:19 +00:00
Item {
2021-08-02 13:38:03 +00:00
id: root
2021-07-22 14:53:19 +00:00
anchors.fill: parent
2021-10-01 15:58:36 +00:00
property var store
2021-10-21 22:39:53 +00:00
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
2021-07-08 15:20:03 +00:00
2021-07-16 15:02:47 +00:00
property var messageContextMenuInst
2021-08-05 21:05:47 +00:00
property var messageList: MessagesData { }
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-08-02 13:38:03 +00:00
property int countOnStartUp: 0
2021-05-26 17:36:24 +00:00
2021-08-02 13:38:03 +00:00
ListView {
id: chatLogView
2021-07-22 14:53:19 +00:00
anchors.fill: parent
2021-10-20 09:50:50 +00:00
spacing: localAccountSensitiveSettings . useCompactMode ? 0 : 4
2021-08-02 13:38:03 +00:00
boundsBehavior: Flickable . StopAtBounds
clip: true
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-07-23 20:22:45 +00:00
2021-08-02 13:38:03 +00:00
function checkHeaderHeight ( ) {
if ( ! chatLogView . headerItem ) {
return
2020-07-23 20:22:45 +00:00
}
2020-09-24 15:05:17 +00:00
2021-08-02 13:38:03 +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
}
2021-08-02 13:38:03 +00:00
}
2020-09-24 15:05:17 +00:00
2021-09-24 14:04:49 +00:00
property var scrollToMessage: function ( messageId , isSearch = false ) {
delayPositioningViewTimer . msgId = messageId ;
delayPositioningViewTimer . isSearch = isSearch ;
delayPositioningViewTimer . restart ( ) ;
}
Timer {
id: delayPositioningViewTimer
interval: 1000
property string msgId
property bool isSearch
onTriggered: {
let item
for ( let i = 0 ; i < messages . rowCount ( ) ; i ++ ) {
item = messageListDelegate . items . get ( i ) ;
if ( item . model . messageId === msgId ) {
chatLogView . positionViewAtIndex ( i , ListView . Beginning ) ;
2021-10-20 09:50:50 +00:00
if ( localAccountSensitiveSettings . useCompactMode && isSearch ) {
2021-09-24 14:04:49 +00:00
chatLogView . itemAtIndex ( i ) . startMessageFoundAnimation ( ) ;
}
2021-09-22 08:25:55 +00:00
}
2021-06-30 18:46:26 +00:00
}
2021-09-24 14:04:49 +00:00
msgId = "" ;
isSearch = false ;
2020-09-25 19:44:40 +00:00
}
2021-08-02 13:38:03 +00:00
}
ScrollBar.vertical: ScrollBar {
visible: chatLogView . visibleArea . heightRatio < 1
}
2020-07-23 20:22:45 +00:00
2021-08-02 13:38:03 +00:00
Connections {
id: contentHeightConnection
enabled: true
target: chatLogView
onContentHeightChanged: {
chatLogView . checkHeaderHeight ( )
}
onHeightChanged: {
chatLogView . checkHeaderHeight ( )
2020-07-22 18:37:43 +00:00
}
2021-08-02 13:38:03 +00:00
}
2020-07-22 18:37:43 +00:00
2021-08-02 13:38:03 +00:00
Timer {
id: timer
}
2021-06-23 17:30:57 +00:00
2021-08-02 13:38:03 +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: {
newMessages = 0
scrollDownButton . visible = false
chatLogView . scrollToBottom ( true )
}
2021-06-23 17:30:57 +00:00
2021-08-02 13:38:03 +00:00
StyledText {
id: nbMessages
visible: newMessages > 0
width: visible ? implicitWidth : 0
text: newMessages
anchors.verticalCenter: parent . verticalCenter
anchors.left: parent . left
color: Style . current . pillButtonTextColor
font.pixelSize: 15
anchors.leftMargin: Style . current . halfPadding
}
2020-06-08 19:25:46 +00:00
2021-08-02 13:38:03 +00:00
SVGImage {
id: arrowImage
width: 24
height: 24
anchors.verticalCenter: parent . verticalCenter
anchors.left: nbMessages . right
2021-09-28 15:04:06 +00:00
source: Style . svg ( "leave_chat" )
2021-08-02 13:38:03 +00:00
anchors.leftMargin: nbMessages . visible ? scrollDownButton.buttonPadding : 0
rotation: - 90
ColorOverlay {
2021-06-30 18:46:26 +00:00
anchors.fill: parent
2021-08-02 13:38:03 +00:00
source: parent
color: Style . current . pillButtonTextColor
2021-06-30 18:46:26 +00:00
}
2020-07-22 15:12:24 +00:00
}
2021-08-02 13:38:03 +00:00
MouseArea {
cursorShape: Qt . PointingHandCursor
anchors.fill: parent
onPressed: mouse . accepted = false
2020-11-30 21:24:01 +00:00
}
2021-08-02 13:38:03 +00:00
}
2020-11-30 21:24:01 +00:00
2021-08-02 13:38:03 +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
2021-07-08 15:20:03 +00:00
}
2021-08-02 13:38:03 +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
Qt . callLater ( chatLogView . positionViewAtBeginning )
timer . setTimeout ( function ( ) {
Qt . callLater ( chatLogView . positionViewAtBeginning )
} , 100 ) ;
return true
}
2021-07-08 15:20:03 +00:00
2021-08-02 13:38:03 +00:00
Connections {
2021-10-21 22:39:53 +00:00
target: root . store . chatsModelInst
2020-07-10 21:47:31 +00:00
2021-08-02 13:38:03 +00:00
onAppReady: {
chatLogView . scrollToBottom ( true )
}
}
2020-12-28 15:45:46 +00:00
2021-08-02 13:38:03 +00:00
Connections {
2021-10-21 22:39:53 +00:00
target: root . store . chatsModelInst . messageView
2020-12-07 17:37:39 +00:00
2021-08-25 10:02:43 +00:00
onSendingMessageSuccess: {
2021-08-02 13:38:03 +00:00
chatLogView . scrollToBottom ( true )
2020-07-10 21:47:31 +00:00
}
2021-02-11 20:37:31 +00:00
2021-08-02 13:38:03 +00:00
onSendingMessageFailed: {
sendingMsgFailedPopup . open ( ) ;
}
2021-02-10 20:37:17 +00:00
2021-08-02 13:38:03 +00:00
onNewMessagePushed: {
if ( ! chatLogView . scrollToBottom ( ) ) {
newMessages ++
2021-06-30 18:46:26 +00:00
}
2021-02-10 20:37:17 +00:00
}
2021-08-02 13:38:03 +00:00
}
2020-06-08 19:25:46 +00:00
2021-08-02 13:38:03 +00:00
Connections {
2021-10-21 22:39:53 +00:00
target: root . store . chatsModelInst . communities
2021-08-02 13:38:03 +00:00
2021-08-18 14:43:59 +00:00
// Note:
// Whole this Connection object (both slots) should be moved to the nim side.
// Left here only cause we don't have a way to deal with translations on the nim side.
2021-08-02 13:38:03 +00:00
onMembershipRequestChanged: function ( communityId , communityName , accepted ) {
chatColumnLayout . currentNotificationChatId = null
chatColumnLayout . currentNotificationCommunityId = communityId
2021-10-21 22:39:53 +00:00
root . store . chatsModelInst . showOSNotification ( "Status" ,
2021-08-18 14:43:59 +00:00
//% "You have been accepted into the ‘ %1’ community"
accepted ? qsTrId ( "you-have-been-accepted-into-the---1--community" ) . arg ( communityName ) :
//% "Your request to join the ‘ %1’ community was declined"
qsTrId ( "your-request-to-join-the---1--community-was-declined" ) . arg ( communityName ) ,
accepted ? Constants.osNotificationType.acceptedIntoCommunity :
Constants . osNotificationType . rejectedByCommunity ,
communityId ,
"" ,
"" ,
2021-10-20 09:50:50 +00:00
localAccountSensitiveSettings . useOSNotifications )
2020-06-08 19:25:46 +00:00
}
2021-08-02 13:38:03 +00:00
onMembershipRequestPushed: function ( communityId , communityName , pubKey ) {
chatColumnLayout . currentNotificationChatId = null
chatColumnLayout . currentNotificationCommunityId = communityId
//% "New membership request"
2021-10-21 22:39:53 +00:00
root . store . chatsModelInst . showOSNotification ( qsTrId ( "new-membership-request" ) ,
2021-08-18 14:43:59 +00:00
//% "%1 asks to join ‘ %2’ "
qsTrId ( "-1-asks-to-join---2-" ) . arg ( Utils . getDisplayName ( pubKey ) ) . arg ( communityName ) ,
Constants . osNotificationType . joinCommunityRequest ,
communityId ,
"" ,
"" ,
2021-10-20 09:50:50 +00:00
localAccountSensitiveSettings . useOSNotifications )
2021-08-02 13:38:03 +00:00
}
2021-06-30 18:46:26 +00:00
}
2021-06-22 18:30:51 +00:00
2021-08-02 13:38:03 +00:00
property var loadMsgs : Backpressure . oneInTime ( chatLogView , 500 , function ( ) {
2021-07-29 13:56:01 +00:00
if ( ! messages . initialMessagesLoaded || messages . loadingHistoryMessages )
2021-07-26 08:08:03 +00:00
return
2021-10-21 22:39:53 +00:00
root . store . chatsModelInst . messageView . loadMoreMessages ( chatId ) ;
2021-08-02 13:38:03 +00:00
} ) ;
2021-06-30 18:46:26 +00:00
2021-08-02 13:38:03 +00:00
onContentYChanged: {
scrollDownButton . visible = ( contentHeight - ( scrollY + height ) > 400 )
2021-08-13 11:33:11 +00:00
if ( scrollDownButton . visible && scrollY < 500 ) {
2021-08-02 13:38:03 +00:00
loadMsgs ( ) ;
2021-07-16 17:00:06 +00:00
}
}
2021-08-02 13:38:03 +00:00
model: messageListDelegate
section.property: "sectionIdentifier"
section.criteria: ViewSection . FullString
2021-09-21 14:59:39 +00:00
Component.onCompleted: scrollToBottom ( true )
2021-08-02 13:38:03 +00:00
}
MessageDialog {
id: sendingMsgFailedPopup
standardButtons: StandardButton . Ok
//% "Failed to send message."
text: qsTrId ( "failed-to-send-message-" )
icon: StandardIcon . Critical
}
Timer {
id: modelLoadingDelayTimer
interval: 1000
onTriggered: {
root . countOnStartUp = messageListDelegate . count ;
}
}
DelegateModelGeneralized {
id: messageListDelegate
lessThan: [
function ( left , right ) { return left . clock > right . clock }
]
2021-07-29 13:56:01 +00:00
model: messages
2021-08-02 13:38:03 +00:00
2021-10-01 15:58:36 +00:00
delegate: MessageView {
2021-08-02 13:38:03 +00:00
id: msgDelegate
2021-10-01 15:58:36 +00:00
rootStore: root . store
messageStore: root . store . messageStore
/////////////TODO Remove
2021-08-02 13:38:03 +00:00
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-09-20 22:07:31 +00:00
visible: ! model . hide
2021-08-02 13:38:03 +00:00
messageContextMenu: root . messageContextMenuInst
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
2021-06-22 18:30:51 +00:00
}
2021-08-02 13:38:03 +00:00
return - 1 ;
2021-06-22 18:30:51 +00:00
}
2021-08-02 13:38:03 +00:00
nextMessageIndex: {
if ( msgDelegate . DelegateModel . itemsIndex < 1 ) {
return - 1
}
return messageListDelegate . items . get ( msgDelegate . DelegateModel . itemsIndex - 1 ) . model . index
2021-07-16 17:00:06 +00:00
}
2021-08-02 13:38:03 +00:00
scrollToBottom: chatLogView . scrollToBottom
timeout: model . timeout
2021-10-01 15:58:36 +00:00
Component.onCompleted: {
if ( ( root . countOnStartUp > 0 ) && ( root . countOnStartUp - 1 ) < index ) {
//new message, increment z order
z = index ;
}
messageStore . fromAuthor = model . fromAuthor ;
messageStore . chatId = model . chatId ;
messageStore . userName = model . userName ;
messageStore . alias = model . alias ;
messageStore . localName = model . localName ;
messageStore . message = model . message ;
messageStore . plainText = model . plainText ;
messageStore . identicon = model . identicon ;
messageStore . isCurrentUser = model . isCurrentUser ;
messageStore . timestamp = model . timestamp ;
messageStore . sticker = model . sticker ;
messageStore . contentType = model . contentType ;
messageStore . replaces = model . replaces ;
messageStore . isEdited = model . isEdited ;
messageStore . outgoingStatus = model . outgoingStatus ;
messageStore . responseTo = model . responseTo ;
messageStore . authorCurrentMsg = msgDelegate . ListView . section ;
// The previous message is actually the nextSection since we reversed the list order
messageStore . authorPrevMsg = msgDelegate . ListView . nextSection ;
messageStore . imageClick = imagePopup . openPopup . bind ( imagePopup ) ;
messageStore . messageId = model . messageId ;
messageStore . emojiReactions = model . emojiReactions ;
messageStore . linkUrls = model . linkUrls ;
messageStore . communityId = model . communityId ;
messageStore . hasMention = model . hasMention ;
messageStore . stickerPackId = model . stickerPackId ;
messageStore . pinnedMessage = model . isPinned ;
messageStore . pinnedBy = model . pinnedBy ;
messageStore . gapFrom = model . gapFrom ;
messageStore . gapTo = model . gapTo ;
messageStore . messageContextMenu = root . messageContextMenuInst ;
messageStore . 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
( msgDelegate . DelegateModel . itemsIndex < messageListDelegate . items . count - 1 ) ?
messageListDelegate . items . get ( msgDelegate . DelegateModel . itemsIndex + 1 ) . model . index
: - 1 ;
messageStore . nextMessageIndex = ( msgDelegate . DelegateModel . itemsIndex < 1 ) ?
- 1 : messageListDelegate . items . get ( msgDelegate . DelegateModel . itemsIndex - 1 ) . model . index ;
messageStore . scrollToBottom = chatLogView . scrollToBottom ;
messageStore . timeout = model . timeout ;
}
2021-08-02 13:38:03 +00:00
}
Component.onCompleted: {
modelLoadingDelayTimer . start ( ) ;
2021-06-30 18:46:26 +00:00
}
}
2020-05-27 22:59:17 +00:00
}