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

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

View File

@@ -129,13 +129,19 @@ TimelineContainer {
TapHandler {
acceptedButtons: Qt.LeftButton
gesturePolicy: TapHandler.ReleaseWithinBounds | TapHandler.WithinBounds
onTapped: {
imageContainer.QQC2.ToolTip.hide()
if (root.mediaInfo.animated) {
imageContainer.imageItem.paused = true
}
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()
} else {
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 var maxWidth: Number.POSITIVE_INFINITY
property int colorSet: Kirigami.Theme.Window
topPadding: Kirigami.Units.largeSpacing
bottomPadding: 0 // Note not 0 by default
@@ -42,6 +44,6 @@ QQC2.ItemDelegate {
background: Rectangle {
color: Config.blur ? "transparent" : Kirigami.Theme.backgroundColor
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
/**
* @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.
*/
@@ -262,12 +272,17 @@ ColumnLayout {
*/
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.
*
* 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.
@@ -296,7 +311,7 @@ ColumnLayout {
width: parent ? timelineDelegateSizeHelper.currentWidth : 0
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
states: [
State {
@@ -325,21 +340,21 @@ ColumnLayout {
SectionDelegate {
id: sectionDelegate
Layout.fillWidth: true
visible: root.showSection
labelText: root.section
colorSet: Config.compactLayout || root.alwaysMaxWidth ? Kirigami.Theme.View : Kirigami.Theme.Window
}
QQC2.ItemDelegate {
id: mainContainer
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.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: {
if (root.isReply && root.reply === undefined) {
@@ -365,7 +380,7 @@ ColumnLayout {
topMargin: Kirigami.Units.smallSpacing
}
visible: root.showAuthor &&
visible: (root.showAuthor || root.alwaysShowAuthor) &&
Config.showAvatarInTimeline &&
(Config.compactLayout || !showUserMessageOnRight)
name: root.author.displayName
@@ -395,7 +410,7 @@ ColumnLayout {
rightMargin: Kirigami.Units.largeSpacing
}
// 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"
// states for anchor animations on window resize
@@ -440,7 +455,7 @@ ColumnLayout {
id: rowLayout
spacing: Kirigami.Units.smallSpacing
visible: root.showAuthor
visible: root.showAuthor || root.alwaysShowAuthor
QQC2.Label {
id: nameLabel
@@ -535,7 +550,7 @@ ColumnLayout {
}
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)
radius: Kirigami.Units.smallSpacing
}
@@ -577,6 +592,16 @@ ColumnLayout {
return (yoff + height > 0 && yoff < ListView.view.height)
}
Component {
id: messageDelegateContextMenu
MessageDelegateContextMenu {}
}
Component {
id: fileDelegateContextMenu
FileDelegateContextMenu {}
}
/// Open message context dialog for file and videos
function openFileContext(file) {
const contextMenu = fileDelegateContextMenu.createObject(root, {
@@ -616,8 +641,8 @@ ColumnLayout {
startBreakpoint: Kirigami.Units.gridUnit * 46
endBreakpoint: Kirigami.Units.gridUnit * 66
startPercentWidth: 100
endPercentWidth: Config.compactLayout ? 100 : 85
maxWidth: Config.compactLayout ? -1 : Kirigami.Units.gridUnit * 60
endPercentWidth: Config.compactLayout || root.alwaysMaxWidth ? 100 : 85
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
}
@@ -625,8 +650,8 @@ ColumnLayout {
id: bubbleSizeHelper
startBreakpoint: Kirigami.Units.gridUnit * 25
endBreakpoint: Kirigami.Units.gridUnit * 40
startPercentWidth: Config.compactLayout ? 100 : 90
endPercentWidth: Config.compactLayout ? 100 : 60
startPercentWidth: Config.compactLayout || root.alwaysMaxWidth ? 100 : 90
endPercentWidth: Config.compactLayout || root.alwaysMaxWidth ? 100 : 60
parentWidth: mainContainer.availableWidth - (Config.showAvatarInTimeline ? avatar.width + bubble.anchors.leftMargin : 0)
}

View File

@@ -292,7 +292,12 @@ TimelineContainer {
onTriggered: {
root.ListView.view.interactive = false
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 {
acceptedButtons: Qt.LeftButton
gesturePolicy: TapHandler.ReleaseWithinBounds | TapHandler.WithinBounds
onTapped: if (root.progressInfo.completed) {
if (vid.playbackState == MediaPlayer.PlayingState) {
vid.pause()
@@ -352,7 +358,7 @@ TimelineContainer {
playSavedFile()
} else {
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 {
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 var sectionBannerItem: contentHeight >= height ? itemAtIndex(sectionBannerIndex()) : undefined
@@ -47,33 +50,23 @@ QQC2.ScrollView {
interactive: Kirigami.Settings.isMobile
bottomMargin: Kirigami.Units.largeSpacing + Math.round(Kirigami.Theme.defaultFont.pointSize * 2)
model: sortedMessageEventModel
MessageEventModel {
id: messageEventModel
room: root.currentRoom
}
MessageFilterModel {
id: sortedMessageEventModel
sourceModel: messageEventModel
}
model: RoomManager.messageFilterModel
Timer {
interval: 1000
running: messageListView.atYBeginning
triggeredOnStart: true
onTriggered: {
if (messageListView.atYBeginning && messageEventModel.canFetchMore(messageEventModel.index(0, 0))) {
messageEventModel.fetchMore(messageEventModel.index(0, 0));
if (messageListView.atYBeginning && RoomManager.messageEventModel.canFetchMore(RoomManager.messageEventModel.index(0, 0))) {
RoomManager.messageEventModel.fetchMore(RoomManager.messageEventModel.index(0, 0));
}
}
repeat: true
}
// HACK: The view should do this automatically but doesn't.
onAtYBeginningChanged: if (atYBeginning && messageEventModel.canFetchMore(messageEventModel.index(0, 0))) {
messageEventModel.fetchMore(messageEventModel.index(0, 0));
onAtYBeginningChanged: if (atYBeginning && RoomManager.messageEventModel.canFetchMore(RoomManager.messageEventModel.index(0, 0))) {
RoomManager.messageEventModel.fetchMore(RoomManager.messageEventModel.index(0, 0));
}
Timer {
@@ -207,18 +200,6 @@ QQC2.ScrollView {
}
}
Component {
id: messageDelegateContextMenu
MessageDelegateContextMenu {}
}
Component {
id: fileDelegateContextMenu
FileDelegateContextMenu {}
}
TypingPane {
id: typingPane
visible: root.currentRoom && root.currentRoom.usersTyping.length > 0
@@ -285,7 +266,7 @@ QQC2.ScrollView {
}
Connections {
target: messageEventModel
target: RoomManager.messageEventModel
function onRowsInserted() {
markReadIfVisibleTimer.restart()
@@ -326,7 +307,7 @@ QQC2.ScrollView {
Connections {
//enabled: Config.showFancyEffects
target: messageEventModel
target: RoomManager.messageEventModel
function onFancyEffectsReasonFound(fancyEffect) {
fancyEffectsContainer.processFancyEffectsReason(fancyEffect)
@@ -344,21 +325,23 @@ QQC2.ScrollView {
}
}
MediaMessageFilterModel {
id: mediaMessageFilterModel
sourceModel: sortedMessageEventModel
}
Component {
id: maximizeComponent
NeochatMaximizeComponent {
model: mediaMessageFilterModel
model: RoomManager.mediaMessageFilterModel
}
}
Connections {
target: RoomManager
function onShowMaximizedMedia(index) {
messageListView.showMaximizedMedia(index)
}
}
function showMaximizedMedia(index) {
var popup = maximizeComponent.createObject(QQC2.ApplicationWindow.overlay, {
initialIndex: index === -1 ? -1 : mediaMessageFilterModel.getRowForSourceItem(index)
initialIndex: index
})
popup.closed.connect(() => {
messageListView.interactive = true
@@ -374,10 +357,10 @@ QQC2.ScrollView {
}
function eventToIndex(eventID) {
const index = messageEventModel.eventIdToRow(eventID)
const index = RoomManager.messageEventModel.eventIdToRow(eventID)
if (index === -1)
return -1
return sortedMessageEventModel.mapFromSource(messageEventModel.index(index, 0)).row
return RoomManager.messageFilterModel.mapFromSource(RoomManager.messageEventModel.index(index, 0)).row
}
function firstVisibleIndex() {