Loading and End of Timeline Delegates

Add delegate for showing the user a loading indicator and for the beginning of the timeline.

BUG: 455045
BUG: 465285
This commit is contained in:
James Graham
2023-11-20 17:10:56 +00:00
parent 0dbef58ff2
commit 5efd17d370
15 changed files with 370 additions and 41 deletions

View File

@@ -99,6 +99,7 @@ DelegateChooser {
connection: root.connection
}
}
DelegateChoice {
roleValue: DelegateType.LiveLocation
delegate: LiveLocationDelegate {
@@ -107,6 +108,18 @@ DelegateChooser {
}
}
DelegateChoice {
roleValue: DelegateType.Loading
delegate: LoadingDelegate {}
}
DelegateChoice {
roleValue: DelegateType.TimelineEnd
delegate: TimelineEndDelegate {
room: root.room
}
}
DelegateChoice {
roleValue: DelegateType.Other
delegate: Item {}

View File

@@ -0,0 +1,15 @@
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
import QtQuick
import org.kde.kirigami as Kirigami
import org.kde.neochat
TimelineDelegate {
id: root
contentItem: Kirigami.PlaceholderMessage {
text: i18n("Loading…")
}
}

View File

@@ -23,17 +23,17 @@ Kirigami.Page {
required property NeoChatConnection connection
/**
* @brief The MessageEventModel to use.
* @brief The TimelineModel to use.
*
* Required so that new events can be requested when the end of the current
* local timeline is reached.
*
* @note For loading a room in a different window, override this with a new
* MessageEventModel set with the room to be shown.
* TimelineModel set with the room to be shown.
*
* @sa MessageEventModel
* @sa TimelineModel
*/
property MessageEventModel messageEventModel: RoomManager.messageEventModel
property TimelineModel timelineModel: RoomManager.timelineModel
/**
* @brief The MessageFilterModel to use.
@@ -41,9 +41,9 @@ Kirigami.Page {
* This model has the filtered list of events that should be shown in the timeline.
*
* @note For loading a room in a different window, override this with a new
* MessageFilterModel with the new MessageEventModel as the source model.
* MessageFilterModel with the new TimelineModel as the source model.
*
* @sa MessageEventModel, MessageFilterModel
* @sa TimelineModel, MessageFilterModel
*/
property MessageFilterModel messageFilterModel: RoomManager.messageFilterModel
@@ -56,7 +56,7 @@ Kirigami.Page {
* @note For loading a room in a different window, override this with a new
* MediaMessageFilterModel with the new MessageFilterModel as the source model.
*
* @sa MessageEventModel, MessageFilterModel
* @sa TimelineModel, MessageFilterModel
*/
property MediaMessageFilterModel mediaMessageFilterModel: RoomManager.mediaMessageFilterModel
@@ -120,7 +120,7 @@ Kirigami.Page {
sourceComponent: TimelineView {
id: timelineView
currentRoom: root.currentRoom
messageEventModel: root.messageEventModel
timelineModel: root.timelineModel
messageFilterModel: root.messageFilterModel
actionsHandler: root.actionsHandler
onFocusChatBar: {

View File

@@ -29,7 +29,7 @@ Kirigami.ApplicationWindow {
disableCancelShortcut: true
connection: root.connection
messageEventModel: MessageEventModel {
timelineModel: TimelineModel {
room: currentRoom
}
messageFilterModel: MessageFilterModel {

View File

@@ -0,0 +1,90 @@
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
import QtQuick
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.labs.components as KirigamiComponents
import org.kde.neochat
TimelineDelegate {
id: root
/**
* @brief The current room that user is viewing.
*/
required property NeoChatRoom room
contentItem: ColumnLayout {
RowLayout {
Layout.topMargin: Kirigami.Units.largeSpacing
Layout.bottomMargin: Kirigami.Units.largeSpacing
Layout.leftMargin: Kirigami.Units.largeSpacing
Layout.rightMargin: Kirigami.Units.largeSpacing
spacing: Kirigami.Units.largeSpacing
KirigamiComponents.Avatar {
Layout.preferredWidth: Kirigami.Units.iconSizes.large
Layout.preferredHeight: Kirigami.Units.iconSizes.large
name: root.room ? root.room.displayName : ""
source: root.room && root.room.avatarMediaId ? ("image://mxc/" + root.room.avatarMediaId) : ""
Rectangle {
visible: room.usesEncryption
color: Kirigami.Theme.backgroundColor
width: Kirigami.Units.gridUnit
height: Kirigami.Units.gridUnit
anchors {
bottom: parent.bottom
right: parent.right
}
radius: Math.round(width / 2)
Kirigami.Icon {
source: "channel-secure-symbolic"
anchors.fill: parent
}
}
}
ColumnLayout {
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
spacing: 0
Kirigami.Heading {
Layout.fillWidth: true
text: root.room ? root.room.displayName : i18n("No name")
textFormat: Text.PlainText
wrapMode: Text.Wrap
}
Kirigami.SelectableLabel {
Layout.fillWidth: true
font: Kirigami.Theme.smallFont
textFormat: TextEdit.PlainText
visible: root.room && root.room.canonicalAlias
text: root.room && root.room.canonicalAlias ? root.room.canonicalAlias : ""
}
}
}
Kirigami.SelectableLabel {
Layout.fillWidth: true
Layout.leftMargin: Kirigami.Units.largeSpacing
Layout.rightMargin: Kirigami.Units.largeSpacing
Layout.bottomMargin: Kirigami.Units.largeSpacing
text: i18n("This is the beginning of the chat. There are no historical messages beyond this point.")
wrapMode: Text.Wrap
onLinkActivated: link => UrlHelper.openUrl(link)
}
}
}

View File

@@ -28,12 +28,12 @@ QQC2.ScrollView {
property bool roomChanging: false
/**
* @brief The MessageEventModel to use.
* @brief The TimelineModel to use.
*
* Required so that new events can be requested when the end of the current
* local timeline is reached.
*/
required property MessageEventModel messageEventModel
required property TimelineModel timelineModel
/**
* @brief The MessageFilterModel to use.
@@ -85,16 +85,16 @@ QQC2.ScrollView {
running: messageListView.atYBeginning
triggeredOnStart: true
onTriggered: {
if (messageListView.atYBeginning && root.messageEventModel.canFetchMore(root.messageEventModel.index(0, 0))) {
root.messageEventModel.fetchMore(root.messageEventModel.index(0, 0));
if (messageListView.atYBeginning && root.timelineModel.messageEventModel.canFetchMore(root.timelineModel.index(0, 0))) {
root.timelineModel.messageEventModel.fetchMore(root.timelineModel.index(0, 0));
}
}
repeat: true
}
// HACK: The view should do this automatically but doesn't.
onAtYBeginningChanged: if (atYBeginning && root.messageEventModel.canFetchMore(root.messageEventModel.index(0, 0))) {
root.messageEventModel.fetchMore(root.messageEventModel.index(0, 0));
onAtYBeginningChanged: if (atYBeginning && root.timelineModel.messageEventModel.canFetchMore(root.timelineModel.index(0, 0))) {
root.timelineModel.messageEventModel.fetchMore(root.timelineModel.index(0, 0));
}
Timer {
@@ -270,7 +270,7 @@ QQC2.ScrollView {
}
Connections {
target: root.messageEventModel
target: root.timelineModel
function onRowsInserted() {
markReadIfVisibleTimer.restart()
@@ -311,7 +311,7 @@ QQC2.ScrollView {
Connections {
//enabled: Config.showFancyEffects
target: root.messageEventModel
target: root.timelineModel.messageEventModel
function onFancyEffectsReasonFound(fancyEffect) {
fancyEffectsContainer.processFancyEffectsReason(fancyEffect)
@@ -336,10 +336,10 @@ QQC2.ScrollView {
}
function eventToIndex(eventID) {
const index = root.messageEventModel.eventIdToRow(eventID)
const index = root.timelineModel.messageEventModel.eventIdToRow(eventID)
if (index === -1)
return -1
return root.messageFilterModel.mapFromSource(root.messageEventModel.index(index, 0)).row
return root.messageFilterModel.mapFromSource(root.timelineModel.index(index, 0)).row
}
function firstVisibleIndex() {