Skip to content
Snippets Groups Projects
Unverified Commit b3da8738 authored by Nicolas Werner's avatar Nicolas Werner Committed by GitHub
Browse files

Merge pull request #951 from maltee1/fix_timeline_scrollbar

Fix timeline scrollbar
parents fd25f6ee 8b6d4a57
No related branches found
No related tags found
No related merge requests found
Pipeline #2685 passed
...@@ -23,552 +23,544 @@ Item { ...@@ -23,552 +23,544 @@ Item {
ScrollBar { ScrollBar {
id: scrollbar id: scrollbar
interactive: !touchObserver.wasTouched
parent: chat.parent parent: chat.parent
anchors.top: parent.top anchors.top: parent.top
anchors.right: parent.right anchors.right: parent.right
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
} }
ListView {
id: chat
EventObserver {
id: touchObserver
anchors.fill: parent anchors.fill: parent
ListView { property int delegateMaxWidth: ((Settings.timelineMaxWidth > 100 && Settings.timelineMaxWidth < chatRoot.availableWidth) ? Settings.timelineMaxWidth : chatRoot.availableWidth) - chatRoot.padding * 2 - (scrollbar.interactive? scrollbar.width : 0)
id: chat
displayMarginBeginning: height / 2
displayMarginEnd: height / 2
model: room
// reuseItems still has a few bugs, see https://bugreports.qt.io/browse/QTBUG-95105 https://bugreports.qt.io/browse/QTBUG-95107
//onModelChanged: if (room) room.sendReset()
//reuseItems: true
boundsBehavior: Flickable.StopAtBounds
//pixelAligned: true
spacing: 2
verticalLayoutDirection: ListView.BottomToTop
onCountChanged: {
// Mark timeline as read
if (atYEnd && room) model.currentIndex = 0;
anchors.fill: parent }
property int delegateMaxWidth: ((Settings.timelineMaxWidth > 100 && Settings.timelineMaxWidth < chatRoot.availableWidth) ? Settings.timelineMaxWidth : chatRoot.availableWidth) - chatRoot.padding * 2 - scrollbar.width
displayMarginBeginning: height / 2
displayMarginEnd: height / 2
model: room
// reuseItems still has a few bugs, see https://bugreports.qt.io/browse/QTBUG-95105 https://bugreports.qt.io/browse/QTBUG-95107
//onModelChanged: if (room) room.sendReset()
//reuseItems: true
boundsBehavior: Flickable.StopAtBounds
//pixelAligned: true
spacing: 2
verticalLayoutDirection: ListView.BottomToTop
onCountChanged: {
// Mark timeline as read
if (atYEnd && room) model.currentIndex = 0;
}
ScrollBar.vertical: scrollbar
anchors.rightMargin: scrollbar.interactive ? scrollbar.width : 0
Rectangle { ScrollBar.vertical: scrollbar
//closePolicy: Popup.NoAutoClose
id: messageActions anchors.rightMargin: scrollbar.interactive? scrollbar.width : 0
property Item attached: null Rectangle {
property alias model: row.model //closePolicy: Popup.NoAutoClose
// use comma to update on scroll
property var attachedPos: chat.contentY, attached ? chat.mapFromItem(attached, attached ? attached.width - width : 0, -height) : null
readonly property int padding: Nheko.paddingSmall
visible: Settings.buttonsInTimeline && !!attached && (attached.hovered || messageActionHover.hovered) id: messageActions
x: attached ? attachedPos.x : 0
y: attached ? attachedPos.y : 0
z: 10
height: row.implicitHeight + padding * 2
width: row.implicitWidth + padding * 2
color: Nheko.colors.window
border.color: Nheko.colors.buttonText
border.width: 1
radius: padding
HoverHandler { property Item attached: null
id: messageActionHover property alias model: row.model
// use comma to update on scroll
property var attachedPos: chat.contentY, attached ? chat.mapFromItem(attached, attached ? attached.width - width : 0, -height) : null
readonly property int padding: Nheko.paddingSmall
grabPermissions: PointerHandler.CanTakeOverFromAnything visible: Settings.buttonsInTimeline && !!attached && (attached.hovered || messageActionHover.hovered)
} x: attached ? attachedPos.x : 0
y: attached ? attachedPos.y : 0
z: 10
height: row.implicitHeight + padding * 2
width: row.implicitWidth + padding * 2
color: Nheko.colors.window
border.color: Nheko.colors.buttonText
border.width: 1
radius: padding
Row { HoverHandler {
id: row id: messageActionHover
property var model grabPermissions: PointerHandler.CanTakeOverFromAnything
}
anchors.centerIn: parent Row {
spacing: messageActions.padding id: row
Repeater { property var model
model: Settings.recentReactions
delegate: TextButton { anchors.centerIn: parent
required property string modelData spacing: messageActions.padding
visible: chat.model ? chat.model.permissions.canSend(MtxEvent.Reaction) : false Repeater {
model: Settings.recentReactions
height: fontMetrics.height delegate: TextButton {
font.family: Settings.emojiFont required property string modelData
text: modelData visible: chat.model ? chat.model.permissions.canSend(MtxEvent.Reaction) : false
onClicked: {
room.input.reaction(row.model.eventId, modelData);
TimelineManager.focusMessageInput();
}
}
}
ImageButton { height: fontMetrics.height
id: editButton font.family: Settings.emojiFont
visible: !!row.model && row.model.isEditable text: modelData
buttonTextColor: Nheko.colors.buttonText
width: 16
hoverEnabled: true
image: ":/icons/icons/ui/edit.svg"
ToolTip.visible: hovered
ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("Edit")
onClicked: { onClicked: {
if (row.model.isEditable) room.input.reaction(row.model.eventId, modelData);
chat.model.editAction(row.model.eventId); TimelineManager.focusMessageInput();
} }
} }
}
ImageButton { ImageButton {
id: reactButton id: editButton
visible: !!row.model && row.model.isEditable
buttonTextColor: Nheko.colors.buttonText
width: 16
hoverEnabled: true
image: ":/icons/icons/ui/edit.svg"
ToolTip.visible: hovered
ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("Edit")
onClicked: {
if (row.model.isEditable)
chat.model.editAction(row.model.eventId);
visible: chat.model ? chat.model.permissions.canSend(MtxEvent.Reaction) : false
width: 16
hoverEnabled: true
image: ":/icons/icons/ui/smile.svg"
ToolTip.visible: hovered
ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("React")
onClicked: emojiPopup.visible ? emojiPopup.close() : emojiPopup.show(reactButton, function(emoji) {
var event_id = row.model ? row.model.eventId : "";
room.input.reaction(event_id, emoji);
TimelineManager.focusMessageInput();
})
} }
}
ImageButton { ImageButton {
id: replyButton id: reactButton
visible: chat.model ? chat.model.permissions.canSend(MtxEvent.TextMessage) : false visible: chat.model ? chat.model.permissions.canSend(MtxEvent.Reaction) : false
width: 16 width: 16
hoverEnabled: true hoverEnabled: true
image: ":/icons/icons/ui/reply.svg" image: ":/icons/icons/ui/smile.svg"
ToolTip.visible: hovered ToolTip.visible: hovered
ToolTip.delay: Nheko.tooltipDelay ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("Reply") ToolTip.text: qsTr("React")
onClicked: chat.model.replyAction(row.model.eventId) onClicked: emojiPopup.visible ? emojiPopup.close() : emojiPopup.show(reactButton, function(emoji) {
} var event_id = row.model ? row.model.eventId : "";
room.input.reaction(event_id, emoji);
TimelineManager.focusMessageInput();
})
}
ImageButton { ImageButton {
id: optionsButton id: replyButton
visible: chat.model ? chat.model.permissions.canSend(MtxEvent.TextMessage) : false
width: 16
hoverEnabled: true
image: ":/icons/icons/ui/reply.svg"
ToolTip.visible: hovered
ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("Reply")
onClicked: chat.model.replyAction(row.model.eventId)
}
width: 16 ImageButton {
hoverEnabled: true id: optionsButton
image: ":/icons/icons/ui/options.svg"
ToolTip.visible: hovered
ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("Options")
onClicked: messageContextMenu.show(row.model.eventId, row.model.type, row.model.isSender, row.model.isEncrypted, row.model.isEditable, "", row.model.body, optionsButton)
}
width: 16
hoverEnabled: true
image: ":/icons/icons/ui/options.svg"
ToolTip.visible: hovered
ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("Options")
onClicked: messageContextMenu.show(row.model.eventId, row.model.type, row.model.isSender, row.model.isEncrypted, row.model.isEditable, "", row.model.body, optionsButton)
} }
} }
ScrollHelper { }
flickable: parent
anchors.fill: parent
}
Shortcut { ScrollHelper {
sequence: StandardKey.MoveToPreviousPage flickable: parent
onActivated: { anchors.fill: parent
chat.contentY = chat.contentY - chat.height / 2; }
chat.returnToBounds();
} Shortcut {
sequence: StandardKey.MoveToPreviousPage
onActivated: {
chat.contentY = chat.contentY - chat.height / 2;
chat.returnToBounds();
} }
}
Shortcut { Shortcut {
sequence: StandardKey.MoveToNextPage sequence: StandardKey.MoveToNextPage
onActivated: { onActivated: {
chat.contentY = chat.contentY + chat.height / 2; chat.contentY = chat.contentY + chat.height / 2;
chat.returnToBounds(); chat.returnToBounds();
}
} }
}
Shortcut { Shortcut {
sequence: StandardKey.Cancel sequence: StandardKey.Cancel
onActivated: { onActivated: {
if (chat.model.reply) if (chat.model.reply)
chat.model.reply = undefined; chat.model.reply = undefined;
else else
chat.model.edit = undefined; chat.model.edit = undefined;
}
} }
}
Shortcut { Shortcut {
sequence: "Alt+Up" sequence: "Alt+Up"
onActivated: chat.model.reply = chat.model.indexToId(chat.model.reply ? chat.model.idToIndex(chat.model.reply) + 1 : 0) onActivated: chat.model.reply = chat.model.indexToId(chat.model.reply ? chat.model.idToIndex(chat.model.reply) + 1 : 0)
} }
Shortcut { Shortcut {
sequence: "Alt+Down" sequence: "Alt+Down"
onActivated: { onActivated: {
var idx = chat.model.reply ? chat.model.idToIndex(chat.model.reply) - 1 : -1; var idx = chat.model.reply ? chat.model.idToIndex(chat.model.reply) - 1 : -1;
chat.model.reply = idx >= 0 ? chat.model.indexToId(idx) : null; chat.model.reply = idx >= 0 ? chat.model.indexToId(idx) : null;
}
} }
}
Shortcut { Shortcut {
sequence: "Alt+F" sequence: "Alt+F"
onActivated: { onActivated: {
if (chat.model.reply) { if (chat.model.reply) {
var forwardMess = forwardCompleterComponent.createObject(timelineRoot); var forwardMess = forwardCompleterComponent.createObject(timelineRoot);
forwardMess.setMessageEventId(chat.model.reply); forwardMess.setMessageEventId(chat.model.reply);
forwardMess.open(); forwardMess.open();
chat.model.reply = null; chat.model.reply = null;
}
} }
} }
}
Shortcut { Shortcut {
sequence: "Ctrl+E" sequence: "Ctrl+E"
onActivated: { onActivated: {
chat.model.edit = chat.model.reply; chat.model.edit = chat.model.reply;
}
} }
}
Connections { Connections {
function onFocusChanged() { function onFocusChanged() {
readTimer.running = TimelineManager.isWindowFocused; readTimer.running = TimelineManager.isWindowFocused;
}
target: TimelineManager
} }
Timer { target: TimelineManager
id: readTimer }
Timer {
id: readTimer
// force current read index to update // force current read index to update
onTriggered: { onTriggered: {
if (chat.model) if (chat.model)
chat.model.setCurrentIndex(chat.model.currentIndex); chat.model.setCurrentIndex(chat.model.currentIndex);
}
interval: 1000
} }
interval: 1000
}
Component { Component {
id: sectionHeader id: sectionHeader
Column {
topPadding: userName_.visible? 4: 0
bottomPadding: Settings.bubbles? (isSender? 0 : 2) : 3
spacing: 8
visible: (previousMessageUserId !== userId || previousMessageDay !== day || isStateEvent !== previousMessageIsStateEvent)
width: parentWidth
height: ((previousMessageDay !== day) ? dateBubble.height : 0) + (isStateEvent? 0 : userName.height +8 )
Label {
id: dateBubble
anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined
visible: room && previousMessageDay !== day
text: room ? room.formatDateSeparator(timestamp) : ""
color: Nheko.colors.text
height: Math.round(fontMetrics.height * 1.4)
width: contentWidth * 1.2
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
background: Rectangle {
radius: parent.height / 2
color: Nheko.colors.window
}
Column { }
topPadding: userName_.visible? 4: 0
bottomPadding: Settings.bubbles? (isSender? 0 : 2) : 3 Row {
height: userName_.height
spacing: 8 spacing: 8
visible: (previousMessageUserId !== userId || previousMessageDay !== day || isStateEvent !== previousMessageIsStateEvent) visible: !isStateEvent && (!isSender || !Settings.bubbles)
width: parentWidth
height: ((previousMessageDay !== day) ? dateBubble.height : 0) + (isStateEvent? 0 : userName.height +8 ) Avatar {
id: messageUserAvatar
width: Nheko.avatarSize * (Settings.smallAvatars? 0.5 : 1)
height: Nheko.avatarSize * (Settings.smallAvatars? 0.5 : 1)
url: !room ? "" : room.avatarUrl(userId).replace("mxc://", "image://MxcImage/")
displayName: userName
userid: userId
onClicked: room.openUserProfile(userId)
ToolTip.visible: avatarHover.hovered
ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: userid
Label { HoverHandler {
id: dateBubble id: avatarHover
anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined
visible: room && previousMessageDay !== day
text: room ? room.formatDateSeparator(timestamp) : ""
color: Nheko.colors.text
height: Math.round(fontMetrics.height * 1.4)
width: contentWidth * 1.2
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
background: Rectangle {
radius: parent.height / 2
color: Nheko.colors.window
} }
} }
Row { Connections {
height: userName_.height function onRoomAvatarUrlChanged() {
spacing: 8 messageUserAvatar.url = chat.model.avatarUrl(userId).replace("mxc://", "image://MxcImage/");
visible: !isStateEvent && (!isSender || !Settings.bubbles) }
Avatar {
id: messageUserAvatar
width: Nheko.avatarSize * (Settings.smallAvatars? 0.5 : 1)
height: Nheko.avatarSize * (Settings.smallAvatars? 0.5 : 1)
url: !room ? "" : room.avatarUrl(userId).replace("mxc://", "image://MxcImage/")
displayName: userName
userid: userId
onClicked: room.openUserProfile(userId)
ToolTip.visible: avatarHover.hovered
ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: userid
HoverHandler {
id: avatarHover
}
function onScrollToIndex(index) {
chat.positionViewAtIndex(index, ListView.Center);
} }
Connections { target: chat.model
function onRoomAvatarUrlChanged() { }
messageUserAvatar.url = chat.model.avatarUrl(userId).replace("mxc://", "image://MxcImage/");
}
function onScrollToIndex(index) { Label {
chat.positionViewAtIndex(index, ListView.Center); id: userName_
}
target: chat.model text: TimelineManager.escapeEmoji(userName)
} color: TimelineManager.userColor(userId, Nheko.colors.base)
textFormat: Text.RichText
ToolTip.visible: displayNameHover.hovered
ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: userId
Label { TapHandler {
id: userName_ onSingleTapped: chat.model.openUserProfile(userId)
dragThreshold: 0
}
text: TimelineManager.escapeEmoji(userName) CursorShape {
color: TimelineManager.userColor(userId, Nheko.colors.base) anchors.fill: parent
textFormat: Text.RichText cursorShape: Qt.PointingHandCursor
ToolTip.visible: displayNameHover.hovered }
ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: userId
TapHandler { HoverHandler {
onSingleTapped: chat.model.openUserProfile(userId) id: displayNameHover
dragThreshold: 0 }
}
CursorShape { }
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
}
HoverHandler { Label {
id: displayNameHover id: statusMsg
} color: Nheko.colors.buttonText
text: Presence.userStatus(userId)
textFormat: Text.PlainText
elide: Text.ElideRight
width: chat.delegateMaxWidth - parent.spacing * 2 - userName.implicitWidth - Nheko.avatarSize
font.italic: true
} Connections {
target: Presence
Label { function onPresenceChanged(id) {
id: statusMsg if (id == userId) statusMsg.text = Presence.userStatus(userId);
color: Nheko.colors.buttonText
text: Presence.userStatus(userId)
textFormat: Text.PlainText
elide: Text.ElideRight
width: chat.delegateMaxWidth - parent.spacing * 2 - userName.implicitWidth - Nheko.avatarSize
font.italic: true
Connections {
target: Presence
function onPresenceChanged(id) {
if (id == userId) statusMsg.text = Presence.userStatus(userId);
}
} }
} }
} }
} }
} }
delegate: ItemDelegate { }
id: wrapper
required property double proportionalHeight
required property int type
required property string typeString
required property int originalWidth
required property string blurhash
required property string body
required property string formattedBody
required property string eventId
required property string filename
required property string filesize
required property string url
required property string thumbnailUrl
required property bool isOnlyEmoji
required property bool isSender
required property bool isEncrypted
required property bool isEditable
required property bool isEdited
required property bool isStateEvent
required property bool previousMessageIsStateEvent
required property string replyTo
required property string userId
required property string roomTopic
required property string roomName
required property string callType
required property var reactions
required property int trustlevel
required property int encryptionError
required property var timestamp
required property int status
required property int index
required property int relatedEventCacheBuster
required property string previousMessageUserId
required property string day
required property string previousMessageDay
required property string userName
property bool scrolledToThis: eventId === chat.model.scrollTarget && (y + height > chat.y + chat.contentY && y < chat.y + chat.height + chat.contentY)
anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined
width: chat.delegateMaxWidth
height: section.active ? section.height + timelinerow.height : timelinerow.height
hoverEnabled: true
background: Rectangle {
id: scrollHighlight
opacity: 0
visible: true
z: 1
enabled: false
color: Nheko.colors.highlight
states: State {
name: "revealed"
when: wrapper.scrolledToThis
}
transitions: Transition { delegate: ItemDelegate {
from: "" id: wrapper
to: "revealed"
required property double proportionalHeight
SequentialAnimation { required property int type
PropertyAnimation { required property string typeString
target: scrollHighlight required property int originalWidth
properties: "opacity" required property string blurhash
easing.type: Easing.InOutQuad required property string body
from: 0 required property string formattedBody
to: 1 required property string eventId
duration: 500 required property string filename
} required property string filesize
required property string url
required property string thumbnailUrl
required property bool isOnlyEmoji
required property bool isSender
required property bool isEncrypted
required property bool isEditable
required property bool isEdited
required property bool isStateEvent
required property bool previousMessageIsStateEvent
required property string replyTo
required property string userId
required property string roomTopic
required property string roomName
required property string callType
required property var reactions
required property int trustlevel
required property int encryptionError
required property var timestamp
required property int status
required property int index
required property int relatedEventCacheBuster
required property string previousMessageUserId
required property string day
required property string previousMessageDay
required property string userName
property bool scrolledToThis: eventId === chat.model.scrollTarget && (y + height > chat.y + chat.contentY && y < chat.y + chat.height + chat.contentY)
anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined
width: chat.delegateMaxWidth
height: section.active ? section.height + timelinerow.height : timelinerow.height
hoverEnabled: true
background: Rectangle {
id: scrollHighlight
opacity: 0
visible: true
z: 1
enabled: false
color: Nheko.colors.highlight
states: State {
name: "revealed"
when: wrapper.scrolledToThis
}
PropertyAnimation { transitions: Transition {
target: scrollHighlight from: ""
properties: "opacity" to: "revealed"
easing.type: Easing.InOutQuad
from: 1 SequentialAnimation {
to: 0 PropertyAnimation {
duration: 500 target: scrollHighlight
} properties: "opacity"
easing.type: Easing.InOutQuad
from: 0
to: 1
duration: 500
}
ScriptAction { PropertyAnimation {
script: chat.model.eventShown() target: scrollHighlight
} properties: "opacity"
easing.type: Easing.InOutQuad
from: 1
to: 0
duration: 500
}
ScriptAction {
script: chat.model.eventShown()
} }
} }
} }
Loader { }
id: section
property int parentWidth: parent.width
property string userId: wrapper.userId
property string previousMessageUserId: wrapper.previousMessageUserId
property string day: wrapper.day
property string previousMessageDay: wrapper.previousMessageDay
property bool previousMessageIsStateEvent: wrapper.previousMessageIsStateEvent
property bool isStateEvent: wrapper.isStateEvent
property bool isSender: wrapper.isSender
property string userName: wrapper.userName
property date timestamp: wrapper.timestamp
z: 4
active: previousMessageUserId !== undefined && previousMessageUserId !== userId || previousMessageDay !== day || previousMessageIsStateEvent !== isStateEvent
//asynchronous: true
sourceComponent: sectionHeader
visible: status == Loader.Ready
}
TimelineRow { Loader {
id: timelinerow id: section
hovered: (wrapper.hovered && !messageActionHover.hovered) || (messageActions.model != undefined && messageActions.model.eventId == timelinerow.eventId) property int parentWidth: parent.width
property string userId: wrapper.userId
proportionalHeight: wrapper.proportionalHeight property string previousMessageUserId: wrapper.previousMessageUserId
type: chat.model, wrapper.type property string day: wrapper.day
typeString: wrapper.typeString property string previousMessageDay: wrapper.previousMessageDay
originalWidth: wrapper.originalWidth property bool previousMessageIsStateEvent: wrapper.previousMessageIsStateEvent
blurhash: wrapper.blurhash property bool isStateEvent: wrapper.isStateEvent
body: wrapper.body property bool isSender: wrapper.isSender
formattedBody: wrapper.formattedBody property string userName: wrapper.userName
eventId: chat.model, wrapper.eventId property date timestamp: wrapper.timestamp
filename: wrapper.filename
filesize: wrapper.filesize z: 4
url: wrapper.url active: previousMessageUserId !== undefined && previousMessageUserId !== userId || previousMessageDay !== day || previousMessageIsStateEvent !== isStateEvent
thumbnailUrl: wrapper.thumbnailUrl //asynchronous: true
isOnlyEmoji: wrapper.isOnlyEmoji sourceComponent: sectionHeader
isSender: wrapper.isSender visible: status == Loader.Ready
isEncrypted: wrapper.isEncrypted }
isEditable: wrapper.isEditable
isEdited: wrapper.isEdited
isStateEvent: wrapper.isStateEvent
replyTo: wrapper.replyTo
userId: wrapper.userId
userName: wrapper.userName
roomTopic: wrapper.roomTopic
roomName: wrapper.roomName
callType: wrapper.callType
reactions: wrapper.reactions
trustlevel: wrapper.trustlevel
encryptionError: wrapper.encryptionError
timestamp: wrapper.timestamp
status: wrapper.status
relatedEventCacheBuster: wrapper.relatedEventCacheBuster
y: section.visible && section.active ? section.y + section.height : 0
}
onHoveredChanged: { TimelineRow {
if (!Settings.mobileMode && hovered) { id: timelinerow
if (!messageActionHover.hovered) {
messageActions.attached = timelinerow; hovered: (wrapper.hovered && !messageActionHover.hovered) || (messageActions.model != undefined && messageActions.model.eventId == timelinerow.eventId)
messageActions.model = timelinerow;
} proportionalHeight: wrapper.proportionalHeight
type: chat.model, wrapper.type
typeString: wrapper.typeString
originalWidth: wrapper.originalWidth
blurhash: wrapper.blurhash
body: wrapper.body
formattedBody: wrapper.formattedBody
eventId: chat.model, wrapper.eventId
filename: wrapper.filename
filesize: wrapper.filesize
url: wrapper.url
thumbnailUrl: wrapper.thumbnailUrl
isOnlyEmoji: wrapper.isOnlyEmoji
isSender: wrapper.isSender
isEncrypted: wrapper.isEncrypted
isEditable: wrapper.isEditable
isEdited: wrapper.isEdited
isStateEvent: wrapper.isStateEvent
replyTo: wrapper.replyTo
userId: wrapper.userId
userName: wrapper.userName
roomTopic: wrapper.roomTopic
roomName: wrapper.roomName
callType: wrapper.callType
reactions: wrapper.reactions
trustlevel: wrapper.trustlevel
encryptionError: wrapper.encryptionError
timestamp: wrapper.timestamp
status: wrapper.status
relatedEventCacheBuster: wrapper.relatedEventCacheBuster
y: section.visible && section.active ? section.y + section.height : 0
}
onHoveredChanged: {
if (!Settings.mobileMode && hovered) {
if (!messageActionHover.hovered) {
messageActions.attached = timelinerow;
messageActions.model = timelinerow;
} }
} }
}
Connections { Connections {
function onMovementEnded() { function onMovementEnded() {
if (y + height + 2 * chat.spacing > chat.contentY + chat.height && y < chat.contentY + chat.height) if (y + height + 2 * chat.spacing > chat.contentY + chat.height && y < chat.contentY + chat.height)
chat.model.currentIndex = index; chat.model.currentIndex = index;
}
target: chat
} }
target: chat
} }
footer: Item { }
anchors.horizontalCenter: parent.horizontalCenter
anchors.margins: Nheko.paddingLarge
visible: chat.model && chat.model.paginationInProgress
// hacky, but works
height: loadingSpinner.height + 2 * Nheko.paddingLarge
Spinner {
id: loadingSpinner
anchors.centerIn: parent
anchors.margins: Nheko.paddingLarge
running: chat.model && chat.model.paginationInProgress
foreground: Nheko.colors.mid
z: 3
}
footer: Item {
anchors.horizontalCenter: parent.horizontalCenter
anchors.margins: Nheko.paddingLarge
visible: chat.model && chat.model.paginationInProgress
// hacky, but works
height: loadingSpinner.height + 2 * Nheko.paddingLarge
Spinner {
id: loadingSpinner
anchors.centerIn: parent
anchors.margins: Nheko.paddingLarge
running: chat.model && chat.model.paginationInProgress
foreground: Nheko.colors.mid
z: 3
} }
} }
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment