diff --git a/plugins/sms/conversationsdbusinterface.h b/plugins/sms/conversationsdbusinterface.h --- a/plugins/sms/conversationsdbusinterface.h +++ b/plugins/sms/conversationsdbusinterface.h @@ -82,8 +82,21 @@ void requestAllConversationThreads(); Q_SIGNALS: + /** + * Emitted whenever a conversation with no cached messages is added, either because the cache + * is being populated or because a new conversation has been created + */ Q_SCRIPTABLE void conversationCreated(const QVariantMap& msg); + + /** + * Emitted whenever a conversation is being deleted + */ Q_SCRIPTABLE void conversationRemoved(const QString& threadID); + + /** + * Emitted whenever a message is added to a conversation and it is the newest message in the + * conversation + */ Q_SCRIPTABLE void conversationUpdated(const QVariantMap& msg) const; private /*methods*/: diff --git a/plugins/sms/conversationsdbusinterface.cpp b/plugins/sms/conversationsdbusinterface.cpp --- a/plugins/sms/conversationsdbusinterface.cpp +++ b/plugins/sms/conversationsdbusinterface.cpp @@ -108,13 +108,16 @@ // Store the Message in the list corresponding to its thread bool newConversation = !m_conversations.contains(threadId); - m_conversations[threadId].insert(message.date(), message); + const auto& threadPosition = m_conversations[threadId].insert(message.date(), message); m_known_messages[threadId].insert(message.uID()); + // If this message was inserted at the end of the list, it is the latest message in the conversation + bool latestMessage = threadPosition == m_conversations[threadId].end() - 1; + // Tell the world about what just happened if (newConversation) { Q_EMIT conversationCreated(message.toVariant()); - } else { + } else if (latestMessage) { Q_EMIT conversationUpdated(message.toVariant()); } } diff --git a/smsapp/conversationmodel.h b/smsapp/conversationmodel.h --- a/smsapp/conversationmodel.h +++ b/smsapp/conversationmodel.h @@ -54,6 +54,7 @@ void setDeviceId(const QString &/*deviceId*/); Q_INVOKABLE void sendReplyToConversation(const QString& message); + Q_INVOKABLE void requestMoreMessages(const quint32& howMany = 10); private Q_SLOTS: void createRowFromMessage(const QVariantMap &msg, int pos); diff --git a/smsapp/conversationmodel.cpp b/smsapp/conversationmodel.cpp --- a/smsapp/conversationmodel.cpp +++ b/smsapp/conversationmodel.cpp @@ -80,6 +80,15 @@ m_conversationsInterface->replyToConversation(m_threadId, message); } +void ConversationModel::requestMoreMessages(const quint32& howMany) +{ + if (m_threadId.isEmpty()) { + return; + } + const auto& numMessages = rowCount(); + m_conversationsInterface->requestConversation(m_threadId, numMessages, numMessages + howMany); +} + void ConversationModel::createRowFromMessage(const QVariantMap& msg, int pos) { const ConversationMessage message(msg); diff --git a/smsapp/qml/ConversationDisplay.qml b/smsapp/qml/ConversationDisplay.qml --- a/smsapp/qml/ConversationDisplay.qml +++ b/smsapp/qml/ConversationDisplay.qml @@ -38,7 +38,21 @@ property string phoneNumber title: person.person && person.person.name ? person.person.name : phoneNumber + /** + * Build a chat message which is representative of all chat messages + * + * In other words, one which I can use to get a reasonable height guess + */ + ChatMessage { + id: genericMessage + messageBody: "Generic Message Body" + dateTime: new Date('2000-0-0') + visible: false + enabled: false + } + ListView { + id: viewport model: QSortFilterProxyModel { id: model sortOrder: Qt.AscendingOrder @@ -55,12 +69,46 @@ messageBody: model.display sentByMe: model.fromMe dateTime: new Date(model.date) + + ListView.onAdd: { + if (index == viewport.count - 1) + // This message is being inserted at the newest position + // We want to scroll to show it if the user is "almost" looking at it + + // Define some fudge area. If the message is being drawn offscreen but within + // this distance, we move to show it anyway. + // Selected to be genericMessage.height because that value scales for different + // font sizes / DPI / etc. -- Better ideas are welcome! + // Double the value works nicely + var offscreenFudge = 2 * genericMessage.height + + var viewportYBottom = viewport.contentY + viewport.height + + if (y < viewportYBottom + genericMessage.height) { + viewport.currentIndex = index + } + } } - // Set the view to start at the bottom of the page and track new elements if it was not manually scrolled up - currentIndex: atYEnd ? - count - 1 : - currentIndex + onMovementEnded: { + // Unset the highlightRangeMode if it was set previously + highlightRangeMode = ListView.ApplyRange + highlightMoveDuration: -1 // "Re-enable" the highlight animation + + if (atYBeginning) { + // "Lock" the view to the message currently at the beginning of the view + // This prevents the view from snapping to the top of the messages we are about to request + currentIndex = 0 // Index 0 is the beginning of the view + preferredHighlightBegin = visibleArea.yPosition + preferredHighlightEnd = preferredHighlightBegin + currentItem.height + highlightRangeMode = ListView.StrictlyEnforceRange + + highlightMoveDuration = 1 // This is not ideal: I would like to disable the highlight animation altogether + + // Get more messages + model.sourceModel.requestMoreMessages() + } + } } footer: RowLayout {