Warning, /network/kdeconnect-kde/smsapp/qml/ConversationDisplay.qml is written in an unsupported language. File is not indexed.

0001 /**
0002  * SPDX-FileCopyrightText: 2018 Aleix Pol Gonzalez <aleixpol@kde.org>
0003  * SPDX-FileCopyrightText: 2018 Nicolas Fella <nicolas.fella@gmx.de>
0004  * SPDX-FileCopyrightText: 2018 Simon Redman <simon@ergotech.com>
0005  *
0006  * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0007  */
0008 
0009 import QtQuick
0010 import QtQuick.Controls as Controls
0011 import QtQuick.Layouts
0012 import org.kde.people
0013 import org.kde.kirigami as Kirigami
0014 import org.kde.kdeconnect.sms
0015 import Qt5Compat.GraphicalEffects
0016 
0017 Kirigami.ScrollablePage
0018 {
0019     id: page
0020 
0021     property bool deviceConnected
0022     property string conversationId
0023     property bool isMultitarget
0024     property string initialMessage
0025     property string invalidId: "-1"
0026 
0027     property bool isInitalized: false
0028 
0029     property var conversationModel: ConversationModel {
0030         deviceId: AppData.deviceId
0031         threadId: page.conversationId
0032         addressList: page.addresses
0033 
0034         onLoadingFinished: {
0035             page.isInitalized = true
0036         }
0037     }
0038 
0039     property var addresses
0040     title: SmsHelper.getTitleForAddresses(addresses)
0041 
0042     Component.onCompleted: {
0043         if (initialMessage.length > 0) {
0044             sendingArea.text = initialMessage;
0045             initialMessage = ""
0046         }
0047         if (conversationId == invalidId) {
0048             isInitalized = true
0049         }
0050     }
0051 
0052     /**
0053      * Build a chat message which is representative of all chat messages
0054      *
0055      * In other words, one which I can use to get a reasonable height guess
0056      */
0057     ChatMessage {
0058         id: genericMessage
0059         name: "Generic Sender"
0060         messageBody: "Generic Message Body"
0061         dateTime: new Date('2000-0-0')
0062         visible: false
0063         enabled: false
0064     }
0065 
0066     ListView {
0067         id: viewport
0068         model: QSortFilterProxyModel {
0069             id: model
0070             sortOrder: Qt.AscendingOrder
0071             sortRole: ConversationModel.DateRole
0072             sourceModel: conversationModel
0073         }
0074 
0075         spacing: Kirigami.Units.largeSpacing
0076         highlightMoveDuration: 0
0077 
0078         Controls.BusyIndicator {
0079             running: !isInitalized
0080             anchors.centerIn: parent
0081         }
0082 
0083         header: Item {
0084             height: Kirigami.Units.largeSpacing * 2
0085         }
0086         headerPositioning: ListView.InlineHeader
0087 
0088         onContentHeightChanged: {
0089             if (viewport.contentHeight <= 0) {
0090                 return
0091             }
0092 
0093             if (!isInitalized) {
0094                 // If we aren't initialized, we need to request enough messages to fill the view
0095                 // In order to do that, request one more message until we have enough
0096                 if (viewport.contentHeight < viewport.height) {
0097                     console.debug("Requesting another message to fill the screen")
0098                     conversationModel.requestMoreMessages(1)
0099                 } else {
0100                     // Finish initializing: Scroll to the bottom of the view
0101 
0102                     // View the most-recent message
0103                     viewport.forceLayout()
0104                     Qt.callLater(viewport.positionViewAtEnd)
0105                     isInitalized = true
0106                 }
0107                 return
0108             }
0109         }
0110 
0111         delegate: ChatMessage {
0112             name: model.sender
0113             messageBody: model.display
0114             sentByMe: model.fromMe
0115             dateTime: new Date(model.date)
0116             multiTarget: isMultitarget
0117             attachmentList: model.attachments
0118 
0119             width: viewport.width
0120 
0121             ListView.onAdd: {
0122                 if (!isInitalized) {
0123                     return
0124                 }
0125 
0126                 if (index == viewport.count - 1) {
0127                     // This message is being inserted at the newest position
0128                     // We want to scroll to show it if the user is "almost" looking at it
0129 
0130                     // Define some fudge area. If the message is being drawn offscreen but within
0131                     // this distance, we move to show it anyway.
0132                     // Selected to be genericMessage.height because that value scales for different
0133                     // font sizes / DPI / etc. -- Better ideas are welcome!
0134                     // Double the value works nicely
0135                     var offscreenFudge = 2 * genericMessage.height
0136 
0137                     var viewportYBottom = viewport.contentY + viewport.height
0138 
0139                     if (y < viewportYBottom + genericMessage.height) {
0140                         viewport.highlightMoveDuration = -1
0141                         viewport.currentIndex = index
0142                     }
0143                 }
0144             }
0145 
0146             onMessageCopyRequested: {
0147                 SmsHelper.copyToClipboard(message)
0148             }
0149         }
0150 
0151         /// As the user scrolls, load more messages when they get to the top.
0152         /// This used to use the onMovementEnded signal, but at some point
0153         /// that signal stopped being emitted reliably when scrolling with
0154         /// the mouse. onContentYChanged is fine for our use, just a bit noisy.
0155         onContentYChanged: {
0156             if (!isInitalized) {
0157                 return
0158             }
0159 
0160             // If we have scrolled near to the top, request more messages
0161             // This threshold of visibleArea.yPosition has been chosen experimentally, but
0162             // should generally be OK because it is defined as a ratio of the content to the visible
0163             // area. As more messages get loaded into our view, this constant will start to be
0164             // less-sane, meaning we will request messages earlier as the user scrolls back in time.
0165             // This is probably a good thing, because it means that scrolling back further and further
0166             // quickly, will be more likely to be smooth.
0167             // Combined with `atYBeginning`, the view scrolls smoothly for me, long past the point where
0168             // the rest of the app is stable, and past the point where Android can fetch messages fast
0169             // enough.
0170             if (visibleArea.yPosition < 0.075 || atYBeginning) {
0171                 // "Lock" the view to the message currently at the beginning of the view
0172                 // This prevents the view from snapping to the top of the messages we are about to request
0173                 currentIndex = 0 // Index 0 is the beginning of the view
0174                 preferredHighlightBegin = visibleArea.yPosition
0175                 preferredHighlightEnd = preferredHighlightBegin + currentItem.height
0176                 highlightRangeMode = ListView.StrictlyEnforceRange
0177 
0178                 highlightMoveDuration = 0
0179 
0180                 // Get more messages
0181                 conversationModel.requestMoreMessages()
0182             }
0183         }
0184     }
0185 
0186     footer: SendingArea {
0187         id: sendingArea
0188 
0189         width: parent.width
0190         addresses: page.addresses
0191     }
0192 }