This repository has been archived on 2021-06-24. You can view files and clone it, but cannot push or open issues or pull requests.
ui/qml/widgets/MessageEditor.qml

278 lines
10 KiB
QML

import QtGraphicalEffects 1.0
import QtQuick 2.7
import QtQuick.Controls 2.4
import QtQuick.Controls.Material 2.0
import QtQuick.Layouts 1.3
import "../opaque" as Opaque
import "../opaque/controls" as Awesome
import "../opaque/fonts/Twemoji.js" as T
import "../utils.js" as Utils
import "../widgets"
import "../opaque/theme"
import "../const"
import "../opaque/fonts"
ColumnLayout {
id: root
property int state: Const.state_disconnected
property string authorization: ""
signal sendClicked(string messageText)
function updateState() {
// TODO unapproved
if (root.authorization == Const.auth_blocked) {
statusSeperator.color = Theme.messageStatusBlockedColor
//: Peer is blocked
statusText.text = qsTr("peer-blocked-message")
statusText.color = Theme.messageStatusBlockedTextColor
txtMessage.enabled = false
btnSend.enabled = false
btnAttach.enabled = false
btnEmoji.enabled = false
} else if ( (Utils.isGroup(gcd.selectedConversation) && root.state == Const.state_synced) || (Utils.isPeer(gcd.selectedConversation) && root.state == Const.state_authenticated) ) {
statusSeperator.color = Theme.messageStatusNormalColor
statusText.text = ""
txtMessage.enabled = true
btnSend.enabled = true
btnSend.enabled = true
btnAttach.enabled = true
btnEmoji.enabled = true
} else {
statusSeperator.color = Theme.messageStatusAlertColor
//: Peer is offline, messages can't be delivered right now
statusText.text = qsTr("peer-offline-message")
statusText.color = Theme.messageStatusAlertTextColor
txtMessage.enabled = false
btnSend.enabled = false
btnSend.enabled = false
btnAttach.enabled = false
btnEmoji.enabled = false
}
}
Opaque.EmojiDrawer {
id: emojiDrawer
Layout.fillWidth: true
size: 24 * gcd.themeScale
onPicked: function(shortcode) {
if (!txtMessage.enabled) return
txtMessage.insert(txtMessage.cursorPosition, ":" + shortcode + ":")
}
}
Rectangle {
id: statusSeperator
Layout.fillWidth: true
height: statusText.visible ? statusText.height + (4 * gcd.themeScale) : 3 * gcd.themeScale
implicitHeight: height
color: Theme.dividerColor
Opaque.ScalingLabel {
id: statusText
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
text: ""
visible: text != ""
size: Theme.chatMetaTextSize
font.family: Fonts.applicationFontRegular.name
font.styleName: "Bold"
}
}
RowLayout { // THE BOTTOM DRAWER
id: rowDrawer
Layout.fillWidth: true
Rectangle { // MESSAGE ENTRY TEXTFIELD
id: rectMessage
Layout.fillWidth: true
Layout.minimumHeight: 120 * gcd.themeScale
Layout.maximumHeight: 120 * gcd.themeScale
color: Theme.backgroundMainColor
Opaque.Flickable {
id: flkMessage
anchors.fill: parent //this does nothing! bug in qt
contentWidth: txtMessage.width
contentHeight: txtMessage.height + txtma.height
TextArea {
id: txtMessage
font.pixelSize: Theme.chatSize * gcd.themeScale
text: ""
padding: 6 * gcd.themeScale
wrapMode: TextEdit.Wrap
textFormat: Text.PlainText
width: rectMessage.width
color: Theme.mainTextColor
property bool skipOneUpdate: false
property int previousCursor
Keys.onReturnPressed: { // CTRL+ENTER = LINEBREAK // TODO: Broken
if ((event.modifiers & Qt.ControlModifier) && gcd.os != "android") {
txtMessage.insert(txtMessage.cursorPosition, "<br>")
} else if (event.modifiers == Qt.NoModifier) {
btnSend.clicked()
}
}
// welcome to the emoji parser! it is horrifying code that needs to leave in <img src="[emoji]">
// while also stripping any other tag, including other images.
// TODO: this probably breaks if people actually do want to paste html
onTextChanged: {
if (gcd.os == "android") {
return
}
// we're taking advantage of TextEdit.getText()'s parsing capability, which means occasionally
// passing text into it to be filtered. this prevents recursive calls putting us into an
// infinite loop
if (skipOneUpdate) {
skipOneUpdate = false
return
}
previousCursor = cursorPosition
//console.log("onTextChanged() at position " + previousCursor)
//console.log("1: " + txtMessage.getText(0, txtMessage.text.length))
// convert <img> tags back to their emoji form
// Then parse out the rest of the HTML
var nt = restoreEmoji(txtMessage.text)
if (nt != txtMessage.text) {
skipOneUpdate = true
txtMessage.text = nt
}
//console.log("2: " + txtMessage.getText(0, txtMessage.text.length))
var preserveSpaces = txtMessage.text.replace(/<br \/>/g,"[:newline:]");
if (preserveSpaces != txtMessage.text) {
skipOneUpdate = true
txtMessage.text = preserveSpaces
}
// strip all HTML tags
var theText = Utils.htmlEscaped(txtMessage.getText(0, txtMessage.text.length))
//console.log("3: " + theText)
// convert emoji back to <img> tags
nt = parse(theText, 10)
//console.log("4: " + nt)
// preserve double spacing
nt = nt.replace(/\s\s/g, "&nbsp;&nbsp;");
nt = nt.replace(/\[\:newline\:\]/g, "<br/>");
// then we actually put the updated text in
skipOneUpdate = true
txtMessage.text = nt
txtMessage.cursorPosition = previousCursor
// autoscroll down only when the scrollbar is already all the way down
if (flkMessage.contentY + flkMessage.height >= flkMessage.contentHeight - txtMessage.height && flkMessage.contentHeight > flkMessage.height) {
flkMessage.contentY = flkMessage.contentHeight - flkMessage.height
}
}
}
MouseArea {
id: txtma
anchors.top: txtMessage.bottom
width: flkMessage.width
height: Math.max(rectMessage.height - txtMessage.height, 0)
onClicked: { txtMessage.focus = true }
}
}
}
ColumnLayout {
id: colRight
spacing: 0
width: 100 * gcd.themeScale
Layout.minimumWidth: width
Layout.preferredWidth: width
Layout.maximumWidth: width
Opaque.Icon { // SEND MESSAGE BUTTON
id: btnSend
source: gcd.assetPath + "core/send-24px.webp"
width: colRight.width
height: 50 * gcd.themeScale
size: 36 * gcd.themeScale
backgroundColor: enabled ? Theme.defaultButtonColor : Theme.defaultButtonDisabledColor
hilightBackgroundColor: Theme.defaultButtonActiveColor
iconColor: Theme.defaultButtonTextColor
property int nextMessageID: 1
onClicked: {
var txt = txtMessage.text.trim()
if (txt.length > 0) {
root.sendClicked(txt)
}
txtMessage.text = ""
}
}
RowLayout {
Layout.alignment: Qt.AlignHCenter
spacing: 0
Opaque.Icon { // EMOJI DRAWER BUTTON
id: btnEmoji
source: gcd.assetPath + "core/mood-24px.webp"
size: 25
height: 36 * gcd.themeScale
width: 48 * gcd.themeScale
backgroundColor: enabled ? Theme.altButtonColor : Theme.altButtonDisabledColor
hilightBackgroundColor: backgroundColor
iconColor: enabled ? Theme.altButtonTextColor : Theme.altButtonDisabledTextColor
onClicked: emojiDrawer.visible ? emojiDrawer.slideclosed() : emojiDrawer.slideopen()
}
Opaque.Icon {
id: btnAttach
source: gcd.assetPath + "core/attach_file-24px.webp"
size: 25
height: 36 * gcd.themeScale
width: 48 * gcd.themeScale
backgroundColor: enabled ? Theme.altButtonColor : Theme.altButtonDisabledColor
hilightBackgroundColor: backgroundColor
iconColor: enabled ? Theme.altButtonTextColor : Theme.altButtonDisabledTextColor
onClicked: {
gcd.popup("attachments not yet implemented, sorry")
}
}
}
}
}
Connections {
target: gcd
onClearMessages: function() {
txtMessage.text = ""
}
}
}