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/overlays/ChatOverlay.qml

309 lines
9.0 KiB
QML
Raw Permalink Normal View History

2018-10-23 18:52:13 +00:00
import QtGraphicalEffects 1.0
import QtQuick 2.7
import QtQuick.Controls 2.4
import QtQuick.Controls.Material 2.0
import QtQuick.Layouts 1.3
2019-01-26 22:54:08 +00:00
import "../widgets"
import "../widgets/controls" as Awesome
2018-10-28 02:49:14 +00:00
import "../fonts/Twemoji.js" as T
2019-01-30 19:46:22 +00:00
import "../utils.js" as Utils
2018-10-28 02:49:14 +00:00
2018-10-23 18:52:13 +00:00
ColumnLayout {
Layout.fillWidth: true
property bool loading
2019-04-08 20:28:36 +00:00
ListModel { // MESSAGE OBJECTS ARE STORED HERE ...
id: messagesModel
}
ListView {
id: messagesListView
Layout.fillHeight: true
Layout.fillWidth: true
model: messagesModel
spacing: 6
clip: true
ScrollBar.vertical: ScrollBar {}
2019-04-17 21:03:50 +00:00
maximumFlickVelocity: 1250
2019-04-08 20:28:36 +00:00
delegate: Message {
handle: _handle
from: _from
displayName: _displayName
message: _message
rawMessage: _rawMessage
image: _image
messageID: _mid
fromMe: _fromMe
timestamp: _ts
ackd: _ackd
error: _error
}
Connections {
target: gcd
onClearMessages: function() {
messagesModel.clear()
txtMessage.text = ""
}
onAppendMessage: function(handle, from, displayName, message, image, mid, fromMe, ts, ackd, error) {
var msg
try {
msg = JSON.parse(message)
} catch (e) {
msg = {"o": 1, "d": "(legacy message type) " + message}
}
if (msg.o != 1) return
messagesModel.append({
"_handle": handle,
"_from": from,
"_displayName": displayName,
"_message":parse(msg.d, 12),
"_rawMessage":msg.d,
"_image": image,
"_mid": mid,
"_fromMe": fromMe,
"_ts": ts,
"_ackd": ackd,
"_error": error == true ? "this message failed to send" : "",
})
messagesListView.positionViewAtEnd()
// If the window is out of focus, alert the user (makes taskbar light up)
windowItem.alert(0)
if (gcd.os == "android" && windowItem.activeFocusItem == null) {
androidCwtchActivity.notification = "New Content"
}
2019-04-08 20:28:36 +00:00
}
onPrependMessage: function(handle, from, displayName, message, image, mid, fromMe, ts, ackd, error) {
var msg
try {
msg = JSON.parse(message)
} catch (e) {
msg = {"o": 1, "d": "(legacy message type) " + message}
}
if (msg.o != 1) return
messagesModel.insert(0, {
"_handle": handle,
"_from": from,
"_displayName": displayName,
2019-04-17 21:03:50 +00:00
"_message":parse(msg.d, 24),
2019-04-08 20:28:36 +00:00
"_rawMessage":msg.d,
"_image": image,
"_mid": mid,
"_fromMe": fromMe,
"_ts": ts,
"_ackd": ackd,
"_error": error == true ? "this message failed to send" : "",
})
messagesListView.positionViewAtEnd()
}
2019-08-08 21:42:51 +00:00
onUpdateContact: function(_handle, _displayName, _image, _server, _badge, _status, _trusted, _blocked, _loading) {
if (gcd.currentOpenConversation == _handle) {
// Group is Synced OR p2p is Authenticated
if ( (_handle.length == 32 && _status == 4) || _status == 3) {
txtMessage.enabled = true
btnSend.enabled = true
} else {
txtMessage.enabled = false
btnSend.enabled = false
}
}
}
2019-04-08 20:28:36 +00:00
}
}
2018-10-23 18:52:13 +00:00
RowLayout { // THE BOTTOM DRAWER
Rectangle { // MESSAGE ENTRY TEXTFIELD
id: rectMessage
2018-10-31 05:43:22 +00:00
Layout.fillWidth: true
2018-11-22 00:01:17 +00:00
Layout.minimumHeight: 40 * gcd.themeScale
Layout.maximumHeight: 40 * gcd.themeScale
2018-10-23 18:52:13 +00:00
color: "#EDEDED"
border.color: "#AAAAAA"
radius: 10
2018-10-28 02:49:14 +00:00
MouseArea {
anchors.fill: parent
onClicked: txtMessage.focus = true
}
2018-10-23 18:52:13 +00:00
Flickable {
id: flkMessage
2019-02-05 21:48:24 +00:00
anchors.fill: parent//this does nothing! bug in qt
Layout.minimumWidth: parent.width
Layout.maximumWidth: parent.width
2018-10-23 18:52:13 +00:00
Layout.minimumHeight: rectMessage.height
Layout.maximumHeight: rectMessage.height
contentWidth: txtMessage.width
contentHeight: txtMessage.height
boundsBehavior: Flickable.StopAtBounds
clip:true
2018-10-28 02:49:14 +00:00
maximumFlickVelocity: 300
2018-10-29 18:00:21 +00:00
ScrollBar.vertical: ScrollBar{}
2018-10-23 18:52:13 +00:00
TextEdit {
id: txtMessage
2019-04-17 21:03:50 +00:00
font.pixelSize: 10 * gcd.themeScale
2018-10-23 18:52:13 +00:00
text: ""
padding: 6
wrapMode: TextEdit.Wrap
2018-10-28 02:49:14 +00:00
textFormat: Text.RichText
2019-02-05 21:48:24 +00:00
width: rectMessage.width
2018-10-23 18:52:13 +00:00
2018-10-28 02:49:14 +00:00
property bool skipOneUpdate: false
property int previousCursor
2018-10-28 02:49:14 +00:00
2018-10-23 18:52:13 +00:00
Keys.onReturnPressed: { // CTRL+ENTER = LINEBREAK
if ((event.modifiers & Qt.ControlModifier) && gcd.os != "android") {
2018-10-29 18:00:21 +00:00
txtMessage.insert(txtMessage.cursorPosition, "<br>")
2018-10-23 18:52:13 +00:00
} else if (event.modifiers == Qt.NoModifier) {
btnSend.clicked()
}
}
2018-10-28 02:49:14 +00:00
// 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
2018-10-23 18:52:13 +00:00
onTextChanged: {
if (gcd.os == "android") {
return
}
2018-10-28 02:49:14 +00:00
// 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)
2018-10-29 18:00:21 +00:00
//console.log("1: " + txtMessage.getText(0, txtMessage.text.length))
2018-10-28 02:49:14 +00:00
// convert <img> tags back to their emoji form
2019-01-30 21:31:45 +00:00
// Then parse out the rest of the HTML
2018-10-28 02:49:14 +00:00
var nt = restoreEmoji(txtMessage.text)
if (nt != txtMessage.text) {
skipOneUpdate = true
txtMessage.text = nt
}
2018-10-29 18:00:21 +00:00
//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
}
2018-10-28 02:49:14 +00:00
// strip all HTML tags
2019-01-30 21:31:45 +00:00
var theText = Utils.htmlEscaped(txtMessage.getText(0, txtMessage.text.length))
2018-10-29 18:00:21 +00:00
//console.log("3: " + theText)
2018-10-28 02:49:14 +00:00
// convert emoji back to <img> tags
nt = parse(theText, 10)
2018-10-29 18:00:21 +00:00
//console.log("4: " + nt)
2018-10-28 02:49:14 +00:00
// preserve double spacing
nt = nt.replace(/\s\s/g, "&nbsp;&nbsp;");
nt = nt.replace(/\[\:newline\:\]/g, "<br/>");
2019-01-30 21:31:45 +00:00
// then we actually put the updated text in
skipOneUpdate = true
txtMessage.text = nt
txtMessage.cursorPosition = previousCursor
2018-10-28 02:49:14 +00:00
// autoscroll down only when the scrollbar is already all the way down
2018-10-23 18:52:13 +00:00
if (flkMessage.contentY + flkMessage.height >= flkMessage.contentHeight - txtMessage.height && flkMessage.contentHeight > flkMessage.height) {
flkMessage.contentY = flkMessage.contentHeight - flkMessage.height
}
}
}
}
}
ColumnLayout {
2018-10-30 19:48:37 +00:00
id: colRight
2018-10-28 02:49:14 +00:00
spacing: 1
2018-10-23 18:52:13 +00:00
2018-10-30 19:48:37 +00:00
SimpleButton { // SEND MESSAGE BUTTON
2018-10-23 18:52:13 +00:00
id: btnSend
2018-10-30 19:48:37 +00:00
icon: "regular/paper-plane"
text: "send"
2018-10-31 05:43:22 +00:00
Layout.minimumWidth: btnEmoji.width + btnAttach.width + 2
Layout.maximumWidth: btnEmoji.width + btnAttach.width + 2
anchors.right: parent.right
anchors.rightMargin: 2
2018-10-30 19:48:37 +00:00
2018-10-28 02:49:14 +00:00
property int nextMessageID: 1
TextEdit {
id: txtHidden
visible: false
textFormat: Text.RichText
}
2018-10-23 18:52:13 +00:00
onClicked: {
if (txtMessage.text != "") {
2018-10-28 02:49:14 +00:00
txtHidden.text = restoreEmoji(txtMessage.text)
txtHidden.text = txtHidden.text.replace(/<br \/>/g,"[:newline:]");
var txt = txtHidden.text.trim()
if (txt.length > 0) {
var rawText = txtHidden.getText(0, txtHidden.text.length)
var msg = JSON.stringify({"o":1, "d":rawText.replace(/\[\:newline\:\]/g,"\n")})
gcd.sendMessage(msg, nextMessageID++)
}
2018-10-23 18:52:13 +00:00
}
txtMessage.text = ""
}
}
RowLayout {
2018-10-28 02:49:14 +00:00
spacing: 1
2018-10-23 18:52:13 +00:00
2018-10-28 02:49:14 +00:00
SimpleButton { // EMOJI DRAWER BUTTON
2018-10-23 18:52:13 +00:00
id: btnEmoji
2018-10-30 19:48:37 +00:00
icon: "regular/smile"
2018-10-31 05:43:22 +00:00
anchors.right: btnAttach.left
anchors.rightMargin: 2
2018-10-23 18:52:13 +00:00
2019-04-17 21:03:50 +00:00
onClicked: {
gcd.popup("emoji not yet implemented, sorry")
}
2018-10-23 18:52:13 +00:00
}
2018-10-30 19:48:37 +00:00
SimpleButton {
2018-10-23 18:52:13 +00:00
id: btnAttach
2018-10-30 19:48:37 +00:00
icon: "solid/paperclip"
2018-10-31 05:43:22 +00:00
anchors.right: parent.right
anchors.rightMargin: 2
2018-10-23 18:52:13 +00:00
2018-10-30 19:48:37 +00:00
onClicked: {
gcd.popup("attachments not yet implemented, sorry")
}
2018-10-23 18:52:13 +00:00
}
}
}
}
}