linphone-desktop/Linphone/view/Control/Display/Chat/ChatMessagesListView.qml

312 lines
13 KiB
QML

import QtQuick
import QtQuick.Effects
import QtQuick.Layouts
import QtQuick.Controls.Basic as Control
import Qt.labs.qmlmodels
import Linphone
import UtilsCpp
import 'qrc:/qt/qml/Linphone/view/Style/buttonStyle.js' as ButtonStyle
ListView {
id: mainItem
spacing: Math.round(4 * DefaultStyle.dp)
property ChatGui chat
property color backgroundColor
property bool lastItemVisible: false
property int lastIndexFoundWithFilter: -1
verticalLayoutDirection: ListView.BottomToTop
signal showReactionsForMessageRequested(ChatMessageGui chatMessage)
signal showImdnStatusForMessageRequested(ChatMessageGui chatMessage)
signal replyToMessageRequested(ChatMessageGui chatMessage)
signal forwardMessageRequested(ChatMessageGui chatMessage)
signal requestHighlight(int indexToHighlight)
signal requestAutoPlayVoiceRecording(int indexToPlay)
currentIndex: -1
property string filterText
onFilterTextChanged: {
lastIndexFoundWithFilter = -1
if (filterText === "") return
eventLogProxy.filterText = filterText
var indexVisible = indexAt(contentX, contentY)
eventLogProxy.findIndexCorrespondingToFilter(indexVisible, true, true)
}
signal findIndexWithFilter(bool forward)
property bool searchForward: true
onFindIndexWithFilter: (forward) => {
searchForward = forward
eventLogProxy.findIndexCorrespondingToFilter(currentIndex, forward, false)
}
Component.onCompleted: {
Qt.callLater(function() {
var index = eventLogProxy.findFirstUnreadIndex()
positionViewAtIndex(index, ListView.Beginning)
eventLogProxy.markIndexAsRead(index)
})
}
onChatChanged: lastItemVisible = false
Button {
visible: !mainItem.lastItemVisible
icon.source: AppIcons.downArrow
leftPadding: Math.round(16 * DefaultStyle.dp)
rightPadding: Math.round(16 * DefaultStyle.dp)
topPadding: Math.round(16 * DefaultStyle.dp)
bottomPadding: Math.round(16 * DefaultStyle.dp)
anchors.bottom: parent.bottom
style: ButtonStyle.main
anchors.right: parent.right
anchors.bottomMargin: Math.round(18 * DefaultStyle.dp)
anchors.rightMargin: Math.round(18 * DefaultStyle.dp)
onClicked: {
var index = eventLogProxy.findFirstUnreadIndex()
mainItem.positionViewAtIndex(index, ListView.Beginning)
eventLogProxy.markIndexAsRead(index)
}
}
onAtYEndChanged: if (atYEnd) {
chat.core.lMarkAsRead()
}
onAtYBeginningChanged: if (atYBeginning) {
if (eventLogProxy.haveMore)
eventLogProxy.displayMore()
}
model: EventLogProxy {
id: eventLogProxy
chatGui: mainItem.chat
filterText: mainItem.filterText
initialDisplayItems: 10
onEventInserted: (index, gui) => {
if (!mainItem.visible) return
if(mainItem.lastItemVisible) mainItem.positionViewAtIndex(index, ListView.Beginning)
}
onModelReset: Qt.callLater(function() {
var index = eventLogProxy.findFirstUnreadIndex()
positionViewAtIndex(index, ListView.Beginning)
eventLogProxy.markIndexAsRead(index)
})
onIndexWithFilterFound: (index) => {
if (index !== -1) {
currentIndex = index
mainItem.positionViewAtIndex(index, ListView.Center)
mainItem.requestHighlight(index)
mainItem.lastIndexFoundWithFilter = index
} else {
if (mainItem.lastIndexFoundWithFilter !== index) {
//: Find message
UtilsCpp.showInformationPopup(qsTr("popup_info_find_message_title"),
mainItem.searchForward
//: Last result reached
? qsTr("info_popup_last_result_message")
//: First result reached
: qsTr("info_popup_first_result_message"), false)
mainItem.positionViewAtIndex(mainItem.lastIndexFoundWithFilter, ListView.Center)
mainItem.requestHighlight(mainItem.lastIndexFoundWithFilter)
}
else {
//: Find message
UtilsCpp.showInformationPopup(qsTr("popup_info_find_message_title"),
//: No result found
qsTr("info_popup_no_result_message"), false)
}
}
}
}
footer: Item {
visible: mainItem.chat && mainItem.chat.core.isEncrypted && !eventLogProxy.haveMore
height: visible ? headerMessage.height + headerMessage.topMargin + headerMessage.bottomMargin : Math.round(30 * DefaultStyle.dp)
width: headerMessage.width
anchors.horizontalCenter: parent.horizontalCenter
Control.Control {
id: headerMessage
property int topMargin: mainItem.contentHeight > mainItem.height ? Math.round(30 * DefaultStyle.dp) : Math.round(50 * DefaultStyle.dp)
property int bottomMargin: Math.round(30 * DefaultStyle.dp)
anchors.topMargin: topMargin
anchors.bottomMargin: bottomMargin
anchors.top: parent.top
padding: Math.round(10 * DefaultStyle.dp)
background: Rectangle {
color: "transparent"
border.color: DefaultStyle.main2_200
border.width: Math.round(2 * DefaultStyle.dp)
radius: Math.round(10 * DefaultStyle.dp)
}
contentItem: RowLayout {
EffectImage {
Layout.preferredWidth: Math.round(23 * DefaultStyle.dp)
Layout.preferredHeight: Math.round(23 * DefaultStyle.dp)
imageSource: AppIcons.lockSimple
colorizationColor: DefaultStyle.info_500_main
}
ColumnLayout {
spacing: Math.round(2 * DefaultStyle.dp)
Text {
//: End to end encrypted chat
text: qsTr("chat_message_list_encrypted_header_title")
Layout.fillWidth: true
color: DefaultStyle.info_500_main
font {
pixelSize: Typography.p2.pixelSize
weight: Typography.p2.weight
}
}
Text {
//: Les messages de cette conversation sont chiffrés de bout \n en bout. Seul votre correspondant peut les déchiffrer.
text: qsTr("chat_message_list_encrypted_header_message")
Layout.fillWidth: true
color: DefaultStyle.grey_400
font {
pixelSize: Typography.p3.pixelSize
weight: Typography.p3.weight
}
}
}
}
}
}
footerPositioning: ListView.PullBackFooter
headerPositioning: ListView.OverlayHeader
header: Control.Control {
visible: composeLayout.composingName !== "" && composeLayout.composingName !== undefined
width: mainItem.width
// height: visible ? contentItem.implicitHeight + topPadding + bottomPadding : 0
z: mainItem.z + 2
topPadding: Math.round(5 * DefaultStyle.dp)
bottomPadding: Math.round(5 * DefaultStyle.dp)
background: Rectangle {
anchors.fill: parent
color: mainItem.backgroundColor
}
contentItem: RowLayout {
id: composeLayout
property var composingName: mainItem.chat?.core.composingName
Avatar {
Layout.preferredWidth: Math.round(20 * DefaultStyle.dp)
Layout.preferredHeight: Math.round(20 * DefaultStyle.dp)
_address: mainItem.chat?.core.composingAddress
}
Text {
Layout.fillWidth: true
font {
pixelSize: Typography.p3.pixelSize
weight: Typography.p3.weight
}
//: %1 is writing…
text: qsTr("chat_message_is_writing_info").arg(composeLayout.composingName)
}
}
}
delegate: DelegateChooser {
role: "eventType"
DelegateChoice {
roleValue: "chatMessage"
delegate: ChatMessage {
id: chatMessageDelegate
property int yoff: Math.round(chatMessageDelegate.y - mainItem.contentY)
property bool isFullyVisible: (yoff > mainItem.y && yoff + height < mainItem.y + mainItem.height)
chatMessage: modelData.core.chatMessageGui
onIsFullyVisibleChanged: {
if (index === 0) {
mainItem.lastItemVisible = isFullyVisible
}
}
Component.onCompleted: {
if (index === 0) mainItem.lastItemVisible = isFullyVisible
}
chat: mainItem.chat
searchedTextPart: mainItem.filterText
maxWidth: Math.round(mainItem.width * (3/4))
width: mainItem.width
property var previousIndex: index - 1
property ChatMessageGui nextChatMessage: index <= 0
? null
: eventLogProxy.getEventAtIndex(index-1)
? eventLogProxy.getEventAtIndex(index-1).core.chatMessageGui
: null
property var previousFromAddress: eventLogProxy.getEventAtIndex(index+1)?.core.chatMessage?.fromAddress
backgroundColor: isRemoteMessage ? DefaultStyle.main2_100 : DefaultStyle.main1_100
isFirstMessage: !previousFromAddress || previousFromAddress !== chatMessage.core.fromAddress
anchors.right: !isRemoteMessage && parent
? parent.right
: undefined
onMessageDeletionRequested: chatMessage.core.lDelete()
onShowReactionsForMessageRequested: mainItem.showReactionsForMessageRequested(chatMessage)
onShowImdnStatusForMessageRequested: mainItem.showImdnStatusForMessageRequested(chatMessage)
onReplyToMessageRequested: mainItem.replyToMessageRequested(chatMessage)
onForwardMessageRequested: mainItem.forwardMessageRequested(chatMessage)
onEndOfVoiceRecordingReached: {
if (nextChatMessage && nextChatMessage.core.isVoiceRecording) mainItem.requestAutoPlayVoiceRecording(index - 1)
}
Connections {
target: mainItem
function onRequestHighlight(indexToHighlight) {
if (indexToHighlight === index) {
requestHighlight()
}
}
function onRequestAutoPlayVoiceRecording(indexToPlay) {
if (indexToPlay === index) {
chatMessageDelegate.requestAutoPlayVoiceRecording()
}
}
}
}
}
DelegateChoice {
roleValue: "event"
delegate: Item {
id: eventDelegate
property int yoff: Math.round(eventDelegate.y - mainItem.contentY)
property bool isFullyVisible: (yoff > mainItem.y && yoff + height < mainItem.y + mainItem.height)
onIsFullyVisibleChanged: {
if (index === 0) {
mainItem.lastItemVisible = isFullyVisible
}
}
property bool showTopMargin: !header.visible && index == 0
width: mainItem.width
height: (showTopMargin ? 30 * DefaultStyle.dp : 0) + eventItem.implicitHeight
Event {
id: eventItem
anchors.top: parent.top
anchors.topMargin: showTopMargin ? 30 : 0 * DefaultStyle.dp
width: parent.width
eventLogGui: modelData
}
}
}
DelegateChoice {
roleValue: "ephemeralEvent"
delegate: Item {
id: ephemeralEventDelegate
property int yoff: Math.round(ephemeralEventDelegate.y - mainItem.contentY)
property bool isFullyVisible: (yoff > mainItem.y && yoff + height < mainItem.y + mainItem.height)
onIsFullyVisibleChanged: {
if (index === 0) {
mainItem.lastItemVisible = isFullyVisible
}
}
property bool showTopMargin: !header.visible && index == 0
width: mainItem.width
//height: 40 * DefaultStyle.dp
height: (showTopMargin ? 30 : 0 * DefaultStyle.dp) + ephemeralEventItem.height
EphemeralEvent {
id: ephemeralEventItem
anchors.top: parent.top
anchors.topMargin: showTopMargin ? 30 : 0 * DefaultStyle.dp
eventLogGui: modelData
}
}
}
}
}