Room Drawer Media Tab

Add a tab bar to the room drawer which includes a new media tab in addition to the room information tab. This mr completes the architecture for adding others easily later e.g. message highlights or threads.

To put this together I had to make sure things like the menus and the maximize delegate were available to both the room drawer and page so there is some rework there to put it all together.

Wide\
![image](/uploads/b7d3a3ee00016f9ede5cf6fb93e7b40c/image.png)

Mobile\
![image](/uploads/aa02e23f79b37f6cad903d3f356e0ef4/image.png)
This commit is contained in:
James Graham
2023-09-03 10:25:04 +00:00
parent 54cc3ac761
commit 199772a013
17 changed files with 562 additions and 264 deletions

View File

@@ -2,12 +2,17 @@
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
#include "mediamessagefiltermodel.h" #include "mediamessagefiltermodel.h"
#include "models/messageeventmodel.h"
#include <Quotient/room.h> #include <Quotient/room.h>
MediaMessageFilterModel::MediaMessageFilterModel(QObject *parent) #include "messageeventmodel.h"
#include "messagefiltermodel.h"
MediaMessageFilterModel::MediaMessageFilterModel(QObject *parent, MessageFilterModel *sourceMediaModel)
: QSortFilterProxyModel(parent) : QSortFilterProxyModel(parent)
{ {
Q_ASSERT(sourceMediaModel);
setSourceModel(sourceMediaModel);
} }
bool MediaMessageFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const bool MediaMessageFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
@@ -43,9 +48,9 @@ QVariant MediaMessageFilterModel::data(const QModelIndex &index, int role) const
} }
if (role == TypeRole) { if (role == TypeRole) {
if (mapToSource(index).data(MessageEventModel::DelegateTypeRole).toInt() == MessageEventModel::DelegateType::Image) { if (mapToSource(index).data(MessageEventModel::DelegateTypeRole).toInt() == MessageEventModel::DelegateType::Image) {
return 0; return MediaType::Image;
} else { } else {
return 1; return MediaType::Video;
} }
} }
if (role == CaptionRole) { if (role == CaptionRole) {
@@ -57,6 +62,13 @@ QVariant MediaMessageFilterModel::data(const QModelIndex &index, int role) const
if (role == SourceHeightRole) { if (role == SourceHeightRole) {
return mapToSource(index).data(MessageEventModel::MediaInfoRole).toMap()[QStringLiteral("height")].toFloat(); return mapToSource(index).data(MessageEventModel::MediaInfoRole).toMap()[QStringLiteral("height")].toFloat();
} }
// We need to catch this one and return true if the next media object was
// on a different day.
if (role == MessageEventModel::ShowSectionRole) {
const auto day = mapToSource(index).data(MessageEventModel::TimeRole).toDateTime().toLocalTime().date();
const auto previousEventDay = mapToSource(this->index(index.row() + 1, 0)).data(MessageEventModel::TimeRole).toDateTime().toLocalTime().date();
return day != previousEventDay;
}
return sourceModel()->data(mapToSource(index), role); return sourceModel()->data(mapToSource(index), role);
} }

View File

@@ -4,9 +4,12 @@
#pragma once #pragma once
#include <QSortFilterProxyModel> #include <QSortFilterProxyModel>
#include <qobjectdefs.h>
#include "models/messagefiltermodel.h" #include "models/messagefiltermodel.h"
class MessageFilterModel;
/** /**
* @class MediaMessageFilterModel * @class MediaMessageFilterModel
* *
@@ -18,6 +21,12 @@ class MediaMessageFilterModel : public QSortFilterProxyModel
{ {
Q_OBJECT Q_OBJECT
public: public:
enum MediaType {
Image = 0,
Video,
};
Q_ENUM(MediaType)
/** /**
* @brief Defines the model roles. * @brief Defines the model roles.
*/ */
@@ -31,7 +40,7 @@ public:
}; };
Q_ENUM(Roles) Q_ENUM(Roles)
explicit MediaMessageFilterModel(QObject *parent = nullptr); explicit MediaMessageFilterModel(QObject *parent = nullptr, MessageFilterModel *sourceMediaModel = nullptr);
/** /**
* @brief Custom filter to show only image and video messages. * @brief Custom filter to show only image and video messages.
@@ -43,7 +52,7 @@ public:
* *
* @sa QSortFilterProxyModel::data * @sa QSortFilterProxyModel::data
*/ */
[[nodiscard]] QVariant data(const QModelIndex &idx, int role = Qt::DisplayRole) const override; [[nodiscard]] QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
/** /**
* @brief Returns a mapping from Role enum values to role names. * @brief Returns a mapping from Role enum values to role names.

View File

@@ -10,9 +10,12 @@
using namespace Quotient; using namespace Quotient;
MessageFilterModel::MessageFilterModel(QObject *parent) MessageFilterModel::MessageFilterModel(QObject *parent, MessageEventModel *sourceMessageModel)
: QSortFilterProxyModel(parent) : QSortFilterProxyModel(parent)
{ {
Q_ASSERT(sourceMessageModel);
setSourceModel(sourceMessageModel);
connect(NeoChatConfig::self(), &NeoChatConfig::ShowStateEventChanged, this, [this] { connect(NeoChatConfig::self(), &NeoChatConfig::ShowStateEventChanged, this, [this] {
invalidateFilter(); invalidateFilter();
}); });

View File

@@ -33,7 +33,7 @@ public:
LastRole, // Keep this last LastRole, // Keep this last
}; };
explicit MessageFilterModel(QObject *parent = nullptr); explicit MessageFilterModel(QObject *parent = nullptr, MessageEventModel *sourceMessageModel = nullptr);
/** /**
* @brief Custom filter function to remove hidden messages. * @brief Custom filter function to remove hidden messages.

View File

@@ -100,6 +100,11 @@ Components.AlbumMaximizeComponent {
dialog.currentFile = dialog.folder + "/" + currentRoom.fileNameToDownload(root.currentEventId) dialog.currentFile = dialog.folder + "/" + currentRoom.fileNameToDownload(root.currentEventId)
} }
Component {
id: fileDelegateContextMenu
FileDelegateContextMenu {}
}
Component { Component {
id: saveAsDialog id: saveAsDialog
Platform.FileDialog { Platform.FileDialog {

View File

@@ -129,13 +129,19 @@ TimelineContainer {
TapHandler { TapHandler {
acceptedButtons: Qt.LeftButton acceptedButtons: Qt.LeftButton
gesturePolicy: TapHandler.ReleaseWithinBounds | TapHandler.WithinBounds
onTapped: { onTapped: {
imageContainer.QQC2.ToolTip.hide() imageContainer.QQC2.ToolTip.hide()
if (root.mediaInfo.animated) { if (root.mediaInfo.animated) {
imageContainer.imageItem.paused = true imageContainer.imageItem.paused = true
} }
root.ListView.view.interactive = false root.ListView.view.interactive = false
root.ListView.view.showMaximizedMedia(root.index) // We need to make sure the index is that of the MediaMessageFilterModel.
if (root.ListView.view.model instanceof MessageFilterModel) {
RoomManager.maximizeMedia(RoomManager.mediaMessageFilterModel.getRowForSourceItem(root.index))
} else {
RoomManager.maximizeMedia(root.index)
}
} }
} }
@@ -144,7 +150,7 @@ TimelineContainer {
openSavedFile() openSavedFile()
} else { } else {
openOnFinished = true openOnFinished = true
currentRoom.downloadFile(root.eventId, StandardPaths.writableLocation(StandardPaths.CacheLocation) + "/" + root.eventId.replace(":", "_").replace("/", "_").replace("+", "_") + currentRoom.fileNameToDownload(root.eventId)) ListView.view.currentRoom.downloadFile(root.eventId, StandardPaths.writableLocation(StandardPaths.CacheLocation) + "/" + root.eventId.replace(":", "_").replace("/", "_").replace("+", "_") + ListView.view.currentRoom.fileNameToDownload(root.eventId))
} }
} }

View File

@@ -16,6 +16,8 @@ QQC2.ItemDelegate {
property alias labelText: sectionLabel.text property alias labelText: sectionLabel.text
property var maxWidth: Number.POSITIVE_INFINITY property var maxWidth: Number.POSITIVE_INFINITY
property int colorSet: Kirigami.Theme.Window
topPadding: Kirigami.Units.largeSpacing topPadding: Kirigami.Units.largeSpacing
bottomPadding: 0 // Note not 0 by default bottomPadding: 0 // Note not 0 by default
@@ -42,6 +44,6 @@ QQC2.ItemDelegate {
background: Rectangle { background: Rectangle {
color: Config.blur ? "transparent" : Kirigami.Theme.backgroundColor color: Config.blur ? "transparent" : Kirigami.Theme.backgroundColor
Kirigami.Theme.inherit: false Kirigami.Theme.inherit: false
Kirigami.Theme.colorSet: Config.compactLayout ? Kirigami.Theme.View : Kirigami.Theme.Window Kirigami.Theme.colorSet: sectionDelegate.colorSet
} }
} }

View File

@@ -65,6 +65,16 @@ ColumnLayout {
*/ */
required property bool showAuthor required property bool showAuthor
/**
* @brief Whether the author should always be shown.
*
* This is primarily used when these delegates are used in a filtered list of
* events rather than a sequential timeline, e.g. the media model view.
*
* @note This setting still respects the avatar configuration settings.
*/
property bool alwaysShowAuthor: false
/** /**
* @brief The delegate type of the message. * @brief The delegate type of the message.
*/ */
@@ -262,12 +272,17 @@ ColumnLayout {
*/ */
property bool cardBackground: true property bool cardBackground: true
/**
* @brief Whether the delegate should always stretch to the maximum availabel width.
*/
property bool alwaysMaxWidth: false
/** /**
* @brief Whether local user messages should be aligned right. * @brief Whether local user messages should be aligned right.
* *
* TODO: make private * TODO: make private
*/ */
property bool showUserMessageOnRight: Config.showLocalMessagesOnRight && root.author.isLocalUser && !Config.compactLayout property bool showUserMessageOnRight: Config.showLocalMessagesOnRight && root.author.isLocalUser && !Config.compactLayout && !alwaysMaxWidth
/** /**
* @brief Whether the message should be highlighted. * @brief Whether the message should be highlighted.
@@ -296,7 +311,7 @@ ColumnLayout {
width: parent ? timelineDelegateSizeHelper.currentWidth : 0 width: parent ? timelineDelegateSizeHelper.currentWidth : 0
spacing: Kirigami.Units.smallSpacing spacing: Kirigami.Units.smallSpacing
state: Config.compactLayout ? "alignLeft" : "alignCenter" state: Config.compactLayout || root.alwaysMaxWidth ? "alignLeft" : "alignCenter"
// Align left when in compact mode and center when using bubbles // Align left when in compact mode and center when using bubbles
states: [ states: [
State { State {
@@ -325,21 +340,21 @@ ColumnLayout {
SectionDelegate { SectionDelegate {
id: sectionDelegate id: sectionDelegate
Layout.fillWidth: true Layout.fillWidth: true
visible: root.showSection visible: root.showSection
labelText: root.section labelText: root.section
colorSet: Config.compactLayout || root.alwaysMaxWidth ? Kirigami.Theme.View : Kirigami.Theme.Window
} }
QQC2.ItemDelegate { QQC2.ItemDelegate {
id: mainContainer id: mainContainer
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: root.showAuthor ? Kirigami.Units.largeSpacing : (Config.compactLayout ? 1 : Kirigami.Units.smallSpacing) Layout.topMargin: root.showAuthor || root.alwaysShowAuthor ? Kirigami.Units.largeSpacing : (Config.compactLayout ? 1 : Kirigami.Units.smallSpacing)
Layout.leftMargin: Kirigami.Units.smallSpacing Layout.leftMargin: Kirigami.Units.smallSpacing
Layout.rightMargin: Kirigami.Units.smallSpacing Layout.rightMargin: Kirigami.Units.smallSpacing
implicitHeight: Math.max(root.showAuthor ? avatar.implicitHeight : 0, bubble.height) implicitHeight: Math.max(root.showAuthor || root.alwaysShowAuthor ? avatar.implicitHeight : 0, bubble.height)
Component.onCompleted: { Component.onCompleted: {
if (root.isReply && root.reply === undefined) { if (root.isReply && root.reply === undefined) {
@@ -365,7 +380,7 @@ ColumnLayout {
topMargin: Kirigami.Units.smallSpacing topMargin: Kirigami.Units.smallSpacing
} }
visible: root.showAuthor && visible: (root.showAuthor || root.alwaysShowAuthor) &&
Config.showAvatarInTimeline && Config.showAvatarInTimeline &&
(Config.compactLayout || !showUserMessageOnRight) (Config.compactLayout || !showUserMessageOnRight)
name: root.author.displayName name: root.author.displayName
@@ -395,7 +410,7 @@ ColumnLayout {
rightMargin: Kirigami.Units.largeSpacing rightMargin: Kirigami.Units.largeSpacing
} }
// HACK: anchoring didn't reset anchors.right when switching from parent.right to undefined reliably // HACK: anchoring didn't reset anchors.right when switching from parent.right to undefined reliably
width: Config.compactLayout ? mainContainer.width - (Config.showAvatarInTimeline ? Kirigami.Units.gridUnit * 2 : 0) + Kirigami.Units.largeSpacing * 2 : implicitWidth width: Config.compactLayout || root.alwaysMaxWidth ? mainContainer.width - (Config.showAvatarInTimeline ? Kirigami.Units.gridUnit * 2 : 0) + Kirigami.Units.largeSpacing * 2 : implicitWidth
state: showUserMessageOnRight ? "userMessageOnRight" : "userMessageOnLeft" state: showUserMessageOnRight ? "userMessageOnRight" : "userMessageOnLeft"
// states for anchor animations on window resize // states for anchor animations on window resize
@@ -440,7 +455,7 @@ ColumnLayout {
id: rowLayout id: rowLayout
spacing: Kirigami.Units.smallSpacing spacing: Kirigami.Units.smallSpacing
visible: root.showAuthor visible: root.showAuthor || root.alwaysShowAuthor
QQC2.Label { QQC2.Label {
id: nameLabel id: nameLabel
@@ -535,7 +550,7 @@ ColumnLayout {
} }
background: Rectangle { background: Rectangle {
visible: mainContainer.hovered && Config.compactLayout visible: mainContainer.hovered && (Config.compactLayout || root.alwaysMaxWidth)
color: Kirigami.ColorUtils.tintWithAlpha(Kirigami.Theme.backgroundColor, Kirigami.Theme.highlightColor, 0.15) color: Kirigami.ColorUtils.tintWithAlpha(Kirigami.Theme.backgroundColor, Kirigami.Theme.highlightColor, 0.15)
radius: Kirigami.Units.smallSpacing radius: Kirigami.Units.smallSpacing
} }
@@ -577,6 +592,16 @@ ColumnLayout {
return (yoff + height > 0 && yoff < ListView.view.height) return (yoff + height > 0 && yoff < ListView.view.height)
} }
Component {
id: messageDelegateContextMenu
MessageDelegateContextMenu {}
}
Component {
id: fileDelegateContextMenu
FileDelegateContextMenu {}
}
/// Open message context dialog for file and videos /// Open message context dialog for file and videos
function openFileContext(file) { function openFileContext(file) {
const contextMenu = fileDelegateContextMenu.createObject(root, { const contextMenu = fileDelegateContextMenu.createObject(root, {
@@ -616,8 +641,8 @@ ColumnLayout {
startBreakpoint: Kirigami.Units.gridUnit * 46 startBreakpoint: Kirigami.Units.gridUnit * 46
endBreakpoint: Kirigami.Units.gridUnit * 66 endBreakpoint: Kirigami.Units.gridUnit * 66
startPercentWidth: 100 startPercentWidth: 100
endPercentWidth: Config.compactLayout ? 100 : 85 endPercentWidth: Config.compactLayout || root.alwaysMaxWidth ? 100 : 85
maxWidth: Config.compactLayout ? -1 : Kirigami.Units.gridUnit * 60 maxWidth: Config.compactLayout || root.alwaysMaxWidth ? -1 : Kirigami.Units.gridUnit * 60
parentWidth: root.parent ? root.parent.width - (Config.compactLayout && root.ListView.view.width >= Kirigami.Units.gridUnit * 20 ? Kirigami.Units.gridUnit * 2 + Kirigami.Units.largeSpacing : 0) : 0 parentWidth: root.parent ? root.parent.width - (Config.compactLayout && root.ListView.view.width >= Kirigami.Units.gridUnit * 20 ? Kirigami.Units.gridUnit * 2 + Kirigami.Units.largeSpacing : 0) : 0
} }
@@ -625,8 +650,8 @@ ColumnLayout {
id: bubbleSizeHelper id: bubbleSizeHelper
startBreakpoint: Kirigami.Units.gridUnit * 25 startBreakpoint: Kirigami.Units.gridUnit * 25
endBreakpoint: Kirigami.Units.gridUnit * 40 endBreakpoint: Kirigami.Units.gridUnit * 40
startPercentWidth: Config.compactLayout ? 100 : 90 startPercentWidth: Config.compactLayout || root.alwaysMaxWidth ? 100 : 90
endPercentWidth: Config.compactLayout ? 100 : 60 endPercentWidth: Config.compactLayout || root.alwaysMaxWidth ? 100 : 60
parentWidth: mainContainer.availableWidth - (Config.showAvatarInTimeline ? avatar.width + bubble.anchors.leftMargin : 0) parentWidth: mainContainer.availableWidth - (Config.showAvatarInTimeline ? avatar.width + bubble.anchors.leftMargin : 0)
} }

View File

@@ -292,7 +292,12 @@ TimelineContainer {
onTriggered: { onTriggered: {
root.ListView.view.interactive = false root.ListView.view.interactive = false
vid.pause() vid.pause()
root.ListView.view.showMaximizedMedia(root.index) // We need to make sure the index is that of the MediaMessageFilterModel.
if (root.ListView.view.model instanceof MessageFilterModel) {
RoomManager.maximizeMedia(RoomManager.mediaMessageFilterModel.getRowForSourceItem(root.index))
} else {
RoomManager.maximizeMedia(root.index)
}
} }
} }
} }
@@ -328,6 +333,7 @@ TimelineContainer {
TapHandler { TapHandler {
acceptedButtons: Qt.LeftButton acceptedButtons: Qt.LeftButton
gesturePolicy: TapHandler.ReleaseWithinBounds | TapHandler.WithinBounds
onTapped: if (root.progressInfo.completed) { onTapped: if (root.progressInfo.completed) {
if (vid.playbackState == MediaPlayer.PlayingState) { if (vid.playbackState == MediaPlayer.PlayingState) {
vid.pause() vid.pause()
@@ -352,7 +358,7 @@ TimelineContainer {
playSavedFile() playSavedFile()
} else { } else {
playOnFinished = true playOnFinished = true
currentRoom.downloadFile(root.eventId, Platform.StandardPaths.writableLocation(Platform.StandardPaths.CacheLocation) + "/" + root.eventId.replace(":", "_").replace("/", "_").replace("+", "_") + currentRoom.fileNameToDownload(root.eventId)) ListView.view.currentRoom.downloadFile(root.eventId, Platform.StandardPaths.writableLocation(Platform.StandardPaths.CacheLocation) + "/" + root.eventId.replace(":", "_").replace("/", "_").replace("+", "_") + ListView.view.currentRoom.fileNameToDownload(root.eventId))
} }
} }

View File

@@ -32,6 +32,9 @@ QQC2.ScrollView {
ListView { ListView {
id: messageListView id: messageListView
// So that delegates can access the current room properly.
readonly property NeoChatRoom currentRoom: root.currentRoom
readonly property int largestVisibleIndex: count > 0 ? indexAt(contentX + (width / 2), contentY + height - 1) : -1 readonly property int largestVisibleIndex: count > 0 ? indexAt(contentX + (width / 2), contentY + height - 1) : -1
readonly property var sectionBannerItem: contentHeight >= height ? itemAtIndex(sectionBannerIndex()) : undefined readonly property var sectionBannerItem: contentHeight >= height ? itemAtIndex(sectionBannerIndex()) : undefined
@@ -47,33 +50,23 @@ QQC2.ScrollView {
interactive: Kirigami.Settings.isMobile interactive: Kirigami.Settings.isMobile
bottomMargin: Kirigami.Units.largeSpacing + Math.round(Kirigami.Theme.defaultFont.pointSize * 2) bottomMargin: Kirigami.Units.largeSpacing + Math.round(Kirigami.Theme.defaultFont.pointSize * 2)
model: sortedMessageEventModel model: RoomManager.messageFilterModel
MessageEventModel {
id: messageEventModel
room: root.currentRoom
}
MessageFilterModel {
id: sortedMessageEventModel
sourceModel: messageEventModel
}
Timer { Timer {
interval: 1000 interval: 1000
running: messageListView.atYBeginning running: messageListView.atYBeginning
triggeredOnStart: true triggeredOnStart: true
onTriggered: { onTriggered: {
if (messageListView.atYBeginning && messageEventModel.canFetchMore(messageEventModel.index(0, 0))) { if (messageListView.atYBeginning && RoomManager.messageEventModel.canFetchMore(RoomManager.messageEventModel.index(0, 0))) {
messageEventModel.fetchMore(messageEventModel.index(0, 0)); RoomManager.messageEventModel.fetchMore(RoomManager.messageEventModel.index(0, 0));
} }
} }
repeat: true repeat: true
} }
// HACK: The view should do this automatically but doesn't. // HACK: The view should do this automatically but doesn't.
onAtYBeginningChanged: if (atYBeginning && messageEventModel.canFetchMore(messageEventModel.index(0, 0))) { onAtYBeginningChanged: if (atYBeginning && RoomManager.messageEventModel.canFetchMore(RoomManager.messageEventModel.index(0, 0))) {
messageEventModel.fetchMore(messageEventModel.index(0, 0)); RoomManager.messageEventModel.fetchMore(RoomManager.messageEventModel.index(0, 0));
} }
Timer { Timer {
@@ -207,18 +200,6 @@ QQC2.ScrollView {
} }
} }
Component {
id: messageDelegateContextMenu
MessageDelegateContextMenu {}
}
Component {
id: fileDelegateContextMenu
FileDelegateContextMenu {}
}
TypingPane { TypingPane {
id: typingPane id: typingPane
visible: root.currentRoom && root.currentRoom.usersTyping.length > 0 visible: root.currentRoom && root.currentRoom.usersTyping.length > 0
@@ -285,7 +266,7 @@ QQC2.ScrollView {
} }
Connections { Connections {
target: messageEventModel target: RoomManager.messageEventModel
function onRowsInserted() { function onRowsInserted() {
markReadIfVisibleTimer.restart() markReadIfVisibleTimer.restart()
@@ -326,7 +307,7 @@ QQC2.ScrollView {
Connections { Connections {
//enabled: Config.showFancyEffects //enabled: Config.showFancyEffects
target: messageEventModel target: RoomManager.messageEventModel
function onFancyEffectsReasonFound(fancyEffect) { function onFancyEffectsReasonFound(fancyEffect) {
fancyEffectsContainer.processFancyEffectsReason(fancyEffect) fancyEffectsContainer.processFancyEffectsReason(fancyEffect)
@@ -344,21 +325,23 @@ QQC2.ScrollView {
} }
} }
MediaMessageFilterModel {
id: mediaMessageFilterModel
sourceModel: sortedMessageEventModel
}
Component { Component {
id: maximizeComponent id: maximizeComponent
NeochatMaximizeComponent { NeochatMaximizeComponent {
model: mediaMessageFilterModel model: RoomManager.mediaMessageFilterModel
}
}
Connections {
target: RoomManager
function onShowMaximizedMedia(index) {
messageListView.showMaximizedMedia(index)
} }
} }
function showMaximizedMedia(index) { function showMaximizedMedia(index) {
var popup = maximizeComponent.createObject(QQC2.ApplicationWindow.overlay, { var popup = maximizeComponent.createObject(QQC2.ApplicationWindow.overlay, {
initialIndex: index === -1 ? -1 : mediaMessageFilterModel.getRowForSourceItem(index) initialIndex: index
}) })
popup.closed.connect(() => { popup.closed.connect(() => {
messageListView.interactive = true messageListView.interactive = true
@@ -374,10 +357,10 @@ QQC2.ScrollView {
} }
function eventToIndex(eventID) { function eventToIndex(eventID) {
const index = messageEventModel.eventIdToRow(eventID) const index = RoomManager.messageEventModel.eventIdToRow(eventID)
if (index === -1) if (index === -1)
return -1 return -1
return sortedMessageEventModel.mapFromSource(messageEventModel.index(index, 0)).row return RoomManager.messageFilterModel.mapFromSource(RoomManager.messageEventModel.index(index, 0)).row
} }
function firstVisibleIndex() { function firstVisibleIndex() {

View File

@@ -65,9 +65,12 @@ Kirigami.OverlayDrawer {
onAnimatingChanged: if (dim === false) dim = undefined onAnimatingChanged: if (dim === false) dim = undefined
topPadding: 0 topPadding: 0
bottomPadding: 0
leftPadding: 0 leftPadding: 0
rightPadding: 0 rightPadding: 0
Kirigami.Theme.colorSet: Kirigami.Theme.View Kirigami.Theme.colorSet: Kirigami.Theme.View
contentItem: Loader { contentItem: Loader {
id: loader id: loader
active: root.drawerOpen active: root.drawerOpen
@@ -75,6 +78,8 @@ Kirigami.OverlayDrawer {
sourceComponent: ColumnLayout { sourceComponent: ColumnLayout {
spacing: 0 spacing: 0
Component.onCompleted: infoAction.toggle()
QQC2.ToolBar { QQC2.ToolBar {
Layout.fillWidth: true Layout.fillWidth: true
@@ -83,7 +88,7 @@ Kirigami.OverlayDrawer {
contentItem: RowLayout { contentItem: RowLayout {
Kirigami.Heading { Kirigami.Heading {
Layout.fillWidth: true Layout.fillWidth: true
text: i18n("Room information") text: drawerItemLoader.item ? drawerItemLoader.item.title : ""
} }
QQC2.ToolButton { QQC2.ToolButton {
@@ -102,18 +107,47 @@ Kirigami.OverlayDrawer {
} }
} }
QQC2.ScrollView { Loader {
id: drawerItemLoader
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
sourceComponent: roomInformation
}
// HACK: Hide unnecessary horizontal scrollbar (https://bugreports.qt.io/browse/QTBUG-83890) Component {
QQC2.ScrollBar.horizontal.policy: QQC2.ScrollBar.AlwaysOff id: roomInformation
RoomInformation { RoomInformation {
id: roomInformation
room: root.room room: root.room
} }
} }
Component {
id: roomMedia
RoomMedia {
currentRoom: root.room
}
}
Kirigami.NavigationTabBar {
id: navigationBar
Layout.fillWidth: true
Kirigami.Theme.colorSet: Kirigami.Theme.Window
Kirigami.Theme.inherit: false
actions: [
Kirigami.Action {
id: infoAction
text: i18n("Information")
icon.name: "documentinfo"
onTriggered: drawerItemLoader.sourceComponent = roomInformation
},
Kirigami.Action {
text: i18n("Media")
icon.name: "mail-attachment-symbollic"
onTriggered: drawerItemLoader.sourceComponent = roomMedia
}
]
}
} }
} }
} }

View File

@@ -18,7 +18,7 @@ import org.kde.neochat 1.0
* *
* @sa RoomDrawer * @sa RoomDrawer
*/ */
Kirigami.ScrollablePage { Kirigami.Page {
id: root id: root
/** /**
@@ -26,18 +26,66 @@ Kirigami.ScrollablePage {
*/ */
readonly property NeoChatRoom room: RoomManager.currentRoom readonly property NeoChatRoom room: RoomManager.currentRoom
title: roomInformation.title title: drawerItemLoader.item ? drawerItemLoader.item.title : ""
topPadding: 0
bottomPadding: 0
leftPadding: 0
rightPadding: 0
Kirigami.Theme.colorSet: Kirigami.Theme.View
Kirigami.Theme.inherit: false
Component.onCompleted: infoAction.toggle()
actions { actions {
main: Kirigami.Action { main: Kirigami.Action {
displayHint: Kirigami.DisplayHint.IconOnly
text: i18n("Settings")
icon.name: "settings-configure" icon.name: "settings-configure"
onTriggered: applicationWindow().pageStack.pushDialogLayer('qrc:/Categories.qml', {room: root.room}, { title: i18n("Room Settings") }) onTriggered: applicationWindow().pageStack.pushDialogLayer('qrc:/Categories.qml', {room: root.room}, { title: i18n("Room Settings") })
} }
} }
RoomInformation { Loader {
id: drawerItemLoader
width: parent.width
height: parent.height
sourceComponent: roomInformation
}
Component {
id: roomInformation id: roomInformation
room: root.room RoomInformation {
room: root.room
}
}
Component {
id: roomMedia
RoomMedia {
currentRoom: root.room
}
}
footer: Kirigami.NavigationTabBar {
id: navigationBar
Kirigami.Theme.colorSet: Kirigami.Theme.Window
Kirigami.Theme.inherit: false
actions: [
Kirigami.Action {
id: infoAction
text: i18n("Information")
icon.name: "documentinfo"
onTriggered: drawerItemLoader.sourceComponent = roomInformation
},
Kirigami.Action {
text: i18n("Media")
icon.name: "mail-attachment-symbollic"
onTriggered: drawerItemLoader.sourceComponent = roomMedia
}
]
} }
Connections { Connections {

View File

@@ -24,7 +24,7 @@ import org.kde.neochat 1.0
* *
* @sa RoomDrawer, RoomDrawerPage * @sa RoomDrawer, RoomDrawerPage
*/ */
ListView { QQC2.ScrollView {
id: root id: root
/** /**
@@ -37,206 +37,212 @@ ListView {
*/ */
readonly property string title: i18nc("@action:title", "Room information") readonly property string title: i18nc("@action:title", "Room information")
header: ColumnLayout { // HACK: Hide unnecessary horizontal scrollbar (https://bugreports.qt.io/browse/QTBUG-83890)
id: columnLayout QQC2.ScrollBar.horizontal.policy: QQC2.ScrollBar.AlwaysOff
property alias userListSearchField: userListSearchField ListView {
id: userList
header: ColumnLayout {
id: columnLayout
spacing: 0 property alias userListSearchField: userListSearchField
width: root.width
Loader {
active: true
Layout.fillWidth: true
Layout.topMargin: Kirigami.Units.smallSpacing
sourceComponent: root.room.isDirectChat() ? directChatDrawerHeader : groupChatDrawerHeader
onItemChanged: if (item) {
root.positionViewAtBeginning();
}
}
Kirigami.ListSectionHeader {
label: i18n("Options")
activeFocusOnTab: false
Layout.fillWidth: true
}
Delegates.RoundedItemDelegate {
id: devtoolsButton
icon.name: "tools"
text: i18n("Open developer tools")
visible: Config.developerTools
Layout.fillWidth: true
onClicked: {
applicationWindow().pageStack.pushDialogLayer("qrc:/DevtoolsPage.qml", {room: root.room}, {title: i18n("Developer Tools")})
}
}
Delegates.RoundedItemDelegate {
id: searchButton
icon.name: "search"
text: i18n("Search in this room")
Layout.fillWidth: true
onClicked: {
pageStack.pushDialogLayer("qrc:/SearchPage.qml", {
currentRoom: root.room
}, {
title: i18nc("@action:title", "Search")
})
}
}
Delegates.RoundedItemDelegate {
id: favouriteButton
icon.name: root.room && root.room.isFavourite ? "rating" : "rating-unrated"
text: root.room && root.room.isFavourite ? i18n("Remove room from favorites") : i18n("Make room favorite")
onClicked: root.room.isFavourite ? root.room.removeTag("m.favourite") : root.room.addTag("m.favourite", 1.0)
Layout.fillWidth: true
}
Delegates.RoundedItemDelegate {
id: locationsButton
icon.name: "map-flat"
text: i18n("Show locations for this room")
onClicked: pageStack.pushDialogLayer("qrc:/LocationsPage.qml", {
room: root.room
}, {
title: i18nc("Locations on a map", "Locations")
})
Layout.fillWidth: true
}
Kirigami.ListSectionHeader {
label: i18n("Members")
activeFocusOnTab: false
spacing: 0 spacing: 0
visible: !root.room.isDirectChat() width: root.width
Layout.fillWidth: true Loader {
active: true
QQC2.ToolButton { Layout.fillWidth: true
id: memberSearchToggle Layout.topMargin: Kirigami.Units.smallSpacing
checkable: true sourceComponent: root.room.isDirectChat() ? directChatDrawerHeader : groupChatDrawerHeader
icon.name: "search" onItemChanged: if (item) {
QQC2.ToolTip.text: i18n("Search user in room") userList.positionViewAtBeginning();
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
onToggled: {
userListSearchField.text = "";
} }
} }
QQC2.ToolButton { Kirigami.ListSectionHeader {
visible: root.room.canSendState("invite") label: i18n("Options")
icon.name: "list-add-user" activeFocusOnTab: false
onClicked: {
applicationWindow().pageStack.pushDialogLayer("qrc:/InviteUserPage.qml", {room: root.room}, {title: i18nc("@title", "Invite a User")})
}
QQC2.ToolTip.text: i18n("Invite user to room")
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
}
QQC2.Label {
Layout.alignment: Qt.AlignRight
text: root.room ? i18np("%1 member", "%1 members", root.room.joinedCount) : i18n("No member count")
}
}
Kirigami.SearchField {
id: userListSearchField
visible: memberSearchToggle.checked
onVisibleChanged: if (visible) forceActiveFocus()
Layout.fillWidth: true
Layout.leftMargin: Kirigami.Units.largeSpacing - 1
Layout.rightMargin: Kirigami.Units.largeSpacing - 1
Layout.bottomMargin: Kirigami.Units.smallSpacing
focusSequence: "Ctrl+Shift+F"
onAccepted: sortedMessageEventModel.filterString = text;
}
}
KSortFilterProxyModel {
id: sortedMessageEventModel
sourceModel: UserListModel {
room: root.room
}
sortRole: "powerLevel"
sortOrder: Qt.DescendingOrder
filterRole: "name"
filterCaseSensitivity: Qt.CaseInsensitive
}
model: root.room.isDirectChat() ? 0 : sortedMessageEventModel
clip: true
activeFocusOnTab: true
delegate: Delegates.RoundedItemDelegate {
id: userDelegate
required property string name
required property string userId
required property string avatar
required property int powerLevel
required property string powerLevelString
implicitHeight: Kirigami.Units.gridUnit * 2
text: name
onClicked: {
userDelegate.highlighted = true;
RoomManager.visitUser(room.getUser(userDelegate.userId).object, "mention")
}
contentItem: RowLayout {
KirigamiComponents.Avatar {
implicitWidth: height
sourceSize {
height: Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing * 2.5
width: Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing * 2.5
}
source: userDelegate.avatar
name: userDelegate.userId
Layout.fillHeight: true
}
QQC2.Label {
text: userDelegate.name
textFormat: Text.PlainText
elide: Text.ElideRight
Layout.fillWidth: true Layout.fillWidth: true
} }
QQC2.Label { Delegates.RoundedItemDelegate {
visible: userDelegate.powerLevel > 0 id: devtoolsButton
text: userDelegate.powerLevelString icon.name: "tools"
color: Kirigami.Theme.disabledTextColor text: i18n("Open developer tools")
textFormat: Text.PlainText visible: Config.developerTools
Layout.fillWidth: true
onClicked: {
applicationWindow().pageStack.pushDialogLayer("qrc:/DevtoolsPage.qml", {room: root.room}, {title: i18n("Developer Tools")})
}
}
Delegates.RoundedItemDelegate {
id: searchButton
icon.name: "search"
text: i18n("Search in this room")
Layout.fillWidth: true
onClicked: {
pageStack.pushDialogLayer("qrc:/SearchPage.qml", {
currentRoom: root.room
}, {
title: i18nc("@action:title", "Search")
})
}
}
Delegates.RoundedItemDelegate {
id: favouriteButton
icon.name: root.room && root.room.isFavourite ? "rating" : "rating-unrated"
text: root.room && root.room.isFavourite ? i18n("Remove room from favorites") : i18n("Make room favorite")
onClicked: root.room.isFavourite ? root.room.removeTag("m.favourite") : root.room.addTag("m.favourite", 1.0)
Layout.fillWidth: true
}
Delegates.RoundedItemDelegate {
id: locationsButton
icon.name: "map-flat"
text: i18n("Show locations for this room")
onClicked: pageStack.pushDialogLayer("qrc:/LocationsPage.qml", {
room: root.room
}, {
title: i18nc("Locations on a map", "Locations")
})
Layout.fillWidth: true
}
Kirigami.ListSectionHeader {
label: i18n("Members")
activeFocusOnTab: false
spacing: 0
visible: !root.room.isDirectChat()
Layout.fillWidth: true
QQC2.ToolButton {
id: memberSearchToggle
checkable: true
icon.name: "search"
QQC2.ToolTip.text: i18n("Search user in room")
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
onToggled: {
userListSearchField.text = "";
}
}
QQC2.ToolButton {
visible: root.room.canSendState("invite")
icon.name: "list-add-user"
onClicked: {
applicationWindow().pageStack.pushDialogLayer("qrc:/InviteUserPage.qml", {room: root.room}, {title: i18nc("@title", "Invite a User")})
}
QQC2.ToolTip.text: i18n("Invite user to room")
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
}
QQC2.Label {
Layout.alignment: Qt.AlignRight
text: root.room ? i18np("%1 member", "%1 members", root.room.joinedCount) : i18n("No member count")
}
}
Kirigami.SearchField {
id: userListSearchField
visible: memberSearchToggle.checked
onVisibleChanged: if (visible) forceActiveFocus()
Layout.fillWidth: true
Layout.leftMargin: Kirigami.Units.largeSpacing - 1
Layout.rightMargin: Kirigami.Units.largeSpacing - 1
Layout.bottomMargin: Kirigami.Units.smallSpacing
focusSequence: "Ctrl+Shift+F"
onAccepted: sortedMessageEventModel.filterString = text;
}
}
KSortFilterProxyModel {
id: sortedMessageEventModel
sourceModel: UserListModel {
room: root.room
}
sortRole: "powerLevel"
sortOrder: Qt.DescendingOrder
filterRole: "name"
filterCaseSensitivity: Qt.CaseInsensitive
}
model: root.room.isDirectChat() ? 0 : sortedMessageEventModel
clip: true
activeFocusOnTab: true
delegate: Delegates.RoundedItemDelegate {
id: userDelegate
required property string name
required property string userId
required property string avatar
required property int powerLevel
required property string powerLevelString
implicitHeight: Kirigami.Units.gridUnit * 2
text: name
onClicked: {
userDelegate.highlighted = true;
RoomManager.visitUser(room.getUser(userDelegate.userId).object, "mention")
}
contentItem: RowLayout {
KirigamiComponents.Avatar {
implicitWidth: height
sourceSize {
height: Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing * 2.5
width: Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing * 2.5
}
source: userDelegate.avatar
name: userDelegate.userId
Layout.fillHeight: true
}
QQC2.Label {
text: userDelegate.name
textFormat: Text.PlainText
elide: Text.ElideRight
Layout.fillWidth: true
}
QQC2.Label {
visible: userDelegate.powerLevel > 0
text: userDelegate.powerLevelString
color: Kirigami.Theme.disabledTextColor
textFormat: Text.PlainText
}
} }
} }
} }
@@ -255,6 +261,6 @@ ListView {
if (root.headerItem) { if (root.headerItem) {
root.headerItem.userListSearchField.text = ""; root.headerItem.userListSearchField.text = "";
} }
root.currentIndex = -1 userList.currentIndex = -1
} }
} }

View File

@@ -0,0 +1,71 @@
// 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 2.15
import QtQuick.Controls 2.15 as QQC2
import QtQuick.Layouts 1.15
import Qt.labs.qmlmodels 1.0
import org.kde.kirigami 2.20 as Kirigami
import org.kde.neochat 1.0
/**
* @brief Component for visualising the loaded media items in the room.
*
* The component is a simple list of media delegates (videos or images) with the
* ability to open them in the mamimize component.
*
* @note This component is only the contents, it will need to be placed in either
* a drawer (desktop) or page (mobile) to be used.
*
* @sa RoomDrawer, RoomDrawerPage
*/
QQC2.ScrollView {
id: root
/**
* @brief The title that should be displayed for this component if available.
*/
readonly property string title: i18nc("@action:title", "Room Media")
/**
* @brief The current room that user is viewing.
*/
required property NeoChatRoom currentRoom
// HACK: Hide unnecessary horizontal scrollbar (https://bugreports.qt.io/browse/QTBUG-83890)
QQC2.ScrollBar.horizontal.policy: QQC2.ScrollBar.AlwaysOff
ListView {
// So that delegates can access current room properly.
readonly property NeoChatRoom currentRoom: root.currentRoom
clip: true
verticalLayoutDirection: ListView.BottomToTop
model: RoomManager.mediaMessageFilterModel
delegate: DelegateChooser {
role: "type"
DelegateChoice {
roleValue: 0//MediaMessageFilterModel.Image
delegate: ImageDelegate {
alwaysShowAuthor: true
alwaysMaxWidth: true
cardBackground: false
}
}
DelegateChoice {
roleValue: 1//MediaMessageFilterModel.Video
delegate: VideoDelegate {
alwaysShowAuthor: true
alwaysMaxWidth: true
cardBackground: false
}
}
}
}
}

View File

@@ -141,6 +141,7 @@
<file alias="DirectChatDrawerHeader.qml">qml/RoomDrawer/DirectChatDrawerHeader.qml</file> <file alias="DirectChatDrawerHeader.qml">qml/RoomDrawer/DirectChatDrawerHeader.qml</file>
<file alias="GroupChatDrawerHeader.qml">qml/RoomDrawer/GroupChatDrawerHeader.qml</file> <file alias="GroupChatDrawerHeader.qml">qml/RoomDrawer/GroupChatDrawerHeader.qml</file>
<file alias="RoomInformation.qml">qml/RoomDrawer/RoomInformation.qml</file> <file alias="RoomInformation.qml">qml/RoomDrawer/RoomInformation.qml</file>
<file alias="RoomMedia.qml">qml/RoomDrawer/RoomMedia.qml</file>
<file alias="ChooseRoomDialog.qml">qml/Page/ChooseRoomDialog.qml</file> <file alias="ChooseRoomDialog.qml">qml/Page/ChooseRoomDialog.qml</file>
</qresource> </qresource>
</RCC> </RCC>

View File

@@ -5,6 +5,7 @@
#include "roommanager.h" #include "roommanager.h"
#include "controller.h" #include "controller.h"
#include "models/messageeventmodel.h"
#include "neochatconfig.h" #include "neochatconfig.h"
#include "neochatroom.h" #include "neochatroom.h"
#include <KLocalizedString> #include <KLocalizedString>
@@ -26,8 +27,15 @@ RoomManager::RoomManager(QObject *parent)
, m_currentRoom(nullptr) , m_currentRoom(nullptr)
, m_lastCurrentRoom(nullptr) , m_lastCurrentRoom(nullptr)
, m_config(KConfig(QStringLiteral("data"), KConfig::SimpleConfig, QStandardPaths::AppDataLocation)) , m_config(KConfig(QStringLiteral("data"), KConfig::SimpleConfig, QStandardPaths::AppDataLocation))
, m_messageEventModel(new MessageEventModel(this))
, m_messageFilterModel(new MessageFilterModel(this, m_messageEventModel))
, m_mediaMessageFilterModel(new MediaMessageFilterModel(this, m_messageFilterModel))
{ {
m_lastRoomConfig = m_config.group(QStringLiteral("LastOpenRoom")); m_lastRoomConfig = m_config.group(QStringLiteral("LastOpenRoom"));
connect(this, &RoomManager::currentRoomChanged, this, [this]() {
m_messageEventModel->setRoom(m_currentRoom);
});
} }
RoomManager::~RoomManager() RoomManager::~RoomManager()
@@ -45,6 +53,21 @@ NeoChatRoom *RoomManager::currentRoom() const
return m_currentRoom; return m_currentRoom;
} }
MessageEventModel *RoomManager::messageEventModel() const
{
return m_messageEventModel;
}
MessageFilterModel *RoomManager::messageFilterModel() const
{
return m_messageFilterModel;
}
MediaMessageFilterModel *RoomManager::mediaMessageFilterModel() const
{
return m_mediaMessageFilterModel;
}
void RoomManager::openResource(const QString &idOrUri, const QString &action) void RoomManager::openResource(const QString &idOrUri, const QString &action)
{ {
Uri uri{idOrUri}; Uri uri{idOrUri};
@@ -72,6 +95,14 @@ void RoomManager::openResource(const QString &idOrUri, const QString &action)
} }
} }
void RoomManager::maximizeMedia(int index)
{
if (index < -1 || index > m_mediaMessageFilterModel->rowCount()) {
return;
}
Q_EMIT showMaximizedMedia(index);
}
bool RoomManager::hasOpenRoom() const bool RoomManager::hasOpenRoom() const
{ {
return m_currentRoom != nullptr; return m_currentRoom != nullptr;
@@ -266,7 +297,6 @@ void RoomManager::leaveRoom(NeoChatRoom *room)
if (m_currentRoom && m_currentRoom->id() == room->id()) { if (m_currentRoom && m_currentRoom->id() == room->id()) {
m_currentRoom = m_lastCurrentRoom; m_currentRoom = m_lastCurrentRoom;
m_lastCurrentRoom = nullptr; m_lastCurrentRoom = nullptr;
Q_EMIT currentRoomChanged(); Q_EMIT currentRoomChanged();
} }

View File

@@ -9,6 +9,9 @@
#include <Quotient/uriresolver.h> #include <Quotient/uriresolver.h>
#include "chatdocumenthandler.h" #include "chatdocumenthandler.h"
#include "models/mediamessagefiltermodel.h"
#include "models/messageeventmodel.h"
#include "models/messagefiltermodel.h"
class NeoChatRoom; class NeoChatRoom;
@@ -36,6 +39,34 @@ class RoomManager : public QObject, public UriResolverBase
*/ */
Q_PROPERTY(NeoChatRoom *currentRoom READ currentRoom NOTIFY currentRoomChanged) Q_PROPERTY(NeoChatRoom *currentRoom READ currentRoom NOTIFY currentRoomChanged)
/**
* @brief The MessageEventModel that should be used for room message visualisation.
*
* The room object the model uses to get the data will be updated by this class
* so there is no need to do this manually or replace the model when a room
* changes.
*
* @note Available here so that the room page and drawer both have access to the
* same model.
*/
Q_PROPERTY(MessageEventModel *messageEventModel READ messageEventModel CONSTANT)
/**
* @brief The MessageFilterModel that should be used for room message visualisation.
*
* @note Available here so that the room page and drawer both have access to the
* same model.
*/
Q_PROPERTY(MessageFilterModel *messageFilterModel READ messageFilterModel CONSTANT)
/**
* @brief The MediaMessageFilterModel that should be used for room media message visualisation.
*
* @note Available here so that the room page and drawer both have access to the
* same model.
*/
Q_PROPERTY(MediaMessageFilterModel *mediaMessageFilterModel READ mediaMessageFilterModel CONSTANT)
/** /**
* @brief Whether a room is currently open in NeoChat. * @brief Whether a room is currently open in NeoChat.
* *
@@ -57,6 +88,10 @@ public:
NeoChatRoom *currentRoom() const; NeoChatRoom *currentRoom() const;
MessageEventModel *messageEventModel() const;
MessageFilterModel *messageFilterModel() const;
MediaMessageFilterModel *mediaMessageFilterModel() const;
bool hasOpenRoom() const; bool hasOpenRoom() const;
/** /**
@@ -151,6 +186,15 @@ public:
*/ */
Q_INVOKABLE void openResource(const QString &idOrUri, const QString &action = {}); Q_INVOKABLE void openResource(const QString &idOrUri, const QString &action = {});
/**
* @brief Show a media item maximized.
*
* @param index the index to open the maximize delegate model at. This is the
* index in the MediaMessageFilterModel owned by this RoomManager. A value
* of -1 opens a the default item.
*/
Q_INVOKABLE void maximizeMedia(int index);
/** /**
* @brief Call this when the current used connection is dropped. * @brief Call this when the current used connection is dropped.
*/ */
@@ -208,6 +252,15 @@ Q_SIGNALS:
*/ */
void showUserDetail(const Quotient::User *user); void showUserDetail(const Quotient::User *user);
/**
* @brief Request a media item is shown maximized.
*
* @param index the index to open the maximize delegate model at. This is the
* index in the MediaMessageFilterModel owned by this RoomManager. A value
* of -1 opens a the default item.
*/
void showMaximizedMedia(int index);
/** /**
* @brief Show the direct chat confirmation dialog. * @brief Show the direct chat confirmation dialog.
* *
@@ -232,4 +285,8 @@ private:
KConfig m_config; KConfig m_config;
KConfigGroup m_lastRoomConfig; KConfigGroup m_lastRoomConfig;
QPointer<ChatDocumentHandler> m_chatDocumentHandler; QPointer<ChatDocumentHandler> m_chatDocumentHandler;
MessageEventModel *m_messageEventModel;
MessageFilterModel *m_messageFilterModel;
MediaMessageFilterModel *m_mediaMessageFilterModel;
}; };