import QtQuick import QtQuick.Controls import QtQuick.Controls.Basic import "." as Msg Rectangle { id: main color: hourglass.visible ? "#d2eecc" : "#e5d8bd" ListView { id: view objectName: "message_view" anchors.topMargin: rectFind.height + 4 anchors.fill: parent anchors.bottomMargin: rectEdit.height + 3 anchors.margins: 5 model: messages clip: true visible: false property int fontSize: 18 delegate: SwipeDelegate { id: swipeDelegate width: view.width height: delegate.height clip: true onPressAndHold: Lisp.call("msg:message-press-and-hold", model.text) onDoubleClicked: Lisp.call("msg:swipe-to-left") background: Item { id: delegate width: Math.max(text.paintedWidth, rowSender.width + view.fontSize / 4 * text.padding) + 2 * text.padding + view.fontSize / 4 height: model.hidden ? 0 : (text.contentHeight + 2 * text.padding + sender.contentHeight + 8) Rectangle { anchors.centerIn: parent width: parent.width height: parent.height - 4 color: model.me ? "#f2f2f2" : "#ffffcc" radius: 12 Row { id: rowSender padding: text.padding spacing: padding - 2 AnimatedImage { id: semaphore anchors.verticalCenter: sender.verticalCenter anchors.verticalCenterOffset: -0.5 width: view.fontSize / 2 - 1 height: width playing: false source: "../../img/semaphore.gif" currentFrame: model.ackState ? parseInt(model.ackState.substr(2), 16) : 0 // see 'qml:hex' visible: model.me } Text { id: sender font.pixelSize: 2/3 * view.fontSize font.family: fontText.name color: "#8B0000" text: model.senderName ? model.senderName : model.sender } } Text { id: timestamp x: delegate.width - contentWidth - text.padding y: text.padding font.pixelSize: 2/3 * view.fontSize font.family: fontText.name color: "#505050" text: model.hour MouseArea { anchors.fill: parent onClicked: Lisp.call("msg:show-date", model.timestamp) } } Text { id: text y: sender.contentHeight width: main.width - 10 padding: 5 wrapMode: Text.Wrap font.pixelSize: view.fontSize font.family: fontText.name color: "#303030" textFormat: Text.StyledText // for 'paintedWidth' to always work text: model.text } } } ListView.onRemove: SequentialAnimation { PropertyAction { target: swipeDelegate property: "ListView.delayRemove" value: true } NumberAnimation { target: swipeDelegate property: "height" to: 0 easing.type: Easing.InOutQuad } PropertyAction { target: swipeDelegate property: "ListView.delayRemove" value: false } } swipe.left: Rectangle { y: 2 width: 35 height: parent.height - 2 * y color: "#dd4141" radius: 12 Image { anchors.centerIn: parent width: 12 height: width source: "../../img/delete.png" } MouseArea { anchors.fill: parent onClicked: { var mid = model.mid view.model.remove(index) Lisp.call("db:delete-message", mid) } } } } } ListModel { id: messages objectName: "messages" // hack to define all model key _types_ ListElement { receiver: ""; sender: ""; senderName: ""; timestamp: ""; hour: ""; text: ""; text2: ""; mid: ""; ackState: ""; me: true; hidden: false } function addMessage(message) { append(message) } function changeState(state, mid) { for (var i = count - 1; i >= 0; i--) { if (get(i).mid === mid) { setProperty(i, "ackState", state) break } } } function find(term) { for (var i = 0; i < count; i++) { var text = get(i).text var highlighted = Lisp.call("msg:highlight-term", text, term) if (highlighted) { if (!get(i).text2) { setProperty(i, "text2", text) } setProperty(i, "text", highlighted) } setProperty(i, "hidden", !highlighted) } view.positionViewAtBeginning() } function clearFind() { for (var i = 0; i < count; i++) { var text2 = get(i).text2 if (text2) { setProperty(i, "text", text2) setProperty(i, "text2", "") } setProperty(i, "hidden", false) } } Component.onCompleted: remove(0) // see hack above } // find text TextField { id: findText objectName: "find_text" y: 1 width: parent.width height: visible ? (edit.paintedHeight + 14) : 0 font.pixelSize: view.fontSize font.family: fontText.name selectionColor: "#228ae3" selectedTextColor: "white" placeholderText: qsTr("search") visible: false background: Rectangle { id: rectFind color: "white" border.width: 3 border.color: findText.focus ? "dodgerblue" : "#c0c0c0" radius: 12 } onEditingFinished: Lisp.call("msg:find-text", text) } // send text Rectangle { id: rectEdit anchors.bottom: parent.bottom anchors.bottomMargin: 1 width: parent.width height: edit.paintedHeight + 14 color: "white" border.width: 3 border.color: edit.focus ? (edit.tooLong ? "#ff5f57" : "dodgerblue") : "#c0c0c0" radius: 12 TextArea { id: edit objectName: "edit" anchors.fill: parent textFormat: TextEdit.PlainText font.pixelSize: view.fontSize font.family: fontText.name selectionColor: "#228ae3" selectedTextColor: "white" wrapMode: TextEdit.Wrap textMargin: 0 placeholderText: qsTr("message") property bool tooLong: false onLengthChanged: if (length > 150) Lisp.call("msg:check-utf8-length", text) Keys.onEscapePressed: emojis.visible = false Image { y: 8 anchors.right: parent.right anchors.rightMargin: 7 width: edit.font.pixelSize + 1 height: width source: "../../img/emoji.png" opacity: 0.55 visible: edit.focus && (Qt.platform.os !== "android") && (Qt.platform.os !== "ios") MouseArea { anchors.fill: parent onClicked: emojis.visible = !emojis.visible } } } Image { id: send anchors.right: parent.right anchors.bottom: parent.top anchors.margins: 3 width: 38 height: width source: "../../img/send.png" visible: edit.focus && !edit.tooLong MouseArea { anchors.fill: parent onClicked: { edit.focus = Qt.NoFocus Lisp.call("lora:send-message", edit.text) edit.clear() } } } Image { id: broadcast anchors.right: send.left anchors.bottom: parent.top anchors.margins: 3 width: 38 height: width opacity: 0.7 source: "../../img/broadcast.png" visible: send.visible && animation.running SequentialAnimation { id: animation loops: Animation.Infinite running: rootItem.broadcast ScaleAnimator { target: broadcast from: 0.8; to: 1.0 duration: 500 easing.type: Easing.InOutSine } ScaleAnimator { target: broadcast from: 1.0; to: 0.8 duration: 500 easing.type: Easing.InOutSine } } } } Msg.Emojis { id: emojis anchors.bottom: rectEdit.top anchors.bottomMargin: -1 width: main.width visible: false } }