Timeline required properties
Move to using required properties for timeline delegates.
This commit is contained in:
committed by
Tobias Fella
parent
a94f46f904
commit
8ad23e7a40
@@ -9,34 +9,276 @@ import org.kde.kirigami 2.15 as Kirigami
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
/**
|
||||
* @brief The base delegate for all messages in the timeline.
|
||||
*
|
||||
* This supports a message bubble plus sender avatar for each message as well as reactions
|
||||
* and read markers. A date section can be show for when the message is on a different
|
||||
* day to the previous one.
|
||||
*
|
||||
* The component is designed for all messages, positioning them in the timeline with
|
||||
* variable padding depending on the window width. Local user messages are highlighted
|
||||
* and can also be aligned to the right if configured.
|
||||
*
|
||||
* This component also supports a compact mode where the padding is adjusted, the
|
||||
* background is hidden and the delegate spans the full width of the timeline.
|
||||
*/
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
property string eventId: model.eventId
|
||||
/**
|
||||
* @brief The index of the delegate in the model.
|
||||
*/
|
||||
required property var index
|
||||
|
||||
property var author: model.author
|
||||
/**
|
||||
* @brief The matrix ID of the message event.
|
||||
*/
|
||||
required property string eventId
|
||||
|
||||
property int delegateType: model.delegateType
|
||||
/**
|
||||
* @brief The timestamp of the message.
|
||||
*/
|
||||
required property var time
|
||||
|
||||
property bool verified: model.verified
|
||||
/**
|
||||
* @brief The message author.
|
||||
*
|
||||
* This should consist of the following:
|
||||
* - id - The matrix ID of the author.
|
||||
* - isLocalUser - Whether the author is the local user.
|
||||
* - avatarSource - The mxc URL for the author's avatar in the current room.
|
||||
* - avatarMediaId - The media ID of the author's avatar.
|
||||
* - avatarUrl - The mxc URL for the author's avatar.
|
||||
* - displayName - The display name of the author.
|
||||
* - display - The name of the author.
|
||||
* - color - The color for the author.
|
||||
* - object - The NeoChatUser object for the author.
|
||||
*
|
||||
* @sa NeoChatUser
|
||||
*/
|
||||
required property var author
|
||||
|
||||
/**
|
||||
* @brief Whether the author should be shown.
|
||||
*/
|
||||
required property bool showAuthor
|
||||
|
||||
/**
|
||||
* @brief The delegate type of the message.
|
||||
*/
|
||||
required property int delegateType
|
||||
|
||||
/**
|
||||
* @brief The display text of the message.
|
||||
*/
|
||||
required property string display
|
||||
|
||||
/**
|
||||
* @brief The display text of the message as plain text.
|
||||
*/
|
||||
required property string plainText
|
||||
|
||||
/**
|
||||
* @brief The formatted body of the message.
|
||||
*/
|
||||
required property string formattedBody
|
||||
|
||||
/**
|
||||
* @brief The date of the event as a string.
|
||||
*/
|
||||
required property string section
|
||||
|
||||
/**
|
||||
* @brief Whether the section header should be shown.
|
||||
*/
|
||||
required property bool showSection
|
||||
|
||||
/**
|
||||
* @brief A model with the reactions to the message in.
|
||||
*/
|
||||
required property var reaction
|
||||
|
||||
/**
|
||||
* @brief Whether the reaction component should be shown.
|
||||
*/
|
||||
required property bool showReactions
|
||||
|
||||
/**
|
||||
* @brief A model with the first 5 other user read markers for this message.
|
||||
*/
|
||||
required property var readMarkers
|
||||
|
||||
/**
|
||||
* @brief String with the display name and matrix ID of the other user read markers.
|
||||
*/
|
||||
required property string readMarkersString
|
||||
|
||||
/**
|
||||
* @brief The number of other users at the event after the first 5.
|
||||
*/
|
||||
required property var excessReadMarkers
|
||||
|
||||
/**
|
||||
* @brief Whether the other user read marker component should be shown.
|
||||
*/
|
||||
required property bool showReadMarkers
|
||||
|
||||
/**
|
||||
* @brief The reply author.
|
||||
*
|
||||
* This should consist of the following:
|
||||
* - id - The matrix ID of the reply author.
|
||||
* - isLocalUser - Whether the reply author is the local user.
|
||||
* - avatarSource - The mxc URL for the reply author's avatar in the current room.
|
||||
* - avatarMediaId - The media ID of the reply author's avatar.
|
||||
* - avatarUrl - The mxc URL for the reply author's avatar.
|
||||
* - displayName - The display name of the reply author.
|
||||
* - display - The name of the reply author.
|
||||
* - color - The color for the reply author.
|
||||
* - object - The NeoChatUser object for the reply author.
|
||||
*
|
||||
* @sa NeoChatUser
|
||||
*/
|
||||
required property var replyAuthor
|
||||
|
||||
/**
|
||||
* @brief The reply content.
|
||||
*
|
||||
* This should consist of the following:
|
||||
* - display - The display text of the reply.
|
||||
* - type - The delegate type of the reply.
|
||||
*/
|
||||
required property var reply
|
||||
|
||||
/**
|
||||
* @brief The media info for the reply event.
|
||||
*
|
||||
* This could be an image, audio, video or file.
|
||||
*
|
||||
* This should consist of the following:
|
||||
* - source - The mxc URL for the media.
|
||||
* - mimeType - The MIME type of the media.
|
||||
* - mimeIcon - The MIME icon name.
|
||||
* - size - The file size in bytes.
|
||||
* - duration - The length in seconds of the audio media (audio/video only).
|
||||
* - width - The width in pixels of the audio media (image/video only).
|
||||
* - height - The height in pixels of the audio media (image/video only).
|
||||
* - tempInfo - mediaInfo (with the same properties as this except no tempInfo) for a temporary image while the file downloads (image/video only).
|
||||
*/
|
||||
required property var replyMediaInfo
|
||||
|
||||
/**
|
||||
* @brief Whether this message is replying to another.
|
||||
*/
|
||||
required property bool isReply
|
||||
|
||||
/**
|
||||
* @brief Whether this message has a local user mention.
|
||||
*/
|
||||
required property bool isHighlighted
|
||||
|
||||
/**
|
||||
* @brief Whether an event is waiting to be accepted by the server.
|
||||
*/
|
||||
required property bool isPending
|
||||
|
||||
/**
|
||||
* @brief Progress info when downloading files.
|
||||
*
|
||||
* @sa Quotient::FileTransferInfo
|
||||
*/
|
||||
required property var progressInfo
|
||||
|
||||
/**
|
||||
* @brief Whether an encrypted message is sent in a verified session.
|
||||
*/
|
||||
required property bool verified
|
||||
|
||||
/**
|
||||
* @brief The mime type of the message's file or media.
|
||||
*/
|
||||
required property var mimeType
|
||||
|
||||
/**
|
||||
* @brief The full message source JSON.
|
||||
*/
|
||||
required property var source
|
||||
|
||||
/**
|
||||
* @brief The x position of the message bubble.
|
||||
*
|
||||
* @note Used for positioning the hover actions.
|
||||
*/
|
||||
readonly property real bubbleX: bubble.x + bubble.anchors.leftMargin
|
||||
|
||||
/**
|
||||
* @brief The y position of the message bubble.
|
||||
*
|
||||
* @note Used for positioning the hover actions.
|
||||
*/
|
||||
readonly property alias bubbleY: mainContainer.y
|
||||
|
||||
/**
|
||||
* @brief The width of the message bubble.
|
||||
*
|
||||
* @note Used for sizing the hover actions.
|
||||
*/
|
||||
readonly property alias bubbleWidth: bubble.width
|
||||
|
||||
/**
|
||||
* @brief Whether this message is hovered.
|
||||
*/
|
||||
readonly property alias hovered: bubble.hovered
|
||||
|
||||
/**
|
||||
* @brief Open the context menu for the message.
|
||||
*/
|
||||
signal openContextMenu
|
||||
|
||||
/**
|
||||
* @brief Open the any message media externally.
|
||||
*/
|
||||
signal openExternally()
|
||||
|
||||
/**
|
||||
* @brief The reply has been clicked.
|
||||
*/
|
||||
signal replyClicked(string eventID)
|
||||
|
||||
onReplyClicked: ListView.view.goToEvent(eventID)
|
||||
|
||||
/**
|
||||
* @brief The component to display the delegate type.
|
||||
*
|
||||
* This is used by the inherited delegates to assign a component to visualise
|
||||
* the message content for that delegate type.
|
||||
*/
|
||||
default property alias innerObject : column.children
|
||||
|
||||
property Item hoverComponent: hoverActions ?? null
|
||||
/**
|
||||
* @brief Whether the bubble background is enabled.
|
||||
*/
|
||||
property bool cardBackground: true
|
||||
property bool showUserMessageOnRight: Config.showLocalMessagesOnRight && model.author.isLocalUser && !Config.compactLayout
|
||||
property bool isHighlighted: model.isHighlighted || isTemporaryHighlighted
|
||||
|
||||
/**
|
||||
* @brief Whether local user messages should be aligned right.
|
||||
*
|
||||
* TODO: make private
|
||||
*/
|
||||
property bool showUserMessageOnRight: Config.showLocalMessagesOnRight && root.author.isLocalUser && !Config.compactLayout
|
||||
|
||||
/**
|
||||
* @brief Whether the message should be highlighted.
|
||||
*/
|
||||
property bool showHighlight: root.isHighlighted || isTemporaryHighlighted
|
||||
|
||||
/**
|
||||
* @brief Whether the message should temporarily be highlighted.
|
||||
*
|
||||
* Normally triggered when jumping to the event in the timeline, e.g. when a reply
|
||||
* is clicked.
|
||||
*/
|
||||
property bool isTemporaryHighlighted: false
|
||||
|
||||
onIsTemporaryHighlightedChanged: if (isTemporaryHighlighted) temporaryHighlightTimer.start()
|
||||
@@ -48,6 +290,7 @@ ColumnLayout {
|
||||
onTriggered: isTemporaryHighlighted = false
|
||||
}
|
||||
|
||||
// TODO: make these private
|
||||
// The bubble and delegate widths are allowed to grow once the ListView gets beyond a certain size
|
||||
// extraWidth defines this as the excess after a certain ListView width, capped to a max value
|
||||
readonly property int extraWidth: parent ? (parent.width >= Kirigami.Units.gridUnit * 46 ? Math.min((parent.width - Kirigami.Units.gridUnit * 46), Kirigami.Units.gridUnit * 20) : 0) : 0
|
||||
@@ -89,23 +332,23 @@ ColumnLayout {
|
||||
id: sectionDelegate
|
||||
|
||||
Layout.fillWidth: true
|
||||
visible: model.showSection
|
||||
labelText: model.showSection ? section : ""
|
||||
visible: root.showSection
|
||||
labelText: root.section
|
||||
}
|
||||
|
||||
QQC2.ItemDelegate {
|
||||
id: mainContainer
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: showAuthor ? Kirigami.Units.largeSpacing : (Config.compactLayout ? 1 : Kirigami.Units.smallSpacing)
|
||||
Layout.topMargin: root.showAuthor ? Kirigami.Units.largeSpacing : (Config.compactLayout ? 1 : Kirigami.Units.smallSpacing)
|
||||
Layout.leftMargin: Kirigami.Units.smallSpacing
|
||||
Layout.rightMargin: Kirigami.Units.smallSpacing
|
||||
|
||||
implicitHeight: Math.max(model.showAuthor ? avatar.implicitHeight : 0, bubble.height)
|
||||
implicitHeight: Math.max(root.showAuthor ? avatar.implicitHeight : 0, bubble.height)
|
||||
|
||||
Component.onCompleted: {
|
||||
if (model.isReply && model.reply === undefined) {
|
||||
messageEventModel.loadReply(sortedMessageEventModel.mapToSource(collapseStateProxyModel.mapToSource(collapseStateProxyModel.index(model.index, 0))))
|
||||
if (root.isReply && root.reply === undefined) {
|
||||
messageEventModel.loadReply(sortedMessageEventModel.mapToSource(collapseStateProxyModel.mapToSource(collapseStateProxyModel.index(root.index, 0))))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,20 +373,20 @@ ColumnLayout {
|
||||
leftMargin: Kirigami.Units.smallSpacing
|
||||
}
|
||||
|
||||
visible: model.showAuthor &&
|
||||
visible: root.showAuthor &&
|
||||
Config.showAvatarInTimeline &&
|
||||
(Config.compactLayout || !showUserMessageOnRight)
|
||||
name: model.author.name ?? model.author.displayName
|
||||
source: model.author.avatarSource
|
||||
color: model.author.color
|
||||
name: root.author.name ?? root.author.displayName
|
||||
source: root.author.avatarSource
|
||||
color: root.author.color
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
userDetailDialog.createObject(QQC2.ApplicationWindow.overlay, {
|
||||
room: currentRoom,
|
||||
user: author.object,
|
||||
displayName: author.displayName
|
||||
user: root.author.object,
|
||||
displayName: root.author.displayName
|
||||
}).open();
|
||||
}
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
@@ -199,7 +442,7 @@ ColumnLayout {
|
||||
width: height
|
||||
Layout.preferredWidth: Kirigami.Units.iconSizes.small
|
||||
Layout.preferredHeight: Kirigami.Units.iconSizes.small
|
||||
visible: model.isPending && Config.showLocalMessagesOnRight
|
||||
visible: root.isPending && Config.showLocalMessagesOnRight
|
||||
}
|
||||
ColumnLayout {
|
||||
id: column
|
||||
@@ -208,17 +451,17 @@ ColumnLayout {
|
||||
id: rowLayout
|
||||
|
||||
spacing: Kirigami.Units.smallSpacing
|
||||
visible: model.showAuthor
|
||||
visible: root.showAuthor
|
||||
|
||||
QQC2.Label {
|
||||
id: nameLabel
|
||||
|
||||
Layout.maximumWidth: contentMaxWidth - timeLabel.implicitWidth - rowLayout.spacing
|
||||
|
||||
text: visible ? author.displayName : ""
|
||||
text: visible ? root.author.displayName : ""
|
||||
textFormat: Text.PlainText
|
||||
font.weight: Font.Bold
|
||||
color: author.color
|
||||
color: root.author.color
|
||||
elide: Text.ElideRight
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
@@ -226,9 +469,9 @@ ColumnLayout {
|
||||
onClicked: {
|
||||
userDetailDialog.createObject(QQC2.ApplicationWindow.overlay, {
|
||||
room: currentRoom,
|
||||
user: author.object,
|
||||
displayName: author.displayName,
|
||||
avatarSource: author.avatarSource
|
||||
user: root.author.object,
|
||||
displayName: root.author.displayName,
|
||||
avatarSource: root.author.avatarSource
|
||||
}).open();
|
||||
}
|
||||
}
|
||||
@@ -236,10 +479,10 @@ ColumnLayout {
|
||||
QQC2.Label {
|
||||
id: timeLabel
|
||||
|
||||
text: visible ? model.time.toLocaleTimeString(Qt.locale(), Locale.ShortFormat) : ""
|
||||
text: visible ? root.time.toLocaleTimeString(Qt.locale(), Locale.ShortFormat) : ""
|
||||
color: Kirigami.Theme.disabledTextColor
|
||||
QQC2.ToolTip.visible: hoverHandler.hovered
|
||||
QQC2.ToolTip.text: model.time.toLocaleString(Qt.locale(), Locale.LongFormat)
|
||||
QQC2.ToolTip.text: root.time.toLocaleString(Qt.locale(), Locale.LongFormat)
|
||||
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||
|
||||
HoverHandler {
|
||||
@@ -252,20 +495,20 @@ ColumnLayout {
|
||||
|
||||
Layout.maximumWidth: contentMaxWidth
|
||||
|
||||
active: model.isReply
|
||||
active: root.isReply
|
||||
visible: active
|
||||
|
||||
sourceComponent: ReplyComponent {
|
||||
name: currentRoom.htmlSafeMemberName(model.replyAuthor.id)
|
||||
avatar: model.replyAuthor.avatarSource
|
||||
color: model.replyAuthor.color
|
||||
mediaInfo: model.replyMediaInfo
|
||||
author: root.replyAuthor
|
||||
type: root.reply.type
|
||||
display: root.reply.display
|
||||
mediaInfo: root.replyMediaInfo
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: replyLoader.item
|
||||
function onReplyClicked() {
|
||||
replyClicked(reply.eventId)
|
||||
replyClicked(root.reply.eventId)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -275,7 +518,7 @@ ColumnLayout {
|
||||
width: height
|
||||
Layout.preferredWidth: Kirigami.Units.iconSizes.small
|
||||
Layout.preferredHeight: Kirigami.Units.iconSizes.small
|
||||
visible: model.isPending && !Config.showLocalMessagesOnRight
|
||||
visible: root.isPending && !Config.showLocalMessagesOnRight
|
||||
}
|
||||
}
|
||||
|
||||
@@ -286,9 +529,9 @@ ColumnLayout {
|
||||
anchors.fill: parent
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
||||
color: {
|
||||
if (model.author.isLocalUser) {
|
||||
if (root.author.isLocalUser) {
|
||||
return Kirigami.ColorUtils.tintWithAlpha(Kirigami.Theme.backgroundColor, Kirigami.Theme.highlightColor, 0.15)
|
||||
} else if (root.isHighlighted) {
|
||||
} else if (root.showHighlight) {
|
||||
return Kirigami.Theme.positiveBackgroundColor
|
||||
} else {
|
||||
return Kirigami.Theme.backgroundColor
|
||||
@@ -296,7 +539,7 @@ ColumnLayout {
|
||||
}
|
||||
radius: Kirigami.Units.smallSpacing
|
||||
shadow.size: Kirigami.Units.smallSpacing
|
||||
shadow.color: root.isHighlighted ? Qt.rgba(0.0, 0.0, 0.0, 0.10) : Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.10)
|
||||
shadow.color: root.showHighlight ? Qt.rgba(0.0, 0.0, 0.0, 0.10) : Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.10)
|
||||
border.color: Kirigami.ColorUtils.tintWithAlpha(color, Kirigami.Theme.textColor, 0.15)
|
||||
border.width: 1
|
||||
|
||||
@@ -332,18 +575,18 @@ ColumnLayout {
|
||||
Layout.leftMargin: showUserMessageOnRight ? 0 : bubble.x + bubble.anchors.leftMargin
|
||||
Layout.rightMargin: showUserMessageOnRight ? Kirigami.Units.largeSpacing : 0
|
||||
|
||||
visible: showReactions
|
||||
model: reaction
|
||||
visible: root.showReactions
|
||||
model: root.reaction
|
||||
|
||||
onReactionClicked: (reaction) => currentRoom.toggleReaction(eventId, reaction)
|
||||
onReactionClicked: (reaction) => currentRoom.toggleReaction(root.eventId, reaction)
|
||||
}
|
||||
AvatarFlow {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
Layout.rightMargin: Kirigami.Units.largeSpacing
|
||||
visible: showReadMarkers
|
||||
model: readMarkers
|
||||
toolTipText: readMarkersString
|
||||
excessAvatars: excessReadMarkers
|
||||
visible: root.showReadMarkers
|
||||
model: root.readMarkers
|
||||
toolTipText: root.readMarkersString
|
||||
excessAvatars: root.excessReadMarkers
|
||||
}
|
||||
|
||||
function isVisibleInTimeline() {
|
||||
@@ -352,31 +595,31 @@ ColumnLayout {
|
||||
}
|
||||
|
||||
/// Open message context dialog for file and videos
|
||||
function openFileContext(event, file) {
|
||||
function openFileContext(file) {
|
||||
const contextMenu = fileDelegateContextMenu.createObject(root, {
|
||||
author: event.author,
|
||||
message: event.plainText,
|
||||
eventId: event.eventId,
|
||||
source: event.source,
|
||||
author: root.author,
|
||||
message: root.plainText,
|
||||
eventId: root.eventId,
|
||||
source: root.source,
|
||||
file: file,
|
||||
mimeType: event.mimeType,
|
||||
progressInfo: event.progressInfo,
|
||||
plainMessage: event.plainText,
|
||||
mimeType: root.mimeType,
|
||||
progressInfo: root.progressInfo,
|
||||
plainMessage: root.plainText,
|
||||
});
|
||||
contextMenu.open();
|
||||
}
|
||||
|
||||
/// Open context menu for normal message
|
||||
function openMessageContext(event, selectedText, plainMessage) {
|
||||
function openMessageContext(selectedText) {
|
||||
const contextMenu = messageDelegateContextMenu.createObject(root, {
|
||||
selectedText: selectedText,
|
||||
author: event.author,
|
||||
message: event.plainText,
|
||||
eventId: event.eventId,
|
||||
formattedBody: event.formattedBody,
|
||||
source: event.source,
|
||||
eventType: event.eventType,
|
||||
plainMessage: event.plainText,
|
||||
author: root.author,
|
||||
message: root.plainText,
|
||||
eventId: root.eventId,
|
||||
formattedBody: root.formattedBody,
|
||||
source: root.source,
|
||||
eventType: root.delegateType,
|
||||
plainMessage: root.plainText,
|
||||
});
|
||||
contextMenu.open();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user