matrixion/resources/qml/TimelineView.qml

414 lines
10 KiB
QML
Raw Normal View History

import QtQuick 2.9
import QtQuick.Controls 2.3
import QtQuick.Layouts 1.2
2019-09-07 02:33:46 +03:00
import QtGraphicalEffects 1.0
2019-09-18 21:34:30 +03:00
import QtQuick.Window 2.2
2019-08-30 20:29:25 +03:00
import im.nheko 1.0
2020-05-14 03:19:15 +03:00
import im.nheko.EmojiModel 1.0
import "./delegates"
2020-05-14 03:19:15 +03:00
import "./emoji"
import "./device-verification"
Page {
id: timelineRoot
property var colors: currentActivePalette
property var systemInactive: SystemPalette { colorGroup: SystemPalette.Disabled }
property var inactiveColors: currentInactivePalette ? currentInactivePalette : systemInactive
property int avatarSize: 40
property real highlightHue: colors.highlight.hslHue
property real highlightSat: colors.highlight.hslSaturation
property real highlightLight: colors.highlight.hslLightness
2020-06-28 18:31:34 +03:00
property variant userProfile
2019-09-19 22:53:24 +03:00
palette: colors
2020-04-30 23:02:41 +03:00
FontMetrics {
id: fontMetrics
}
2020-07-25 23:57:54 +03:00
EmojiPicker {
id: emojiPopup
width: 7 * 52 + 20
height: 6 * 52
colors: palette
model: EmojiProxyModel {
category: EmojiCategory.People
sourceModel: EmojiModel {}
}
}
2020-05-14 03:19:15 +03:00
Menu {
id: messageContextMenu
2020-02-02 02:33:55 +03:00
modal: true
2020-07-26 03:06:38 +03:00
function show(eventId_, eventType_, isEncrypted_, showAt_, position) {
eventId = eventId_
eventType = eventType_
isEncrypted = isEncrypted_
2020-07-26 03:06:38 +03:00
if (position)
popup(position, showAt_)
else
2020-07-20 01:42:48 +03:00
popup(showAt_)
}
property string eventId
property int eventType
property bool isEncrypted
2020-07-20 01:42:48 +03:00
2020-05-08 03:53:24 +03:00
MenuItem {
text: qsTr("React")
2020-07-20 01:42:48 +03:00
onClicked: emojiPopup.show(messageContextMenu.parent, messageContextMenu.eventId)
2020-05-08 03:53:24 +03:00
}
MenuItem {
text: qsTr("Reply")
onClicked: chat.model.replyAction(messageContextMenu.eventId)
}
MenuItem {
text: qsTr("Read receipts")
onTriggered: chat.model.readReceiptsAction(messageContextMenu.eventId)
}
MenuItem {
text: qsTr("Mark as read")
}
MenuItem {
text: qsTr("View raw message")
onTriggered: chat.model.viewRawMessage(messageContextMenu.eventId)
}
MenuItem {
visible: messageContextMenu.isEncrypted
height: visible ? implicitHeight : 0
text: qsTr("View decrypted raw message")
onTriggered: chat.model.viewDecryptedRawMessage(messageContextMenu.eventId)
}
MenuItem {
text: qsTr("Redact message")
onTriggered: chat.model.redactEvent(messageContextMenu.eventId)
}
MenuItem {
visible: messageContextMenu.eventType == MtxEvent.ImageMessage || messageContextMenu.eventType == MtxEvent.VideoMessage || messageContextMenu.eventType == MtxEvent.AudioMessage || messageContextMenu.eventType == MtxEvent.FileMessage || messageContextMenu.eventType == MtxEvent.Sticker
height: visible ? implicitHeight : 0
text: qsTr("Save as")
2020-06-24 17:24:22 +03:00
onTriggered: TimelineManager.timeline.saveMedia(messageContextMenu.eventId)
}
}
Rectangle {
anchors.fill: parent
color: colors.window
Component {
id: deviceVerificationDialog
DeviceVerification {}
}
Connections {
2020-06-24 17:24:22 +03:00
target: TimelineManager
2020-07-05 19:03:27 +03:00
function onNewDeviceVerificationRequest(flow,transactionId,userId,deviceId) {
flow.userId = userId;
flow.sender = false;
flow.deviceId = deviceId;
flow.tranId = transactionId;
deviceVerificationList.add(flow.tranId);
2020-07-04 05:24:28 +03:00
var dialog = deviceVerificationDialog.createObject(timelineRoot, {flow: flow});
dialog.show();
}
}
2020-07-04 05:24:28 +03:00
Connections {
target: TimelineManager.timeline
function onOpenProfile(profile) {
var userProfile = userProfileComponent.createObject(timelineRoot,{profile: profile});
userProfile.show();
}
}
Label {
2020-06-24 17:24:22 +03:00
visible: !TimelineManager.timeline && !TimelineManager.isInitialSync
anchors.centerIn: parent
text: qsTr("No room open")
font.pointSize: 24
2020-04-29 22:41:46 +03:00
color: colors.text
}
2019-08-31 00:20:53 +03:00
BusyIndicator {
visible: running
anchors.centerIn: parent
2020-06-24 17:24:22 +03:00
running: TimelineManager.isInitialSync
height: 200
width: 200
z: 3
}
ListView {
id: chat
2020-06-24 17:24:22 +03:00
visible: TimelineManager.timeline != null
2020-01-17 03:25:14 +03:00
cacheBuffer: 400
anchors.horizontalCenter: parent.horizontalCenter
2020-01-17 03:25:14 +03:00
anchors.top: parent.top
anchors.bottom: chatFooter.top
width: parent.width
2019-08-31 00:20:53 +03:00
anchors.leftMargin: 4
anchors.rightMargin: scrollbar.width
2020-06-24 17:24:22 +03:00
model: TimelineManager.timeline
2019-09-18 21:34:30 +03:00
boundsBehavior: Flickable.StopAtBounds
2020-04-30 23:02:41 +03:00
ScrollHelper {
flickable: parent
anchors.fill: parent
}
2019-09-18 21:34:30 +03:00
pixelAligned: true
2020-01-31 01:57:39 +03:00
Shortcut {
sequence: StandardKey.MoveToPreviousPage
2020-01-31 02:02:15 +03:00
onActivated: { chat.contentY = chat.contentY - chat.height / 2; chat.returnToBounds(); }
2020-01-31 01:57:39 +03:00
}
Shortcut {
sequence: StandardKey.MoveToNextPage
2020-01-31 02:02:15 +03:00
onActivated: { chat.contentY = chat.contentY + chat.height / 2; chat.returnToBounds(); }
2020-01-31 01:57:39 +03:00
}
Shortcut {
sequence: StandardKey.Cancel
onActivated: chat.model.reply = undefined
}
Shortcut {
sequence: "Alt+Up"
onActivated: chat.model.reply = chat.model.indexToId(chat.model.reply? chat.model.idToIndex(chat.model.reply) + 1 : 0)
}
Shortcut {
sequence: "Alt+Down"
onActivated: {
var idx = chat.model.reply? chat.model.idToIndex(chat.model.reply) - 1 : -1
chat.model.reply = idx >= 0 ? chat.model.indexToId(idx) : undefined
}
}
2020-01-31 01:57:39 +03:00
ScrollBar.vertical: ScrollBar {
id: scrollbar
2019-12-07 17:00:06 +03:00
parent: chat.parent
anchors.top: chat.top
anchors.right: chat.right
2019-12-07 17:00:06 +03:00
anchors.bottom: chat.bottom
2019-09-18 21:34:30 +03:00
}
spacing: 4
2020-01-04 01:21:33 +03:00
verticalLayoutDirection: ListView.BottomToTop
onCountChanged: if (atYEnd) model.currentIndex = 0 // Mark last event as read, since we are at the bottom
2020-06-24 17:24:22 +03:00
property int delegateMaxWidth: (Settings.timelineMaxWidth > 100 && (parent.width - Settings.timelineMaxWidth) > 32) ? Settings.timelineMaxWidth : (parent.width - 32)
2020-01-04 01:21:33 +03:00
delegate: Rectangle {
// This would normally be previousSection, but our model's order is inverted.
property bool sectionBoundary: (ListView.nextSection != "" && ListView.nextSection !== ListView.section) || model.index === chat.count - 1
id: wrapper
property Item section
2020-07-08 03:02:48 +03:00
anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined
width: chat.delegateMaxWidth
2020-01-04 01:21:33 +03:00
height: section ? section.height + timelinerow.height : timelinerow.height
color: "transparent"
2020-01-04 01:21:33 +03:00
TimelineRow {
id: timelinerow
y: section ? section.y + section.height : 0
}
onSectionBoundaryChanged: {
if (sectionBoundary) {
var properties = {
'modelData': model.dump,
'section': ListView.section,
'nextSection': ListView.nextSection
}
section = sectionHeader.createObject(wrapper, properties)
} else {
section.destroy()
section = null
}
}
2020-07-25 23:57:54 +03:00
Connections {
target: chat
function onMovementEnded() {
if (y + height + 2 * chat.spacing > chat.contentY + chat.height && y < chat.contentY + chat.height)
chat.model.currentIndex = index;
}
}
}
2020-06-28 18:31:34 +03:00
Component{
id: userProfileComponent
UserProfile{}
}
section {
property: "section"
2020-01-04 01:21:33 +03:00
}
Component {
id: sectionHeader
Column {
property var modelData
property string section
property string nextSection
topPadding: 4
bottomPadding: 4
spacing: 8
2020-01-04 01:21:33 +03:00
visible: !!modelData
width: parent.width
height: (section.includes(" ") ? dateBubble.height + 8 + userName.height : userName.height) + 8
Label {
id: dateBubble
2020-01-04 01:21:33 +03:00
anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined
visible: section.includes(" ")
2020-01-04 01:21:33 +03:00
text: chat.model.formatDateSeparator(modelData.timestamp)
2020-05-06 00:43:43 +03:00
color: colors.text
2020-04-29 12:14:43 +03:00
height: fontMetrics.height * 1.4
width: contentWidth * 1.2
2020-04-28 10:38:13 +03:00
horizontalAlignment: Text.AlignHCenter
background: Rectangle {
radius: parent.height / 2
2020-05-06 00:43:43 +03:00
color: colors.base
}
}
2020-05-27 11:49:26 +03:00
Row {
height: userName.height
spacing: 8
Avatar {
width: avatarSize
height: avatarSize
2020-01-04 01:21:33 +03:00
url: chat.model.avatarUrl(modelData.userId).replace("mxc://", "image://MxcImage/")
displayName: modelData.userName
userid: modelData.userId
MouseArea {
anchors.fill: parent
2020-07-04 05:24:28 +03:00
onClicked: chat.model.openUserProfile(modelData.userId)
cursorShape: Qt.PointingHandCursor
propagateComposedEvents: true
}
}
Label {
id: userName
2020-01-04 01:21:33 +03:00
text: chat.model.escapeEmoji(modelData.userName)
2020-06-24 17:24:22 +03:00
color: TimelineManager.userColor(modelData.userId, colors.window)
textFormat: Text.RichText
MouseArea {
anchors.fill: parent
2020-05-27 11:49:26 +03:00
Layout.alignment: Qt.AlignHCenter
2020-07-04 05:24:28 +03:00
onClicked: chat.model.openUserProfile(modelData.userId)
cursorShape: Qt.PointingHandCursor
propagateComposedEvents: true
}
}
}
}
}
2020-01-17 03:25:14 +03:00
2020-07-25 23:57:54 +03:00
footer: BusyIndicator {
anchors.horizontalCenter: parent.horizontalCenter
running: chat.model && chat.model.paginationInProgress
height: 50
width: 50
z: 3
}
2020-01-17 03:25:14 +03:00
}
Rectangle {
id: chatFooter
2020-04-30 23:02:41 +03:00
height: Math.max(fontMetrics.height * 1.2, footerContent.height)
2020-01-17 03:25:14 +03:00
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
z: 3
2020-02-03 21:21:03 +03:00
color: "transparent"
2020-01-17 03:25:14 +03:00
2020-01-28 21:08:16 +03:00
Column {
id: footerContent
2020-01-17 03:25:14 +03:00
anchors.left: parent.left
anchors.right: parent.right
2020-01-28 07:28:11 +03:00
Label {
2020-01-28 21:08:16 +03:00
id: typingDisplay
anchors.left: parent.left
anchors.right: parent.right
anchors.leftMargin: 10
anchors.rightMargin: 10
2020-01-28 07:28:11 +03:00
2020-04-29 22:41:46 +03:00
color: colors.text
2020-01-28 21:08:16 +03:00
text: chat.model ? chat.model.formatTypingUsers(chat.model.typingUsers, colors.window) : ""
textFormat: Text.RichText
}
2020-01-28 07:28:11 +03:00
2020-01-28 21:08:16 +03:00
Rectangle {
2020-01-28 07:28:11 +03:00
anchors.left: parent.left
anchors.right: parent.right
2020-01-28 21:08:16 +03:00
id: replyPopup
2020-01-28 07:28:11 +03:00
2020-04-13 17:22:30 +03:00
visible: chat.model && chat.model.reply
2020-01-28 21:08:16 +03:00
// Height of child, plus margins, plus border
height: replyPreview.height + 10
2020-02-03 21:21:03 +03:00
color: colors.base
2020-01-28 07:28:11 +03:00
2020-01-28 21:08:16 +03:00
Reply {
id: replyPreview
anchors.left: parent.left
anchors.leftMargin: 10
anchors.right: closeReplyButton.left
anchors.rightMargin: 20
2020-01-28 21:08:16 +03:00
anchors.bottom: parent.bottom
2020-06-24 17:24:22 +03:00
<<<<<<< HEAD
2020-07-10 00:15:22 +03:00
modelData: chat.model ? chat.model.getDump(chat.model.reply, chat.model.id) : {}
userColor: timelineManager.userColor(modelData.userId, colors.window)
2020-06-24 17:24:22 +03:00
=======
modelData: chat.model ? chat.model.getDump(chat.model.reply) : {}
userColor: TimelineManager.userColor(modelData.userId, colors.window)
>>>>>>> Fix presence indicator
2020-01-28 07:28:11 +03:00
}
2020-01-28 21:08:16 +03:00
2020-01-28 07:28:11 +03:00
ImageButton {
id: closeReplyButton
2020-01-28 21:08:16 +03:00
anchors.right: parent.right
anchors.rightMargin: 15
2020-01-28 21:08:16 +03:00
anchors.top: replyPreview.top
hoverEnabled: true
width: 16
height: 16
2020-01-28 21:08:16 +03:00
2020-01-28 07:28:11 +03:00
image: ":/icons/icons/ui/remove-symbol.png"
ToolTip.visible: closeReplyButton.hovered
ToolTip.text: qsTr("Close")
2020-01-28 07:28:11 +03:00
2020-04-13 17:22:30 +03:00
onClicked: chat.model.reply = undefined
2020-01-28 07:28:11 +03:00
}
}
}
2019-08-31 00:20:53 +03:00
}
2019-08-31 23:43:31 +03:00
}
2019-08-30 20:29:25 +03:00
}