Reorganize TimelineView to prepare porting the room list

This commit is contained in:
Nicolas Werner 2021-05-14 15:23:32 +02:00
parent 5658be5215
commit 39a43ad4ab
No known key found for this signature in database
GPG Key ID: C8D75E610773F2D9
10 changed files with 410 additions and 340 deletions

View File

@ -0,0 +1,48 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick 2.9
import QtQuick.Controls 2.13
import QtQuick.Layouts 1.3
import im.nheko 1.0
Rectangle {
id: chatPage
color: Nheko.colors.window
SplitView {
anchors.fill: parent
Rectangle {
SplitView.minimumWidth: Nheko.avatarSize + Nheko.paddingSmall * 2
SplitView.preferredWidth: Nheko.avatarSize + Nheko.paddingSmall * 2
SplitView.maximumWidth: Nheko.avatarSize + Nheko.paddingSmall * 2
color: "blue"
}
Rectangle {
SplitView.minimumWidth: Nheko.avatarSize * 3 + Nheko.paddingSmall * 2
SplitView.preferredWidth: Nheko.avatarSize * 3 + Nheko.paddingSmall * 2
SplitView.maximumWidth: Nheko.avatarSize * 7 + Nheko.paddingSmall * 2
color: "red"
}
TimelineView {
id: timeline
SplitView.fillWidth: true
SplitView.minimumWidth: 400
}
}
PrivacyScreen {
anchors.fill: parent
visible: Settings.privacyScreen
screenTimeout: Settings.privacyScreenTimeout
timelineRoot: timeline
}
}

View File

