linphone-desktop/Linphone/view/Control/Input/Chat/ChatDroppableTextArea.qml
Gaelle Braud 8e10decd65 Fixes :
do not display notification when it comes from the currently displayed chat #LINQT-2079

do not create account model for each chat, too much listener sending signals

fix crash in ConferenceInfoList #LINQT-2080
fix warnings on endResetModel
fix messages added twice because of displayMore() function called when lUpdate is running
Plain text in sending text area
2025-10-24 17:08:51 +02:00

324 lines
9.3 KiB
QML

import QtQuick
import QtQuick.Controls.Basic as Control
import QtQuick.Dialogs
import QtQuick.Layouts
import Linphone
import UtilsCpp
import 'qrc:/qt/qml/Linphone/view/Style/buttonStyle.js' as ButtonStyle
import 'qrc:/qt/qml/Linphone/view/Control/Tool/Helper/utils.js' as Utils
Control.Control {
id: mainItem
// property alias placeholderText: sendingTextArea.placeholderText
property string text
property var textArea
property int selectedFilesCount: 0
// property alias cursorPosition: sendingTextArea.cursorPosition
property bool dropEnabled: true
property bool isEphemeral : false
property bool emojiVisible: false
// disable record button if call ongoing
property bool callOngoing: false
property ChatGui chat
// ---------------------------------------------------------------------------
signal dropped (var files)
signal validText (string text)
signal sendMessage()
signal emojiClicked()
signal composing()
// ---------------------------------------------------------------------------
function _emitFiles (files) {
// Filtering files, other urls are forbidden.
files = files.reduce(function (files, file) {
if (file.toString().startsWith("file:")) {
files.push(Utils.getSystemPathFromUri(file))
}
return files
}, [])
if (files.length > 0) {
dropped(files)
}
}
FileDialog {
id: fileDialog
fileMode: FileDialog.OpenFiles
onAccepted: _emitFiles(fileDialog.selectedFiles)
}
// width: mainItem.implicitWidth
// height: mainItem.height
leftPadding: Utils.getSizeWithScreenRatio(15)
rightPadding: Utils.getSizeWithScreenRatio(15)
topPadding: Utils.getSizeWithScreenRatio(16)
bottomPadding: Utils.getSizeWithScreenRatio(16)
background: Rectangle {
anchors.fill: parent
color: DefaultStyle.grey_100
}
contentItem: Control.StackView {
id: sendingAreaStackView
initialItem: textAreaComp
onHeightChanged: {
mainItem.height = height + mainItem.topPadding + mainItem.bottomPadding
}
Component {
id: textAreaComp
RowLayout {
spacing: Utils.getSizeWithScreenRatio(16)
PopupButton {
id: emojiPickerButton
style: ButtonStyle.noBackground
icon.source: checked ? AppIcons.closeX : AppIcons.smiley
popup.width: Utils.getSizeWithScreenRatio(393)
popup.height: Utils.getSizeWithScreenRatio(291)
popup.contentItem: EmojiPicker {
editor: sendingTextArea
}
}
BigButton {
style: ButtonStyle.noBackground
icon.source: AppIcons.paperclip
onClicked: {
fileDialog.open()
}
}
Control.Control {
id: sendingControl
onHeightChanged: {
sendingAreaStackView.height = height
}
Layout.fillWidth: true
Layout.alignment: Qt.AlignCenter
leftPadding: Utils.getSizeWithScreenRatio(24)
rightPadding: Utils.getSizeWithScreenRatio(20)
topPadding: Utils.getSizeWithScreenRatio(12)
bottomPadding: Utils.getSizeWithScreenRatio(12)
background: Rectangle {
id: inputBackground
anchors.fill: parent
radius: Utils.getSizeWithScreenRatio(35)
color: DefaultStyle.grey_0
MouseArea {
anchors.fill: parent
onPressed: sendingTextArea.forceActiveFocus()
cursorShape: Qt.IBeamCursor
}
}
contentItem: RowLayout {
Flickable {
id: sendingAreaFlickable
Layout.preferredHeight: Math.min(Utils.getSizeWithScreenRatio(100), contentHeight)
Layout.fillHeight: true
width: sendingControl.width - sendingControl.leftPadding - sendingControl.rightPadding
Layout.fillWidth: true
Layout.alignment: Qt.AlignCenter
contentHeight: sendingTextArea.contentHeight
contentWidth: width
function ensureVisible(r) {
if (contentX >= r.x)
contentX = r.x;
else if (contentX+width <= r.x+r.width)
contentX = r.x+r.width-width;
if (contentY >= r.y)
contentY = r.y;
else if (contentY+height <= r.y+r.height)
contentY = r.y+r.height-height;
}
TextArea {
id: sendingTextArea
// RectangleTest{anchors.fill: parent}
width: sendingAreaFlickable.width
height: implicitHeight// sendingAreaFlickable.height
textFormat: TextEdit.PlainText
onTextChanged: {
mainItem.text = text
}
Component.onCompleted: {
mainItem.textArea = sendingTextArea
sendingTextArea.text = mainItem.text
}
//: Say something… : placeholder text for sending message text area
placeholderText: qsTr("chat_view_send_area_placeholder_text")
placeholderTextColor: DefaultStyle.main2_400
color: DefaultStyle.main2_700
font {
pixelSize: Typography.p1.pixelSize
weight: Typography.p1.weight
}
onCursorRectangleChanged: sendingAreaFlickable.ensureVisible(cursorRectangle)
wrapMode: TextEdit.WordWrap
KeyNavigation.tab: recordButton.visible ? recordButton : sendMessageButton
Keys.onPressed: (event) => {
if ((event.key == Qt.Key_Enter || event.key == Qt.Key_Return))
if(!(event.modifiers & Qt.ShiftModifier)) {
mainItem.sendMessage()
event.accepted = true
}
}
Connections {
target: mainItem
function onTextChanged() {
sendingTextArea.text = mainItem.text
}
function onSendMessage() {
sendingTextArea.clear()
}
}
}
}
RowLayout {
id: stackButton
spacing: 0
Layout.preferredHeight: Math.max(recordButton.height, sendMessageButton.height)
BigButton {
id: recordButton
ToolTip.visible: !enabled && hovered
//: Cannot record a message while a call is ongoing
ToolTip.text: qsTr("cannot_record_while_in_call_tooltip")
enabled: !mainItem.callOngoing
visible: !mainItem.callOngoing && sendingTextArea.text.length === 0 && mainItem.selectedFilesCount === 0
style: ButtonStyle.noBackground
hoverEnabled: true
icon.source: AppIcons.microphone
onClicked: {
sendingAreaStackView.push(voiceMessageRecordComp)
}
}
BigButton {
id: sendMessageButton
Layout.preferredHeight: height
visible: sendingTextArea.text.length !== 0 || mainItem.selectedFilesCount > 0
style: ButtonStyle.noBackgroundOrange
icon.source: AppIcons.paperPlaneRight
onClicked: {
mainItem.sendMessage()
}
}
}
}
}
}
}
Component {
id: voiceMessageRecordComp
RowLayout {
spacing: Utils.getSizeWithScreenRatio(16)
RoundButton {
style: ButtonStyle.player
shadowEnabled: true
padding: Utils.getSizeWithScreenRatio(4)
icon.width: Utils.getSizeWithScreenRatio(22)
icon.height: Utils.getSizeWithScreenRatio(22)
icon.source: AppIcons.closeX
width: Utils.getSizeWithScreenRatio(30)
Layout.preferredWidth: width
Layout.preferredHeight: height
onClicked: {
if (voiceMessage.chatMessage) mainItem.chat.core.lDeleteMessage(voiceMessage.chatMessage)
sendingAreaStackView.pop()
}
}
ChatAudioContent {
id: voiceMessage
onHeightChanged: {
sendingAreaStackView.height = height
}
recording: true
Layout.fillWidth: true
Layout.preferredHeight: Utils.getSizeWithScreenRatio(48)
chatMessageContentGui: chatMessage ? chatMessage.core.getVoiceRecordingContent() : null
onVoiceRecordingMessageCreationRequested: (recorderGui) => {
chatMessageObj = UtilsCpp.createVoiceRecordingMessage(recorderGui, mainItem.chat)
}
}
BigButton {
id: sendButton
style: ButtonStyle.noBackgroundOrange
icon.source: AppIcons.paperPlaneRight
icon.width: Utils.getSizeWithScreenRatio(22)
icon.height: Utils.getSizeWithScreenRatio(22)
// Layout.preferredWidth: icon.width
// Layout.preferredHeight: icon.height
property bool sendVoiceRecordingOnCreated: false
onClicked: {
if (voiceMessage.chatMessage) {
voiceMessage.chatMessage.core.lSend()
sendingAreaStackView.pop()
}
else {
sendVoiceRecordingOnCreated = true
voiceMessage.stopRecording()
}
}
Connections {
target: voiceMessage
function onChatMessageChanged() {
if (sendButton.sendVoiceRecordingOnCreated) {
voiceMessage.chatMessage.core.lSend()
sendButton.sendVoiceRecordingOnCreated = false
sendingAreaStackView.pop()
}
}
}
}
}
}
}
Rectangle {
id: hoverContent
anchors.fill: parent
color: DefaultStyle.main2_0
visible: false
radius: Utils.getSizeWithScreenRatio(20)
EffectImage {
anchors.centerIn: parent
imageSource: AppIcons.filePlus
width: Utils.getSizeWithScreenRatio(37)
height: Utils.getSizeWithScreenRatio(37)
colorizationColor: DefaultStyle.main2_500_main
}
DashRectangle {
x: parent.x
y: parent.y
radius: hoverContent.radius
color: DefaultStyle.main2_500_main
width: parent.width
height: parent.height
}
}
DropArea {
anchors.fill: parent
keys: [ 'text/uri-list' ]
visible: mainItem.dropEnabled
onDropped: (drop) => {
state = ''
if (drop.hasUrls) {
_emitFiles(drop.urls)
}
}
onEntered: state = 'hover'
onExited: state = ''
states: State {
name: 'hover'
PropertyChanges { target: hoverContent; visible: true }
}
}
}