@ -21,7 +21,7 @@ Popup {
modal: true
palette: Nheko.colors
parent: Overlay.overlay
width: implicitWidth >= (timelineRoot.width * 0.8) ? implicitWidth : (timelineRoot.width * 0.8)
width: implicitWidth >= (timelineView.width * 0.8) ? implicitWidth : (timelineView.width * 0.8)
height: implicitHeight + completerPopup.height + padding * 2
leftPadding: 10
rightPadding: 10

260
resources/qml/Root.qml Normal file
View File

@ -0,0 +1,260 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
import "./delegates"
import "./device-verification"
import "./emoji"
import "./voip"
import Qt.labs.platform 1.1 as Platform
import QtGraphicalEffects 1.0
import QtQuick 2.9
import QtQuick.Controls 2.13
import QtQuick.Layouts 1.3
import QtQuick.Window 2.2
import im.nheko 1.0
import im.nheko.EmojiModel 1.0
Page {
id: timelineRoot
palette: Nheko.colors
FontMetrics {
id: fontMetrics
}
EmojiPicker {
id: emojiPopup
colors: palette
model: TimelineManager.completerFor("allemoji", "")
}
Component {
id: userProfileComponent
UserProfile {
}
}
Component {
id: roomSettingsComponent
RoomSettings {
}
}
Component {
id: mobileCallInviteDialog
CallInvite {
}
}
Component {
id: quickSwitcherComponent
QuickSwitcher {
}
}
Component {
id: forwardCompleterComponent
ForwardCompleter {
}
}
Shortcut {
sequence: "Ctrl+K"
onActivated: {
var quickSwitch = quickSwitcherComponent.createObject(timelineRoot);
TimelineManager.focusTimeline();
quickSwitch.open();
}
}
Platform.Menu {
id: messageContextMenu
property string eventId
property string link
property string text
property int eventType
property bool isEncrypted
property bool isEditable
property bool isSender
function show(eventId_, eventType_, isSender_, isEncrypted_, isEditable_, link_, text_, showAt_) {
eventId = eventId_;
eventType = eventType_;
isEncrypted = isEncrypted_;
isEditable = isEditable_;
isSender = isSender_;
if (text_)
text = text_;
else
text = "";
if (link_)
link = link_;
else
link = "";
if (showAt_)
open(showAt_);
else
open();
}
Platform.MenuItem {
visible: messageContextMenu.text
enabled: visible
text: qsTr("Copy")
onTriggered: Clipboard.text = messageContextMenu.text
}
Platform.MenuItem {
visible: messageContextMenu.link
enabled: visible
text: qsTr("Copy link location")
onTriggered: Clipboard.text = messageContextMenu.link
}
Platform.MenuItem {
id: reactionOption
visible: TimelineManager.timeline ? TimelineManager.timeline.permissions.canSend(MtxEvent.Reaction) : false
text: qsTr("React")
onTriggered: emojiPopup.show(null, function(emoji) {
TimelineManager.queueReactionMessage(messageContextMenu.eventId, emoji);
})
}
Platform.MenuItem {
visible: TimelineManager.timeline ? TimelineManager.timeline.permissions.canSend(MtxEvent.TextMessage) : false
text: qsTr("Reply")
onTriggered: TimelineManager.timeline.replyAction(messageContextMenu.eventId)
}
Platform.MenuItem {
visible: messageContextMenu.isEditable && (TimelineManager.timeline ? TimelineManager.timeline.permissions.canSend(MtxEvent.TextMessage) : false)
enabled: visible
text: qsTr("Edit")
onTriggered: TimelineManager.timeline.editAction(messageContextMenu.eventId)
}
Platform.MenuItem {
text: qsTr("Read receipts")
onTriggered: TimelineManager.timeline.readReceiptsAction(messageContextMenu.eventId)
}
Platform.MenuItem {
visible: messageContextMenu.eventType == MtxEvent.ImageMessage || messageContextMenu.eventType == MtxEvent.VideoMessage || messageContextMenu.eventType == MtxEvent.AudioMessage || messageContextMenu.eventType == MtxEvent.FileMessage || messageContextMenu.eventType == MtxEvent.Sticker || messageContextMenu.eventType == MtxEvent.TextMessage || messageContextMenu.eventType == MtxEvent.LocationMessage || messageContextMenu.eventType == MtxEvent.EmoteMessage || messageContextMenu.eventType == MtxEvent.NoticeMessage
text: qsTr("Forward")
onTriggered: {
var forwardMess = forwardCompleterComponent.createObject(timelineRoot);
forwardMess.setMessageEventId(messageContextMenu.eventId);
forwardMess.open();
}
}
Platform.MenuItem {
text: qsTr("Mark as read")
}
Platform.MenuItem {
text: qsTr("View raw message")
onTriggered: TimelineManager.timeline.viewRawMessage(messageContextMenu.eventId)
}
Platform.MenuItem {
// TODO(Nico): Fix this still being iterated over, when using keyboard to select options
visible: messageContextMenu.isEncrypted
enabled: visible
text: qsTr("View decrypted raw message")
onTriggered: TimelineManager.timeline.viewDecryptedRawMessage(messageContextMenu.eventId)
}
Platform.MenuItem {
visible: (TimelineManager.timeline ? TimelineManager.timeline.permissions.canRedact() : false) || messageContextMenu.isSender
text: qsTr("Remove message")
onTriggered: TimelineManager.timeline.redactEvent(messageContextMenu.eventId)
}
Platform.MenuItem {
visible: messageContextMenu.eventType == MtxEvent.ImageMessage || messageContextMenu.eventType == MtxEvent.VideoMessage || messageContextMenu.eventType == MtxEvent.AudioMessage || messageContextMenu.eventType == MtxEvent.FileMessage || messageContextMenu.eventType == MtxEvent.Sticker
enabled: visible
text: qsTr("Save as")
onTriggered: TimelineManager.timeline.saveMedia(messageContextMenu.eventId)
}
Platform.MenuItem {
visible: messageContextMenu.eventType == MtxEvent.ImageMessage || messageContextMenu.eventType == MtxEvent.VideoMessage || messageContextMenu.eventType == MtxEvent.AudioMessage || messageContextMenu.eventType == MtxEvent.FileMessage || messageContextMenu.eventType == MtxEvent.Sticker
enabled: visible
text: qsTr("Open in external program")
onTriggered: TimelineManager.timeline.openMedia(messageContextMenu.eventId)
}
Platform.MenuItem {
visible: messageContextMenu.eventId
enabled: visible
text: qsTr("Copy link to event")
onTriggered: TimelineManager.timeline.copyLinkToEvent(messageContextMenu.eventId)
}
}
Component {
id: deviceVerificationDialog
DeviceVerification {
}
}
Connections {
target: TimelineManager
onNewDeviceVerificationRequest: {
var dialog = deviceVerificationDialog.createObject(timelineRoot, {
"flow": flow
});
dialog.show();
}
onOpenProfile: {
var userProfile = userProfileComponent.createObject(timelineRoot, {
"profile": profile
});
userProfile.show();
}
}
Connections {
target: TimelineManager.timeline
onOpenRoomSettingsDialog: {
var roomSettings = roomSettingsComponent.createObject(timelineRoot, {
"roomSettings": settings
});
roomSettings.show();
}
}
Connections {
target: CallManager
onNewInviteState: {
if (CallManager.haveCallInvite && Settings.mobileMode) {
var dialog = mobileCallInviteDialog.createObject(msgView);
dialog.open();
}
}
}
ChatPage {
anchors.fill: parent
}
}

View File

@ -9,370 +9,123 @@ import "./voip"
import Qt.labs.platform 1.1 as Platform
import QtGraphicalEffects 1.0
import QtQuick 2.9
import QtQuick.Controls 2.3
import QtQuick.Controls 2.13
import QtQuick.Layouts 1.3
import QtQuick.Window 2.2
import im.nheko 1.0
import im.nheko.EmojiModel 1.0
Page {
id: timelineRoot
Item {
id: timelineView
palette: Nheko.colors
FontMetrics {
id: fontMetrics
Label {
visible: !TimelineManager.timeline && !TimelineManager.isInitialSync
anchors.centerIn: parent
text: qsTr("No room open")
font.pointSize: 24
color: Nheko.colors.text
}
EmojiPicker {
id: emojiPopup
colors: palette
model: TimelineManager.completerFor("allemoji", "")
BusyIndicator {
visible: running
anchors.centerIn: parent
running: TimelineManager.isInitialSync
height: 200
width: 200
z: 3
}
Component {
id: userProfileComponent
ColumnLayout {
id: timelineLayout
UserProfile {
}
}
Component {
id: roomSettingsComponent
RoomSettings {
}
}
Component {
id: mobileCallInviteDialog
CallInvite {
}
}
Component {
id: quickSwitcherComponent
QuickSwitcher {
}
}
Component {
id: forwardCompleterComponent
ForwardCompleter {
}
}
Shortcut {
sequence: "Ctrl+K"
onActivated: {
var quickSwitch = quickSwitcherComponent.createObject(timelineRoot);
TimelineManager.focusTimeline();
quickSwitch.open();
}
}
Platform.Menu {
id: messageContextMenu
property string eventId
property string link
property string text
property int eventType
property bool isEncrypted
property bool isEditable
property bool isSender
function show(eventId_, eventType_, isSender_, isEncrypted_, isEditable_, link_, text_, showAt_) {
eventId = eventId_;
eventType = eventType_;
isEncrypted = isEncrypted_;
isEditable = isEditable_;
isSender = isSender_;
if (text_)
text = text_;
else
text = "";
if (link_)
link = link_;
else
link = "";
if (showAt_)
open(showAt_);
else
open();
}
Platform.MenuItem {
visible: messageContextMenu.text
enabled: visible
text: qsTr("Copy")
onTriggered: Clipboard.text = messageContextMenu.text
}
Platform.MenuItem {
visible: messageContextMenu.link
enabled: visible
text: qsTr("Copy link location")
onTriggered: Clipboard.text = messageContextMenu.link
}
Platform.MenuItem {
id: reactionOption
visible: TimelineManager.timeline ? TimelineManager.timeline.permissions.canSend(MtxEvent.Reaction) : false
text: qsTr("React")
onTriggered: emojiPopup.show(null, function(emoji) {
TimelineManager.queueReactionMessage(messageContextMenu.eventId, emoji);
})
}
Platform.MenuItem {
visible: TimelineManager.timeline ? TimelineManager.timeline.permissions.canSend(MtxEvent.TextMessage) : false
text: qsTr("Reply")
onTriggered: TimelineManager.timeline.replyAction(messageContextMenu.eventId)
}
Platform.MenuItem {
visible: messageContextMenu.isEditable && (TimelineManager.timeline ? TimelineManager.timeline.permissions.canSend(MtxEvent.TextMessage) : false)
enabled: visible
text: qsTr("Edit")
onTriggered: TimelineManager.timeline.editAction(messageContextMenu.eventId)
}
Platform.MenuItem {
text: qsTr("Read receipts")
onTriggered: TimelineManager.timeline.readReceiptsAction(messageContextMenu.eventId)
}
Platform.MenuItem {
visible: messageContextMenu.eventType == MtxEvent.ImageMessage || messageContextMenu.eventType == MtxEvent.VideoMessage || messageContextMenu.eventType == MtxEvent.AudioMessage || messageContextMenu.eventType == MtxEvent.FileMessage || messageContextMenu.eventType == MtxEvent.Sticker || messageContextMenu.eventType == MtxEvent.TextMessage || messageContextMenu.eventType == MtxEvent.LocationMessage || messageContextMenu.eventType == MtxEvent.EmoteMessage || messageContextMenu.eventType == MtxEvent.NoticeMessage
text: qsTr("Forward")
onTriggered: {
var forwardMess = forwardCompleterComponent.createObject(timelineRoot);
forwardMess.setMessageEventId(messageContextMenu.eventId);
forwardMess.open();
}
}
Platform.MenuItem {
text: qsTr("Mark as read")
}
Platform.MenuItem {
text: qsTr("View raw message")
onTriggered: TimelineManager.timeline.viewRawMessage(messageContextMenu.eventId)
}
Platform.MenuItem {
// TODO(Nico): Fix this still being iterated over, when using keyboard to select options
visible: messageContextMenu.isEncrypted
enabled: visible
text: qsTr("View decrypted raw message")
onTriggered: TimelineManager.timeline.viewDecryptedRawMessage(messageContextMenu.eventId)
}
Platform.MenuItem {
visible: (TimelineManager.timeline ? TimelineManager.timeline.permissions.canRedact() : false) || messageContextMenu.isSender
text: qsTr("Remove message")
onTriggered: TimelineManager.timeline.redactEvent(messageContextMenu.eventId)
}
Platform.MenuItem {
visible: messageContextMenu.eventType == MtxEvent.ImageMessage || messageContextMenu.eventType == MtxEvent.VideoMessage || messageContextMenu.eventType == MtxEvent.AudioMessage || messageContextMenu.eventType == MtxEvent.FileMessage || messageContextMenu.eventType == MtxEvent.Sticker
enabled: visible
text: qsTr("Save as")
onTriggered: TimelineManager.timeline.saveMedia(messageContextMenu.eventId)
}
Platform.MenuItem {
visible: messageContextMenu.eventType == MtxEvent.ImageMessage || messageContextMenu.eventType == MtxEvent.VideoMessage || messageContextMenu.eventType == MtxEvent.AudioMessage || messageContextMenu.eventType == MtxEvent.FileMessage || messageContextMenu.eventType == MtxEvent.Sticker
enabled: visible
text: qsTr("Open in external program")
onTriggered: TimelineManager.timeline.openMedia(messageContextMenu.eventId)
}
Platform.MenuItem {
visible: messageContextMenu.eventId
enabled: visible
text: qsTr("Copy link to event")
onTriggered: TimelineManager.timeline.copyLinkToEvent(messageContextMenu.eventId)
}
}
Rectangle {
visible: TimelineManager.timeline != null
anchors.fill: parent
color: Nheko.colors.window
Component {
id: deviceVerificationDialog
DeviceVerification {
}
spacing: 0
TopBar {
}
Connections {
target: TimelineManager
onNewDeviceVerificationRequest: {
var dialog = deviceVerificationDialog.createObject(timelineRoot, {
"flow": flow
});
dialog.show();
}
onOpenProfile: {
var userProfile = userProfileComponent.createObject(timelineRoot, {
"profile": profile
});
userProfile.show();
}
Rectangle {
Layout.fillWidth: true
height: 1
z: 3
color: Nheko.colors.mid
}
Connections {
target: TimelineManager.timeline
onOpenRoomSettingsDialog: {
var roomSettings = roomSettingsComponent.createObject(timelineRoot, {
"roomSettings": settings
});
roomSettings.show();
}
}
Rectangle {
id: msgView
Layout.fillWidth: true
Layout.fillHeight: true
color: Nheko.colors.base
ColumnLayout {
anchors.fill: parent
spacing: 0
StackLayout {
id: stackLayout
currentIndex: 0
Connections {
function onActiveTimelineChanged() {
stackLayout.currentIndex = 0;
}
target: TimelineManager
}
MessageView {
Layout.fillWidth: true
implicitHeight: msgView.height - typingIndicator.height
}
Loader {
source: CallManager.isOnCall && CallManager.callType != CallType.VOICE ? "voip/VideoCall.qml" : ""
onLoaded: TimelineManager.setVideoCallItem()
}
Connections {
target: CallManager
onNewInviteState: {
if (CallManager.haveCallInvite && Settings.mobileMode) {
var dialog = mobileCallInviteDialog.createObject(msgView);
dialog.open();
}
TypingIndicator {
id: typingIndicator
}
}
}
Label {
visible: !TimelineManager.timeline && !TimelineManager.isInitialSync
anchors.centerIn: parent
text: qsTr("No room open")
font.pointSize: 24
color: Nheko.colors.text
}
CallInviteBar {
id: callInviteBar
BusyIndicator {
visible: running
anchors.centerIn: parent
running: TimelineManager.isInitialSync
height: 200
width: 200
Layout.fillWidth: true
z: 3
}
ColumnLayout {
id: timelineLayout
visible: TimelineManager.timeline != null
anchors.fill: parent
spacing: 0
TopBar {
}
Rectangle {
Layout.fillWidth: true
height: 1
z: 3
color: Nheko.colors.mid
}
Rectangle {
id: msgView
Layout.fillWidth: true
Layout.fillHeight: true
color: Nheko.colors.base
ColumnLayout {
anchors.fill: parent
spacing: 0
StackLayout {
id: stackLayout
currentIndex: 0
Connections {
function onActiveTimelineChanged() {
stackLayout.currentIndex = 0;
}
target: TimelineManager
}
MessageView {
Layout.fillWidth: true
Layout.fillHeight: true
}
Loader {
source: CallManager.isOnCall && CallManager.callType != CallType.VOICE ? "voip/VideoCall.qml" : ""
onLoaded: TimelineManager.setVideoCallItem()
}
}
TypingIndicator {
}
}
}
CallInviteBar {
id: callInviteBar
Layout.fillWidth: true
z: 3
}
ActiveCallBar {
Layout.fillWidth: true
z: 3
}
Rectangle {
Layout.fillWidth: true
z: 3
height: 1
color: Nheko.colors.mid
}
ReplyPopup {
}
MessageInput {
}
ActiveCallBar {
Layout.fillWidth: true
z: 3
}
NhekoDropArea {
anchors.fill: parent
roomid: TimelineManager.timeline ? TimelineManager.timeline.roomId() : ""
Rectangle {
Layout.fillWidth: true
z: 3
height: 1
color: Nheko.colors.mid
}
ReplyPopup {
}
MessageInput {
}
}
PrivacyScreen {
NhekoDropArea {
anchors.fill: parent
visible: Settings.privacyScreen
screenTimeout: Settings.privacyScreenTimeout
timelineRoot: timelineLayout
roomid: TimelineManager.timeline ? TimelineManager.timeline.roomId() : ""
}
}

View File

@ -9,10 +9,10 @@ Item {
property double tempWidth: Math.min(parent ? parent.width : undefined, model.data.width < 1 ? parent.width : model.data.width)
property double tempHeight: tempWidth * model.data.proportionalHeight
property double divisor: model.isReply ? 5 : 3
property bool tooHigh: tempHeight > timelineRoot.height / divisor
property bool tooHigh: tempHeight > timelineView.height / divisor
height: Math.round(tooHigh ? timelineRoot.height / divisor : tempHeight)
width: Math.round(tooHigh ? (timelineRoot.height / divisor) / model.data.proportionalHeight : tempWidth)
height: Math.round(tooHigh ? timelineView.height / divisor : tempHeight)
width: Math.round(tooHigh ? (timelineView.height / divisor) / model.data.proportionalHeight : tempWidth)
Image {
id: blurhash

View File

@ -29,11 +29,11 @@ Rectangle {
property double tempWidth: Math.min(parent ? parent.width : undefined, model.data.width < 1 ? 400 : model.data.width)
property double tempHeight: tempWidth * model.data.proportionalHeight
property double divisor: model.isReply ? 4 : 2
property bool tooHigh: tempHeight > timelineRoot.height / divisor
property bool tooHigh: tempHeight > timelineView.height / divisor
visible: model.data.type == MtxEvent.VideoMessage
height: tooHigh ? timelineRoot.height / divisor : tempHeight
width: tooHigh ? (timelineRoot.height / divisor) / model.data.proportionalHeight : tempWidth
height: tooHigh ? timelineView.height / divisor : tempHeight
width: tooHigh ? (timelineView.height / divisor) / model.data.proportionalHeight : tempWidth
Image {
anchors.fill: parent

View File

@ -11,7 +11,7 @@ MatrixText {
text: "<style type=\"text/css\">a { color:" + Nheko.colors.link + ";}\ncode { background-color: " + Nheko.colors.alternateBase + ";}</style>" + formatted.replace("<pre>", "<pre style='white-space: pre-wrap; background-color: " + Nheko.colors.alternateBase + "'>")
width: parent ? parent.width : undefined
height: isReply ? Math.round(Math.min(timelineRoot.height / 8, implicitHeight)) : undefined
height: isReply ? Math.round(Math.min(timelineView.height / 8, implicitHeight)) : undefined
clip: isReply
selectByMouse: !Settings.mobileMode && !isReply
font.pointSize: (Settings.enlargeEmojiOnlyMessages && model.data.isOnlyEmoji > 0 && model.data.isOnlyEmoji < 4) ? Settings.fontSize * 3 : Settings.fontSize

View File

@ -123,6 +123,8 @@
<qresource prefix="/">
<file>qtquickcontrols2.conf</file>
<file>qml/Root.qml</file>
<file>qml/ChatPage.qml</file>
<file>qml/TimelineView.qml</file>
<file>qml/Avatar.qml</file>
<file>qml/Completer.qml</file>

View File

@ -257,7 +257,7 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par
view->engine()->addImageProvider("MxcImage", imgProvider);
view->engine()->addImageProvider("colorimage", colorImgProvider);
view->engine()->addImageProvider("blurhash", blurhashProvider);
view->setSource(QUrl("qrc:///qml/TimelineView.qml"));
view->setSource(QUrl("qrc:///qml/Root.qml"));
connect(parent, &ChatPage::themeChanged, this, &TimelineViewManager::updateColorPalette);
connect(parent,

View File

@ -14,6 +14,9 @@ class Nheko : public QObject
Q_PROPERTY(QPalette colors READ colors NOTIFY colorsChanged)
Q_PROPERTY(QPalette inactiveColors READ inactiveColors NOTIFY colorsChanged)
Q_PROPERTY(int avatarSize READ avatarSize CONSTANT)
Q_PROPERTY(int paddingSmall READ paddingSmall CONSTANT)
Q_PROPERTY(int paddingMedium READ paddingMedium CONSTANT)
Q_PROPERTY(int paddingLarge READ paddingLarge CONSTANT)
public:
Nheko();
@ -23,6 +26,10 @@ public:
int avatarSize() const { return 40; }
int paddingSmall() const { return 4; }
int paddingMedium() const { return 8; }
int paddingLarge() const { return 20; }
Q_INVOKABLE void openLink(QString link) const;
signals: