From 42bef68accdd76ac784b331adbeb982014e8c3ed Mon Sep 17 00:00:00 2001
From: Joseph Donofry <joedonofry@gmail.com>
Date: Mon, 19 Jul 2021 14:11:03 -0400
Subject: [PATCH 01/13] Import and update lurkki's branch

---
 resources/icons/ui/volume-up.png              |   5 +
 .../qml/delegates/PlayableMediaMessage.qml    | 538 ++++++++++++------
 resources/res.qrc                             |   1 +
 3 files changed, 355 insertions(+), 189 deletions(-)
 create mode 100644 resources/icons/ui/volume-up.png

diff --git a/resources/icons/ui/volume-up.png b/resources/icons/ui/volume-up.png
new file mode 100644
index 000000000..4a42643f2
--- /dev/null
+++ b/resources/icons/ui/volume-up.png
@@ -0,0 +1,5 @@
+‰PNG
+
+���
IHDR��� �������½"���bKGD�ÿ�ÿ�ÿ ½§“��IDATH‰Å×Ï‹aðÏü`f4düXÌÂ,äÇÈFÊŽØøµÄŠò7X)"‘äG)V²RƒYÊÂŽÈÚoÑJ¦‹÷y»gnÞëÞ;ïåÔÛ=}Ïyžï÷yßó<Ϲ´Öº0¯Å…Öƒ§˜À~´ÿkýø‰ÉôÜJ¢¦e؍MuæïÄç â.º›%ïĵ4Ñ/Ô9n^W›!Ÿá0É$ÖäLB¶oÂؽÏÄÍ*ò"ý!þÛCl?Rlóë!ïÂÈÈ‹´áFÈ™ÀÆ?bGrp6ÖË
+«ú¹]@^ë´ãxÈ{kLå-Ì„G5Hj=QÀʪ•Â¥{!à'¾E“äQÀBŒ'ìz¾*ÙžÏos>æ8W†€¹øð3aµ‡¾#a²Ä½2À¼Nø7•
+_òO„ü	-ë|~†ÓÉïƪä¿9ýÁK¿}e^mÍ*KÀRìKþw<Nþâ3üü}ê,¼÷eÅU^ñ¶÷0ýv`EòŸSî6VÙ†³T¶áWÙ!kÂgse­8ˆ.‡Üó?ðÍЫµGñ;SâüÌøÞV¡•qmñc!vøoä¹Mç:Þbk“ |õu]ǹ5Ґ5$Ë6€·aìžFÈsk¶%Ä«@~¥òÜò¦´ºâ‹l—©MéÓhJµê¶|D	my#Öƒ'þ㲺魕ð׏ëAf>����IEND®B`‚
\ No newline at end of file
diff --git a/resources/qml/delegates/PlayableMediaMessage.qml b/resources/qml/delegates/PlayableMediaMessage.qml
index fd764d522..3face74d2 100644
--- a/resources/qml/delegates/PlayableMediaMessage.qml
+++ b/resources/qml/delegates/PlayableMediaMessage.qml
@@ -9,9 +9,7 @@ import QtQuick.Controls 2.1
 import QtQuick.Layouts 1.2
 import im.nheko 1.0
 
-Rectangle {
-    id: bg
-
+ColumnLayout {
     required property double proportionalHeight
     required property int type
     required property int originalWidth
@@ -21,205 +19,367 @@ Rectangle {
     required property string body
     required property string filesize
 
-    radius: 10
-    color: Nheko.colors.alternateBase
-    height: Math.round(content.height + 24)
-    width: parent ? parent.width : undefined
-    ListView.onPooled: height = 4
-    ListView.onReused: height = Math.round(content.height + 24)
-
-    Column {
-        id: content
-
-        width: parent.width - 24
-        anchors.centerIn: parent
-
-        Rectangle {
-            id: videoContainer
-
-            property double tempWidth: Math.min(parent ? parent.width : undefined, originalWidth < 1 ? 400 : originalWidth)
-            property double tempHeight: tempWidth * proportionalHeight
-            property double divisor: isReply ? 4 : 2
-            property bool tooHigh: tempHeight > timelineView.height / divisor
-
-            visible: type == MtxEvent.VideoMessage
-            height: tooHigh ? timelineView.height / divisor : tempHeight
-            width: tooHigh ? (timelineView.height / divisor) / proportionalHeight : tempWidth
-
-            Image {
-                anchors.fill: parent
-                source: thumbnailUrl.replace("mxc://", "image://MxcImage/")
-                asynchronous: true
-                fillMode: Image.PreserveAspectFit
-
-                VideoOutput {
-                    anchors.fill: parent
-                    fillMode: VideoOutput.PreserveAspectFit
-                    source: media
-                }
-
-            }
-
-        }
-
-        RowLayout {
-            width: parent.width
-
-            Text {
-                id: positionText
-
-                text: "--:--:--"
-                color: Nheko.colors.text
-            }
-
-            Slider {
-                id: progress
-
-                //indeterminate: true
-                function updatePositionTexts() {
-                    function formatTime(date) {
-                        var hh = date.getUTCHours();
-                        var mm = date.getUTCMinutes();
-                        var ss = date.getSeconds();
-                        if (hh < 10)
-                            hh = "0" + hh;
-
-                        if (mm < 10)
-                            mm = "0" + mm;
-
-                        if (ss < 10)
-                            ss = "0" + ss;
-
-                        return hh + ":" + mm + ":" + ss;
-                    }
-
-                    positionText.text = formatTime(new Date(media.position));
-                    durationText.text = formatTime(new Date(media.duration));
-                }
-
-                Layout.fillWidth: true
-                value: media.position
-                from: 0
-                to: media.duration
-                onMoved: media.seek(value)
-                onValueChanged: updatePositionTexts()
-                palette: Nheko.colors
-            }
+    function durationToString(duration) {
+		function maybeZeroPrepend(time) {
+			return (time < 10) ? "0" + time.toString() :
+				time.toString()
+		}
+		var totalSeconds = Math.floor(duration / 1000)
+		var seconds = totalSeconds % 60
+		var minutes = (Math.floor(totalSeconds / 60)) % 60
+		var hours = (Math.floor(totalSeconds / (60 * 24))) % 24
+		// Always show minutes and don't prepend zero into the leftmost element
+		var ss = maybeZeroPrepend(seconds)
+		var mm = (hours > 0) ? maybeZeroPrepend(minutes) : minutes.toString()
+		var hh = hours.toString()
+
+		if (hours < 1)
+			return mm + ":" + ss
+		return hh + ":" + mm + ":" + ss
+	}
+
+    id: content
+    Layout.maximumWidth: parent? parent.width: undefined
+    MediaPlayer {
+        id: media
+        // TODO: Show error in overlay or so?
+        onError: console.log(errorString)
+        volume: volumeSlider.desiredVolume
+    }
 
-            Text {
-                id: durationText
+    Connections {
+        property bool mediaCached: false
 
-                text: "--:--:--"
-                color: Nheko.colors.text
+        id: mediaCachedObserver
+        target: room
+        onMediaCached: {
+            if (mxcUrl == url) {
+                mediaCached = true
+                media.source = "file://" + cacheUrl
+                console.log("media loaded: " + mxcUrl + " at " + cacheUrl)
             }
-
+            console.log("media cached: " + mxcUrl + " at " + cacheUrl)
         }
-
-        RowLayout {
-            width: parent.width
-            spacing: 15
-
-            ImageButton {
-                id: button
-
-                Layout.alignment: Qt.AlignVCenter
-                //color: Nheko.colors.window
-                //radius: 22
-                height: 32
-                width: 32
-                z: 3
-                image: ":/icons/icons/ui/arrow-pointing-down.png"
-                onClicked: {
-                    switch (button.state) {
-                    case "":
-                        room.cacheMedia(eventId);
-                        break;
-                    case "stopped":
-                        media.play();
-                        console.log("play");
-                        button.state = "playing";
-                        break;
-                    case "playing":
-                        media.pause();
-                        console.log("pause");
-                        button.state = "stopped";
-                        break;
-                    }
+    }
+      
+    Rectangle {
+        id: videoContainer
+        visible: type == MtxEvent.VideoMessage
+        //property double tempWidth: Math.min(parent ? parent.width : undefined, model.data.width < 1 ? 400 : /////model.data.width)
+        // property double tempWidth: (model.data.width < 1) ? 400 : model.data.width
+        // property double tempHeight: tempWidth * model.data.proportionalHeight
+        //property double tempWidth: Math.min(parent ? parent.width : undefined, originalWidth < 1 ? 400 : originalWidth)
+        property double tempWidth: Math.min(parent ? parent.width: undefined, originalWidth < 1 ? 400 : originalWidth)
+        property double tempHeight: tempWidth * proportionalHeight
+
+        property double divisor: isReply ? 4 : 2
+        property bool tooHigh: tempHeight > timelineRoot.height / divisor
+
+        Layout.maximumWidth: Layout.preferredWidth
+        Layout.preferredHeight: tooHigh ? timelineRoot.height / divisor : tempHeight
+        Layout.preferredWidth: tooHigh ? (timelineRoot.height / divisor) / proportionalHeight : tempWidth
+        Image {
+            anchors.fill: parent
+            source: thumbnailUrl.replace("mxc://", "image://MxcImage/")
+            asynchronous: true
+            fillMode: Image.PreserveAspectFit
+            // Button and window colored overlay to cache media
+            Rectangle {
+                // Display over video controls
+                z: videoOutput.z + 1
+                visible: !mediaCachedObserver.mediaCached
+                anchors.fill: parent
+                color: Nheko.colors.window
+                opacity: 0.5
+                Image {
+                    property color buttonColor: (cacheVideoArea.containsMouse) ? Nheko.colors.highlight :
+                        Nheko.colors.text
+
+                    anchors.verticalCenter: parent.verticalCenter
+                    anchors.horizontalCenter: parent.horizontalCenter
+                    source: "image://colorimage/:/icons/icons/ui/arrow-pointing-down.png?"+buttonColor						
                 }
-                states: [
-                    State {
-                        name: "stopped"
-
-                        PropertyChanges {
-                            target: button
-                            image: ":/icons/icons/ui/play-sign.png"
-                        }
-
-                    },
-                    State {
-                        name: "playing"
-
-                        PropertyChanges {
-                            target: button
-                            image: ":/icons/icons/ui/pause-symbol.png"
-                        }
-
-                    }
-                ]
-
-                CursorShape {
+                MouseArea {
+                    id: cacheVideoArea
                     anchors.fill: parent
-                    cursorShape: Qt.PointingHandCursor
-                }
-
-                MediaPlayer {
-                    id: media
-
-                    onError: console.log(errorString)
-                    onStatusChanged: {
-                        if (status == MediaPlayer.Loaded)
-                            progress.updatePositionTexts();
-
-                    }
-                    onStopped: button.state = "stopped"
+                    hoverEnabled: true
+                    enabled: !mediaCachedObserver.mediaCached
+                    onClicked: room.cacheMedia(eventId)
                 }
-
-                Connections {
-                    target: room
-                    onMediaCached: {
-                        if (mxcUrl == url) {
-                            media.source = cacheUrl;
-                            button.state = "stopped";
-                            console.log("media loaded: " + mxcUrl + " at " + cacheUrl);
+            }
+            VideoOutput {
+                id: videoOutput
+                clip: true
+                anchors.fill: parent
+                fillMode: VideoOutput.PreserveAspectFit
+                source: media
+                // TODO: once we can use Qt 5.12, use HoverHandler
+                MouseArea {
+                    id: playerMouseArea
+                    // Toggle play state on clicks
+                    onClicked: {
+                        if (controlRect.shouldShowControls &&
+                            !controlRect.contains(mapToItem(controlRect, mouseX, mouseY))) {
+                                (media.playbackState == MediaPlayer.PlayingState) ?
+                                    media.pause() :
+                                    media.play()
                         }
-                        console.log("media cached: " + mxcUrl + " at " + cacheUrl);
                     }
-                }
-
-            }
-
-            ColumnLayout {
-                id: col
+					Rectangle {
+						id: controlRect
+						property int controlHeight: 25
+						property bool shouldShowControls: playerMouseArea.shouldShowControls ||
+							volumeSliderRect.visible
+
+						anchors.bottom: playerMouseArea.bottom
+						// Window color with 128/255 alpha
+						color: {
+							var wc = Nheko.colors.window
+							return Qt.rgba(wc.r, wc.g, wc.b, 0.5)
+						}
+						height: 40
+						width: playerMouseArea.width
+						opacity: shouldShowControls ? 1 : 0
+						// Fade controls in/out
+						Behavior on opacity {
+							OpacityAnimator {
+								duration: 100
+							}
+						}
+
+						RowLayout {
+							anchors.fill: parent
+							width: parent.width
+							// Play/pause button
+							Image {
+								id: playbackStateImage
+								fillMode: Image.PreserveAspectFit
+								Layout.preferredHeight: controlRect.controlHeight
+								Layout.alignment: Qt.AlignVCenter
+								property color controlColor: (playbackStateArea.containsMouse) ?
+									Nheko.colors.highlight : Nheko.colors.text
+
+								source: (media.playbackState == MediaPlayer.PlayingState) ?
+									"image://colorimage/:/icons/icons/ui/pause-symbol.png?"+controlColor :
+									"image://colorimage/:/icons/icons/ui/play-sign.png?"+controlColor
+								MouseArea {
+									id: playbackStateArea
+									
+									anchors.fill: parent
+									hoverEnabled: true
+									onClicked: {
+										(media.playbackState == MediaPlayer.PlayingState) ?
+											media.pause() :
+											media.play()
+									}
+								}
+							}
+							Label {
+								text: (!mediaCachedObserver.mediaCached) ? "-/-" :
+									durationToString(media.position) + "/" + durationToString(media.duration)
+							}
+
+							Slider {
+								Layout.fillWidth: true
+								Layout.minimumWidth: 50
+								height: controlRect.controlHeight
+								value: media.position
+								onMoved: media.seek(value)
+								from: 0
+								to: media.duration
+							}
+							// Volume slider activator
+							Image {
+								property color controlColor: (volumeImageArea.containsMouse) ?
+									Nheko.colors.highlight : Nheko.colors.text
+
+								// TODO: add icons for different volume levels
+								id: volumeImage
+								source: (media.volume > 0 && !media.muted) ?
+									"image://colorimage/:/icons/icons/ui/volume-up.png?"+ controlColor :
+									"image://colorimage/:/icons/icons/ui/volume-off-indicator.png?"+ controlColor
+								Layout.rightMargin: 5
+								Layout.preferredHeight: controlRect.controlHeight
+								fillMode: Image.PreserveAspectFit
+								MouseArea {
+									id: volumeImageArea	
+									anchors.fill: parent
+									hoverEnabled: true
+									onClicked: media.muted = !media.muted
+									onExited: volumeSliderHideTimer.start()
+									onPositionChanged: volumeSliderHideTimer.start()
+									// For hiding volume slider after a while
+									Timer {
+										id: volumeSliderHideTimer
+										interval: 1500
+										repeat: false
+										running: false
+									}
+								}
+								Rectangle {
+									id: volumeSliderRect
+									opacity: (visible) ? 1 : 0
+									Behavior on opacity {
+										OpacityAnimator {
+											duration: 100
+										}
+									}
+									// TODO: figure out a better way to put the slider popup above controlRect
+									anchors.bottom: volumeImage.top
+									anchors.bottomMargin: 10
+									anchors.horizontalCenter: volumeImage.horizontalCenter
+									color: {
+										var wc = Nheko.colors.window
+										return Qt.rgba(wc.r, wc.g, wc.b, 0.5)
+									}
+									/* TODO: base width on the slider width (some issue with it not having a geometry
+										when using the width here?) */
+									width: volumeImage.width * 0.7
+									radius: volumeSlider.width / 2
+									height: controlRect.height * 2 //100
+									visible: volumeImageArea.containsMouse ||
+										volumeSliderHideTimer.running ||
+										volumeSliderRectMouseArea.containsMouse
+									Slider {
+										// Desired value to avoid loop onMoved -> media.volume -> value -> onMoved...
+										property real desiredVolume: 1
+										
+										// TODO: the slider is slightly off-center on the left for some reason...
+										id: volumeSlider
+										from: 0
+										to: 1
+										value: (media.muted) ? 0 :
+											QtMultimedia.convertVolume(desiredVolume,
+												QtMultimedia.LinearVolumeScale,
+												QtMultimedia.LogarithmicVolumeScale)
+										anchors.fill: parent
+										anchors.bottomMargin: parent.height * 0.1
+										anchors.topMargin: parent.height * 0.1
+										anchors.horizontalCenter: parent.horizontalCenter
+										orientation: Qt.Vertical
+										onMoved: desiredVolume = QtMultimedia.convertVolume(value,
+											QtMultimedia.LogarithmicVolumeScale,
+											QtMultimedia.LinearVolumeScale)
+										/* This would be better handled in 'media', but it has some issue with listening
+											to this signal */
+										onDesiredVolumeChanged: media.muted = !(desiredVolume > 0)
+									}
+									// Used for resetting the timer on mouse moves on volumeSliderRect
+									MouseArea {
+										id: volumeSliderRectMouseArea
+										anchors.fill: parent
+										hoverEnabled: true
+										propagateComposedEvents: true
+										onExited: volumeSliderHideTimer.start()
+
+										onClicked: mouse.accepted = false
+										onPressed: mouse.accepted = false
+										onReleased: mouse.accepted = false
+										onPressAndHold: mouse.accepted = false
+										onPositionChanged: {
+											mouse.accepted = false
+											volumeSliderHideTimer.start()
+										}
+									}
+								}
+							}
+
+						}
+					}
+                    // This breaks separation of concerns but this same thing doesn't work when called from controlRect...
+                    property bool shouldShowControls: (containsMouse && controlHideTimer.running) ||
+                        (media.playbackState != MediaPlayer.PlayingState) ||
+                        controlRect.contains(mapToItem(controlRect, mouseX, mouseY))
+
+                    // For hiding controls on stationary cursor
+                    Timer {
+                        id: controlHideTimer
+                        interval: 1500 //ms
+                        repeat: false
+                    }
 
-                Text {
-                    Layout.fillWidth: true
-                    text: body
-                    elide: Text.ElideRight
-                    color: Nheko.colors.text
-                }
+                    hoverEnabled: true
+                    onPositionChanged: controlHideTimer.start()
 
-                Text {
-                    Layout.fillWidth: true
-                    text: filesize
-                    textFormat: Text.PlainText
-                    elide: Text.ElideRight
-                    color: Nheko.colors.text
+                    x: videoOutput.contentRect.x
+                    y: videoOutput.contentRect.y
+                    width: videoOutput.contentRect.width
+                    height: videoOutput.contentRect.height
+                    propagateComposedEvents: true
                 }
-
             }
-
         }
-
     }
-
+    // Audio player
+    // TODO: share code with the video player
+    Rectangle {
+		id: audioControlRect
+		
+		visible: type != MtxEvent.VideoMessage
+		property int controlHeight: 25
+		Layout.preferredHeight: 40
+		RowLayout {
+			anchors.fill: parent
+			width: parent.width
+			// Play/pause button
+			Image {
+				id: audioPlaybackStateImage
+				fillMode: Image.PreserveAspectFit
+				Layout.preferredHeight: controlRect.controlHeight
+				Layout.alignment: Qt.AlignVCenter
+				property color controlColor: (audioPlaybackStateArea.containsMouse) ?
+					Nheko.colors.highlight : Nheko.colors.text
+
+				source: {
+                    if (!mediaCachedObserver.mediaCached)
+                        return "image://colorimage/:/icons/icons/ui/arrow-pointing-down.png?"+controlColor
+                    return (media.playbackState == MediaPlayer.PlayingState) ?
+                        "image://colorimage/:/icons/icons/ui/pause-symbol.png?"+controlColor :
+                        "image://colorimage/:/icons/icons/ui/play-sign.png?"+controlColor
+                }
+				MouseArea {
+					id: audioPlaybackStateArea
+					
+					anchors.fill: parent
+					hoverEnabled: true
+					onClicked: {
+                        if (!mediaCachedObserver.mediaCached) {
+                            room.cacheMedia(eventId)
+                            return
+                        }
+						(media.playbackState == MediaPlayer.PlayingState) ?
+							media.pause() :
+							media.play()
+					}
+				}
+			}
+			Label {
+				text: (!mediaCachedObserver.mediaCached) ? "-/-" :
+					durationToString(media.position) + "/" + durationToString(media.duration)
+			}
+
+			Slider {
+				Layout.fillWidth: true
+				Layout.minimumWidth: 50
+				height: controlRect.controlHeight
+				value: media.position
+				onMoved: media.seek(value)
+				from: 0
+				to: media.duration
+			}
+		}
+	}
+    
+    Label {
+        id: fileInfoLabel
+        
+        background: Rectangle {
+            color: Nheko.colors.base
+        }
+		Layout.fillWidth: true
+		text: body + " [" + filesize + "]"
+		textFormat: Text.PlainText
+		elide: Text.ElideRight
+		color: Nheko.colors.text
+	}
 }
diff --git a/resources/res.qrc b/resources/res.qrc
index f41835f96..1e7d0a25a 100644
--- a/resources/res.qrc
+++ b/resources/res.qrc
@@ -3,6 +3,7 @@
         <file>icons/ui/at-solid.svg</file>
         <file>icons/ui/volume-off-indicator.png</file>
         <file>icons/ui/volume-off-indicator@2x.png</file>
+        <file>icons/ui/volume-up.png</file>
         <file>icons/ui/black-bubble-speech.png</file>
         <file>icons/ui/black-bubble-speech@2x.png</file>
         <file>icons/ui/do-not-disturb-rounded-sign.png</file>
-- 
GitLab


From 8e433a7ed2f06a4ad30a6f74323ad373e8eb9cd7 Mon Sep 17 00:00:00 2001
From: Joseph Donofry <joedonofry@gmail.com>
Date: Wed, 3 Nov 2021 23:06:32 -0400
Subject: [PATCH 02/13] PlayableMediaMessage fixes on macOS

---
 src/ui/MxcMediaProxy.cpp | 33 +++++++++++++++++++++++++++++++--
 1 file changed, 31 insertions(+), 2 deletions(-)

diff --git a/src/ui/MxcMediaProxy.cpp b/src/ui/MxcMediaProxy.cpp
index db8c0f1f3..2015b4203 100644
--- a/src/ui/MxcMediaProxy.cpp
+++ b/src/ui/MxcMediaProxy.cpp
@@ -13,6 +13,11 @@
 #include <QStandardPaths>
 #include <QUrl>
 
+#if defined(Q_OS_MACOS)
+// TODO (red_sky): Remove for Qt6.  See other ifdef below
+#include <QTemporaryFile>
+#endif
+
 #include "EventAccessors.h"
 #include "Logging.h"
 #include "MatrixClient.h"
@@ -75,7 +80,7 @@ MxcMediaProxy::startDownload()
 
     QPointer<MxcMediaProxy> self = this;
 
-    auto processBuffer = [this, encryptionInfo, filename, self](QIODevice &device) {
+    auto processBuffer = [this, encryptionInfo, filename, self, suffix](QIODevice &device) {
         if (!self)
             return;
 
@@ -90,10 +95,34 @@ MxcMediaProxy::startDownload()
         buffer.open(QIODevice::ReadOnly);
         buffer.reset();
 
-        QTimer::singleShot(0, this, [this, filename] {
+        QTimer::singleShot(0, this, [this, filename, suffix, encryptionInfo] {
+#if defined(Q_OS_MACOS)
+            if (encryptionInfo) {
+                // macOS has issues reading from a buffer in setMedia for whatever reason.
+                // Instead, write the buffer to a temporary file and read from that.
+                // This should be fixed in Qt6, so update this when we do that!
+                // TODO: REMOVE IN QT6
+                QTemporaryFile tempFile;
+                tempFile.setFileTemplate(tempFile.fileTemplate() + QLatin1Char('.') + suffix);
+                tempFile.open();
+                tempFile.write(buffer.data());
+                tempFile.close();
+                nhlog::ui()->debug("Playing media from temp buffer file: {}.  Remove in QT6!",
+                                   filename.filePath().toStdString());
+                this->setMedia(QUrl::fromLocalFile(tempFile.fileName()));
+            } else {
+                nhlog::ui()->info(
+                  "Playing buffer with size: {}, {}", buffer.bytesAvailable(), buffer.isOpen());
+                this->setMedia(QUrl::fromLocalFile(filename.filePath()));
+            }
+#else
+            Q_UNSUED(suffix)
+            Q_UNUSED(encryptionInfo)
+
             nhlog::ui()->info(
               "Playing buffer with size: {}, {}", buffer.bytesAvailable(), buffer.isOpen());
             this->setMedia(QMediaContent(filename.fileName()), &buffer);
+#endif
             emit loadedChanged();
         });
     };
-- 
GitLab


From 4bd0ec89398068cade5dabddd1bb57731d425550 Mon Sep 17 00:00:00 2001
From: Joseph Donofry <joedonofry@gmail.com>
Date: Wed, 3 Nov 2021 23:54:51 -0400
Subject: [PATCH 03/13] Fix syntax issue

---
 src/ui/MxcMediaProxy.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/ui/MxcMediaProxy.cpp b/src/ui/MxcMediaProxy.cpp
index 2015b4203..df0298dab 100644
--- a/src/ui/MxcMediaProxy.cpp
+++ b/src/ui/MxcMediaProxy.cpp
@@ -116,7 +116,7 @@ MxcMediaProxy::startDownload()
                 this->setMedia(QUrl::fromLocalFile(filename.filePath()));
             }
 #else
-            Q_UNSUED(suffix)
+            Q_UNUSED(suffix)
             Q_UNUSED(encryptionInfo)
 
             nhlog::ui()->info(
-- 
GitLab


From 13a5194c08090d7cac24f6873dee2cb2b92c3842 Mon Sep 17 00:00:00 2001
From: Joseph Donofry <joedonofry@gmail.com>
Date: Mon, 8 Nov 2021 19:18:11 -0500
Subject: [PATCH 04/13] Minor fixes for undefined qml behavior

---
 resources/qml/delegates/PlayableMediaMessage.qml | 11 ++++-------
 1 file changed, 4 insertions(+), 7 deletions(-)

diff --git a/resources/qml/delegates/PlayableMediaMessage.qml b/resources/qml/delegates/PlayableMediaMessage.qml
index fbc4a637e..eb788c6c9 100644
--- a/resources/qml/delegates/PlayableMediaMessage.qml
+++ b/resources/qml/delegates/PlayableMediaMessage.qml
@@ -39,17 +39,13 @@ ColumnLayout {
 	}
 
     id: content
-    Layout.maximumWidth: parent? parent.width: undefined
+
+    Layout.fillWidth: true
     MxcMedia {
         id: mxcmedia
         // TODO: Show error in overlay or so?
         onError: console.log(error)
         roomm: room
-		onMediaStatusChanged: {
-			if (status == MxcMedia.LoadedMedia) {
-				progress.updatePositionTexts();
-			}
-		}
     }
       
     Rectangle {
@@ -65,9 +61,10 @@ ColumnLayout {
         property double divisor: isReply ? 4 : 2
         property bool tooHigh: tempHeight > timelineRoot.height / divisor
 
-        Layout.maximumWidth: Layout.preferredWidth
         Layout.preferredHeight: tooHigh ? timelineRoot.height / divisor : tempHeight
         Layout.preferredWidth: tooHigh ? (timelineRoot.height / divisor) / proportionalHeight : tempWidth
+        Layout.maximumWidth: Layout.preferredWidth
+
         Image {
             anchors.fill: parent
             source: thumbnailUrl.replace("mxc://", "image://MxcImage/")
-- 
GitLab


From 42b74509ea6e0daef6b9c45207560810f714add1 Mon Sep 17 00:00:00 2001
From: Joseph Donofry <joedonofry@gmail.com>
Date: Mon, 8 Nov 2021 22:55:16 -0500
Subject: [PATCH 05/13] Incorporate nico's suggestions, fix volume slider

---
 .../qml/delegates/PlayableMediaMessage.qml    | 544 +++++++++---------
 1 file changed, 272 insertions(+), 272 deletions(-)

diff --git a/resources/qml/delegates/PlayableMediaMessage.qml b/resources/qml/delegates/PlayableMediaMessage.qml
index eb788c6c9..a5de613dd 100644
--- a/resources/qml/delegates/PlayableMediaMessage.qml
+++ b/resources/qml/delegates/PlayableMediaMessage.qml
@@ -20,23 +20,24 @@ ColumnLayout {
     required property string filesize
 
     function durationToString(duration) {
-		function maybeZeroPrepend(time) {
-			return (time < 10) ? "0" + time.toString() :
-				time.toString()
-		}
-		var totalSeconds = Math.floor(duration / 1000)
-		var seconds = totalSeconds % 60
-		var minutes = (Math.floor(totalSeconds / 60)) % 60
-		var hours = (Math.floor(totalSeconds / (60 * 24))) % 24
-		// Always show minutes and don't prepend zero into the leftmost element
-		var ss = maybeZeroPrepend(seconds)
-		var mm = (hours > 0) ? maybeZeroPrepend(minutes) : minutes.toString()
-		var hh = hours.toString()
+        function maybeZeroPrepend(time) {
+            return (time < 10) ? "0" + time.toString() :
+            time.toString()
+        }
+        var totalSeconds = Math.floor(duration / 1000)
+        var seconds = totalSeconds % 60
+        var minutes = (Math.floor(totalSeconds / 60)) % 60
+        var hours = (Math.floor(totalSeconds / (60 * 24))) % 24
+        // Always show minutes and don't prepend zero into the leftmost element
+        var ss = maybeZeroPrepend(seconds)
+        var mm = (hours > 0) ? maybeZeroPrepend(minutes) : minutes.toString()
+        var hh = hours.toString()
 
-		if (hours < 1)
-			return mm + ":" + ss
-		return hh + ":" + mm + ":" + ss
-	}
+        if (hours < 1) {
+        	return mm + ":" + ss
+		}
+        return hh + ":" + mm + ":" + ss
+    }
 
     id: content
 
@@ -46,8 +47,11 @@ ColumnLayout {
         // TODO: Show error in overlay or so?
         onError: console.log(error)
         roomm: room
+		// desiredVolume is a float from 0.0 -> 1.0, MediaPlayer volume is an int from 0 to 100
+		// this value automatically gets clamped for us between these two values.
+		volume: volumeSlider.desiredVolume * 100
     }
-      
+
     Rectangle {
         id: videoContainer
         visible: type == MtxEvent.VideoMessage
@@ -60,6 +64,7 @@ ColumnLayout {
 
         property double divisor: isReply ? 4 : 2
         property bool tooHigh: tempHeight > timelineRoot.height / divisor
+        color: Nheko.colors.window
 
         Layout.preferredHeight: tooHigh ? timelineRoot.height / divisor : tempHeight
         Layout.preferredWidth: tooHigh ? (timelineRoot.height / divisor) / proportionalHeight : tempWidth
@@ -71,16 +76,16 @@ ColumnLayout {
             asynchronous: true
             fillMode: Image.PreserveAspectFit
             // Button and window colored overlay to cache media
-            Rectangle {
+            Item {
                 // Display over video controls
                 z: videoOutput.z + 1
                 visible: !mxcmedia.loaded
                 anchors.fill: parent
-                color: Nheko.colors.window
-                opacity: 0.5
+                //color: Nheko.colors.window
+                //opacity: 0.5
                 Image {
                     property color buttonColor: (cacheVideoArea.containsMouse) ? Nheko.colors.highlight :
-                        Nheko.colors.text
+                    Nheko.colors.text
 
                     anchors.verticalCenter: parent.verticalCenter
                     anchors.horizontalCenter: parent.horizontalCenter
@@ -100,7 +105,7 @@ ColumnLayout {
                 anchors.fill: parent
                 fillMode: VideoOutput.PreserveAspectFit
                 source: mxcmedia
-				flushMode: VideoOutput.FirstFrame
+                flushMode: VideoOutput.FirstFrame
 
                 // TODO: once we can use Qt 5.12, use HoverHandler
                 MouseArea {
@@ -108,267 +113,262 @@ ColumnLayout {
                     // Toggle play state on clicks
                     onClicked: {
                         if (controlRect.shouldShowControls &&
-                            !controlRect.contains(mapToItem(controlRect, mouseX, mouseY))) {
-                                (mxcmedia.state == MediaPlayer.PlayingState) ?
-                                    mxcmedia.pause() :
-                                    mxcmedia.play()
+                        !controlRect.contains(mapToItem(controlRect, mouseX, mouseY))) {
+                            (mxcmedia.state == MediaPlayer.PlayingState) ?
+                            mxcmedia.pause() :
+                            mxcmedia.play()
                         }
                     }
-					Rectangle {
-						id: controlRect
-						property int controlHeight: 25
-						property bool shouldShowControls: playerMouseArea.shouldShowControls ||
-							volumeSliderRect.visible
+                    Rectangle {
+                        id: controlRect
+                        property int controlHeight: 25
+                        property bool shouldShowControls: playerMouseArea.shouldShowControls ||
+                        volumeSliderRect.visible
 
-						anchors.bottom: playerMouseArea.bottom
-						// Window color with 128/255 alpha
-						color: {
-							var wc = Nheko.colors.window
-							return Qt.rgba(wc.r, wc.g, wc.b, 0.5)
-						}
-						height: 40
-						width: playerMouseArea.width
-						opacity: shouldShowControls ? 1 : 0
-						// Fade controls in/out
-						Behavior on opacity {
-							OpacityAnimator {
-								duration: 100
-							}
-						}
+                        anchors.bottom: playerMouseArea.bottom
+                        // Window color with 128/255 alpha
+                        color: {
+                            var wc = Nheko.colors.alternateBase
+                            return Qt.rgba(wc.r, wc.g, wc.b, 0.5)
+                        }
+                        height: 40
+                        width: playerMouseArea.width
+                        opacity: shouldShowControls ? 1 : 0
+                        // Fade controls in/out
+                        Behavior on opacity {
+                            OpacityAnimator {
+                                duration: 100
+                            }
+                        }
 
-						RowLayout {
-							anchors.fill: parent
-							width: parent.width
-							// Play/pause button
-							Image {
-								id: playbackStateImage
-								fillMode: Image.PreserveAspectFit
-								Layout.preferredHeight: controlRect.controlHeight
-								Layout.alignment: Qt.AlignVCenter
-								property color controlColor: (playbackStateArea.containsMouse) ?
-									Nheko.colors.highlight : Nheko.colors.text
+                        RowLayout {
+                            anchors.fill: parent
+                            width: parent.width
+                            // Play/pause button
+                            Image {
+                                id: playbackStateImage
+                                fillMode: Image.PreserveAspectFit
+                                Layout.preferredHeight: controlRect.controlHeight
+                                Layout.alignment: Qt.AlignVCenter
+                                property color controlColor: (playbackStateArea.containsMouse) ?
+                                Nheko.colors.highlight : Nheko.colors.text
 
-								source: (mxcmedia.state == MediaPlayer.PlayingState) ?
-									"image://colorimage/:/icons/icons/ui/pause-symbol.png?"+controlColor :
-									"image://colorimage/:/icons/icons/ui/play-sign.png?"+controlColor
-								MouseArea {
-									id: playbackStateArea
-									
-									anchors.fill: parent
-									hoverEnabled: true
-									onClicked: {
-										(mxcmedia.state == MediaPlayer.PlayingState) ?
-											mxcmedia.pause() :
-											mxcmedia.play()
-									}
-								}
-							}
-							Label {
-								text: (!mxcmedia.loaded) ? "-/-" :
-									durationToString(mxcmedia.position) + "/" + durationToString(mxcmedia.duration)
-							}
+                                source: (mxcmedia.state == MediaPlayer.PlayingState) ?
+                                "image://colorimage/:/icons/icons/ui/pause-symbol.png?"+controlColor :
+                                "image://colorimage/:/icons/icons/ui/play-sign.png?"+controlColor
+                                MouseArea {
+                                    id: playbackStateArea
 
-							Slider {
-								Layout.fillWidth: true
-								Layout.minimumWidth: 50
-								height: controlRect.controlHeight
-								value: mxcmedia.position
-								onMoved: mxcmedia.position = value
-								from: 0
-								to: mxcmedia.duration
-							}
-							// Volume slider activator
-							Image {
-								property color controlColor: (volumeImageArea.containsMouse) ?
-									Nheko.colors.highlight : Nheko.colors.text
+                                    anchors.fill: parent
+                                    hoverEnabled: true
+                                    onClicked: {
+                                        (mxcmedia.state == MediaPlayer.PlayingState) ?
+                                        mxcmedia.pause() :
+                                        mxcmedia.play()
+                                    }
+                                }
+                            }
+                            Label {
+                                text: (!mxcmedia.loaded) ? "-/-" : (durationToString(mxcmedia.position) + "/" + durationToString(mxcmedia.duration))
+                                color: Nheko.colors.text
+                            }
 
-								// TODO: add icons for different volume levels
-								id: volumeImage
-								source: (mxcmedia.volume > 0 && !mxcmedia.muted) ?
-									"image://colorimage/:/icons/icons/ui/volume-up.png?"+ controlColor :
-									"image://colorimage/:/icons/icons/ui/volume-off-indicator.png?"+ controlColor
-								Layout.rightMargin: 5
-								Layout.preferredHeight: controlRect.controlHeight
-								fillMode: Image.PreserveAspectFit
-								MouseArea {
-									id: volumeImageArea	
-									anchors.fill: parent
-									hoverEnabled: true
-									onClicked: mxcmedia.muted = !mxcmedia.muted
-									onExited: volumeSliderHideTimer.start()
-									onPositionChanged: volumeSliderHideTimer.start()
-									// For hiding volume slider after a while
-									Timer {
-										id: volumeSliderHideTimer
-										interval: 1500
-										repeat: false
-										running: false
-									}
-								}
-								Rectangle {
-									id: volumeSliderRect
-									opacity: (visible) ? 1 : 0
-									Behavior on opacity {
-										OpacityAnimator {
-											duration: 100
-										}
-									}
-									// TODO: figure out a better way to put the slider popup above controlRect
-									anchors.bottom: volumeImage.top
-									anchors.bottomMargin: 10
-									anchors.horizontalCenter: volumeImage.horizontalCenter
-									color: {
-										var wc = Nheko.colors.window
-										return Qt.rgba(wc.r, wc.g, wc.b, 0.5)
-									}
-									/* TODO: base width on the slider width (some issue with it not having a geometry
-										when using the width here?) */
-									width: volumeImage.width * 0.7
-									radius: volumeSlider.width / 2
-									height: controlRect.height * 2 //100
-									visible: volumeImageArea.containsMouse ||
-										volumeSliderHideTimer.running ||
-										volumeSliderRectMouseArea.containsMouse
-									Slider {
-										// Desired value to avoid loop onMoved -> media.volume -> value -> onMoved...
-										property real desiredVolume: 1
-										
-										// TODO: the slider is slightly off-center on the left for some reason...
-										id: volumeSlider
-										from: 0
-										to: 1
-										value: (mxcmedia.muted) ? 0 :
-											QtMultimedia.convertVolume(desiredVolume,
-												QtMultimedia.LinearVolumeScale,
-												QtMultimedia.LogarithmicVolumeScale)
-										anchors.fill: parent
-										anchors.bottomMargin: parent.height * 0.1
-										anchors.topMargin: parent.height * 0.1
-										anchors.horizontalCenter: parent.horizontalCenter
-										orientation: Qt.Vertical
-										onMoved: desiredVolume = QtMultimedia.convertVolume(value,
-											QtMultimedia.LogarithmicVolumeScale,
-											QtMultimedia.LinearVolumeScale)
-										/* This would be better handled in 'media', but it has some issue with listening
-											to this signal */
-										onDesiredVolumeChanged: mxcmedia.muted = !(desiredVolume > 0)
-									}
-									// Used for resetting the timer on mouse moves on volumeSliderRect
-									MouseArea {
-										id: volumeSliderRectMouseArea
-										anchors.fill: parent
-										hoverEnabled: true
-										propagateComposedEvents: true
-										onExited: volumeSliderHideTimer.start()
+                            Slider {
+                                Layout.fillWidth: true
+                                Layout.minimumWidth: 50
+                                height: controlRect.controlHeight
+                                value: mxcmedia.position
+                                onMoved: mxcmedia.position = value
+                                from: 0
+                                to: mxcmedia.duration
+                            }
+                            // Volume slider activator
+                            Image {
+                                property color controlColor: (volumeImageArea.containsMouse) ?
+                                Nheko.colors.highlight : Nheko.colors.text
 
-										onClicked: mouse.accepted = false
-										onPressed: mouse.accepted = false
-										onReleased: mouse.accepted = false
-										onPressAndHold: mouse.accepted = false
-										onPositionChanged: {
-											mouse.accepted = false
-											volumeSliderHideTimer.start()
-										}
-									}
-								}
-							}
+                                // TODO: add icons for different volume levels
+                                id: volumeImage
+                                source: (mxcmedia.volume > 0 && !mxcmedia.muted) ?
+                                "image://colorimage/:/icons/icons/ui/volume-up.png?"+ controlColor :
+                                "image://colorimage/:/icons/icons/ui/volume-off-indicator.png?"+ controlColor
+                                Layout.rightMargin: 5
+                                Layout.preferredHeight: controlRect.controlHeight
+                                fillMode: Image.PreserveAspectFit
+                                MouseArea {
+                                    id: volumeImageArea	
+                                    anchors.fill: parent
+                                    hoverEnabled: true
+                                    onClicked: mxcmedia.muted = !mxcmedia.muted
+                                    onExited: volumeSliderHideTimer.start()
+                                    onPositionChanged: volumeSliderHideTimer.start()
+                                    // For hiding volume slider after a while
+                                    Timer {
+                                        id: volumeSliderHideTimer
+                                        interval: 1500
+                                        repeat: false
+                                        running: false
+                                    }
+                                }
+                                Rectangle {
+                                    id: volumeSliderRect
+                                    opacity: (visible) ? 1 : 0
+                                    Behavior on opacity {
+                                        OpacityAnimator {
+                                            duration: 100
+                                        }
+                                    }
+                                    // TODO: figure out a better way to put the slider popup above controlRect
+                                    anchors.bottom: volumeImage.top
+                                    anchors.bottomMargin: 10
+                                    anchors.horizontalCenter: volumeImage.horizontalCenter
+                                    color: {
+                                        var wc = Nheko.colors.window
+                                        return Qt.rgba(wc.r, wc.g, wc.b, 0.5)
+                                    }
+                                    /* TODO: base width on the slider width (some issue with it not having a geometry
+                                     when using the width here?) */
+                                     width: volumeImage.width * 0.7
+                                     radius: volumeSlider.width / 2
+                                     height: controlRect.height * 2 //100
+                                     visible: volumeImageArea.containsMouse ||
+                                     volumeSliderHideTimer.running ||
+                                     volumeSliderRectMouseArea.containsMouse
+                                     Slider {
+                                         // TODO: the slider is slightly off-center on the left for some reason...
+                                         id: volumeSlider
 
-						}
-					}
-                    // This breaks separation of concerns but this same thing doesn't work when called from controlRect...
-                    property bool shouldShowControls: (containsMouse && controlHideTimer.running) ||
-                        (mxcmedia.state != MediaPlayer.PlayingState) ||
-                        controlRect.contains(mapToItem(controlRect, mouseX, mouseY))
+										 value: 1.0
+                                         // Desired value to avoid loop onMoved -> media.volume -> value -> onMoved...
+                                         property real desiredVolume: QtMultimedia.convertVolume(volumeSlider.value,
+                                         QtMultimedia.LogarithmicVolumeScale,
+                                         QtMultimedia.LinearVolumeScale)
 
-                    // For hiding controls on stationary cursor
-                    Timer {
-                        id: controlHideTimer
-                        interval: 1500 //ms
-                        repeat: false
-                    }
+                                         anchors.fill: parent
+                                         anchors.bottomMargin: parent.height * 0.1
+                                         anchors.topMargin: parent.height * 0.1
+                                         anchors.horizontalCenter: parent.horizontalCenter
+                                         orientation: Qt.Vertical
+										 onDesiredVolumeChanged: {
+											 mxcmedia.muted = !(desiredVolume > 0.0)
+										 }
+                                      }
+                                      // Used for resetting the timer on mouse moves on volumeSliderRect
+                                      MouseArea {
+                                          id: volumeSliderRectMouseArea
+                                          anchors.fill: parent
+                                          hoverEnabled: true
+                                          propagateComposedEvents: true
+                                          onExited: volumeSliderHideTimer.start()
 
-                    hoverEnabled: true
-                    onPositionChanged: controlHideTimer.start()
+                                          onClicked: mouse.accepted = false
+                                          onPressed: mouse.accepted = false
+                                          onReleased: mouse.accepted = false
+                                          onPressAndHold: mouse.accepted = false
+                                          onPositionChanged: {
+                                              mouse.accepted = false
+                                              volumeSliderHideTimer.start()
+                                          }
+                                      }
+                                  }
+                              }
 
-                    x: videoOutput.contentRect.x
-                    y: videoOutput.contentRect.y
-                    width: videoOutput.contentRect.width
-                    height: videoOutput.contentRect.height
-                    propagateComposedEvents: true
-                }
-            }
-        }
-    }
-    // Audio player
-    // TODO: share code with the video player
-    Rectangle {
-		id: audioControlRect
-		
-		visible: type != MtxEvent.VideoMessage
-		property int controlHeight: 25
-		Layout.preferredHeight: 40
-		RowLayout {
-			anchors.fill: parent
-			width: parent.width
-			// Play/pause button
-			Image {
-				id: audioPlaybackStateImage
-				fillMode: Image.PreserveAspectFit
-				Layout.preferredHeight: controlRect.controlHeight
-				Layout.alignment: Qt.AlignVCenter
-				property color controlColor: (audioPlaybackStateArea.containsMouse) ?
-					Nheko.colors.highlight : Nheko.colors.text
+                          }
+                      }
+                      // This breaks separation of concerns but this same thing doesn't work when called from controlRect...
+                      property bool shouldShowControls: (containsMouse && controlHideTimer.running) ||
+                      (mxcmedia.state != MediaPlayer.PlayingState) ||
+                      controlRect.contains(mapToItem(controlRect, mouseX, mouseY))
 
-				source: {
-                    if (!mxcmedia.loaded)
-                        return "image://colorimage/:/icons/icons/ui/arrow-pointing-down.png?"+controlColor
-                    return (mxcmedia.state == MediaPlayer.PlayingState) ?
-                        "image://colorimage/:/icons/icons/ui/pause-symbol.png?"+controlColor :
-                        "image://colorimage/:/icons/icons/ui/play-sign.png?"+controlColor
-                }
-				MouseArea {
-					id: audioPlaybackStateArea
-					
-					anchors.fill: parent
-					hoverEnabled: true
-					onClicked: {
-                        if (!mxcmedia.loaded) {
-                            mxcmedia.eventId = eventId
-                            return
-                        }
-						(mxcmedia.state == MediaPlayer.PlayingState) ?
-							mxcmedia.pause() :
-							mxcmedia.play()
-					}
-				}
-			}
-			Label {
-				text: (!mxcmedia.loaded) ? "-/-" :
-					durationToString(mxcmedia.position) + "/" + durationToString(mxcmedia.duration)
-			}
+                      // For hiding controls on stationary cursor
+                      Timer {
+                          id: controlHideTimer
+                          interval: 1500 //ms
+                          repeat: false
+                      }
 
-			Slider {
-				Layout.fillWidth: true
-				Layout.minimumWidth: 50
-				height: controlRect.controlHeight
-				value: mxcmedia.position
-				onMoved: mxcmedia.seek(value)
-				from: 0
-				to: mxcmedia.duration
-			}
-		}
-	}
-    
-    Label {
-        id: fileInfoLabel
-        
-        background: Rectangle {
-            color: Nheko.colors.base
-        }
-		Layout.fillWidth: true
-		text: body + " [" + filesize + "]"
-		textFormat: Text.PlainText
-		elide: Text.ElideRight
-		color: Nheko.colors.text
-	}
-}
+                      hoverEnabled: true
+                      onPositionChanged: controlHideTimer.start()
+
+                      x: videoOutput.contentRect.x
+                      y: videoOutput.contentRect.y
+                      width: videoOutput.contentRect.width
+                      height: videoOutput.contentRect.height
+                      propagateComposedEvents: true
+                  }
+              }
+          }
+      }
+      // Audio player
+      // TODO: share code with the video player
+      Rectangle {
+          id: audioControlRect
+
+          visible: type != MtxEvent.VideoMessage
+          property int controlHeight: 25
+          Layout.preferredHeight: 40
+          RowLayout {
+              anchors.fill: parent
+              width: parent.width
+              // Play/pause button
+              Image {
+                  id: audioPlaybackStateImage
+                  fillMode: Image.PreserveAspectFit
+                  Layout.preferredHeight: controlRect.controlHeight
+                  Layout.alignment: Qt.AlignVCenter
+                  property color controlColor: (audioPlaybackStateArea.containsMouse) ?
+                  Nheko.colors.highlight : Nheko.colors.text
+
+                  source: {
+                      if (!mxcmedia.loaded)
+                      return "image://colorimage/:/icons/icons/ui/arrow-pointing-down.png?"+controlColor
+                      return (mxcmedia.state == MediaPlayer.PlayingState) ?
+                      "image://colorimage/:/icons/icons/ui/pause-symbol.png?"+controlColor :
+                      "image://colorimage/:/icons/icons/ui/play-sign.png?"+controlColor
+                  }
+                  MouseArea {
+                      id: audioPlaybackStateArea
+
+                      anchors.fill: parent
+                      hoverEnabled: true
+                      onClicked: {
+                          if (!mxcmedia.loaded) {
+                              mxcmedia.eventId = eventId
+                              return
+                          }
+                          (mxcmedia.state == MediaPlayer.PlayingState) ?
+                          mxcmedia.pause() :
+                          mxcmedia.play()
+                      }
+                  }
+              }
+              Label {
+                  text: (!mxcmedia.loaded) ? "-/-" :
+                  durationToString(mxcmedia.position) + "/" + durationToString(mxcmedia.duration)
+              }
+
+              Slider {
+                  Layout.fillWidth: true
+                  Layout.minimumWidth: 50
+                  height: controlRect.controlHeight
+                  value: mxcmedia.position
+                  onMoved: mxcmedia.seek(value)
+                  from: 0
+                  to: mxcmedia.duration
+              }
+          }
+      }
+
+      Label {
+          id: fileInfoLabel
+
+          background: Rectangle {
+              color: Nheko.colors.base
+          }
+          Layout.fillWidth: true
+          text: body + " [" + filesize + "]"
+          textFormat: Text.PlainText
+          elide: Text.ElideRight
+          color: Nheko.colors.text
+      }
+  }
-- 
GitLab


From f6fcae124f1f98cf4ac060cfc7f3e83c9e8b2979 Mon Sep 17 00:00:00 2001
From: Joseph Donofry <joedonofry@gmail.com>
Date: Tue, 9 Nov 2021 19:28:53 -0500
Subject: [PATCH 06/13] Initial Refactoring into separate controls

---
 .../qml/delegates/PlayableMediaMessage.qml    |  95 ++--------------
 resources/qml/ui/media/VolumeControl.qml      | 105 ++++++++++++++++++
 resources/qml/ui/media/qmldir                 |   2 +
 resources/res.qrc                             |   1 +
 4 files changed, 115 insertions(+), 88 deletions(-)
 create mode 100644 resources/qml/ui/media/VolumeControl.qml
 create mode 100644 resources/qml/ui/media/qmldir

diff --git a/resources/qml/delegates/PlayableMediaMessage.qml b/resources/qml/delegates/PlayableMediaMessage.qml
index a5de613dd..ceeeeb1ae 100644
--- a/resources/qml/delegates/PlayableMediaMessage.qml
+++ b/resources/qml/delegates/PlayableMediaMessage.qml
@@ -3,6 +3,7 @@
 // SPDX-License-Identifier: GPL-3.0-or-later
 
 import "../"
+import "../ui/media"
 import QtMultimedia 5.15
 import QtQuick 2.15
 import QtQuick.Controls 2.15
@@ -50,6 +51,7 @@ ColumnLayout {
 		// desiredVolume is a float from 0.0 -> 1.0, MediaPlayer volume is an int from 0 to 100
 		// this value automatically gets clamped for us between these two values.
 		volume: volumeSlider.desiredVolume * 100
+        muted: volumeSlider.muted
     }
 
     Rectangle {
@@ -123,7 +125,7 @@ ColumnLayout {
                         id: controlRect
                         property int controlHeight: 25
                         property bool shouldShowControls: playerMouseArea.shouldShowControls ||
-                        volumeSliderRect.visible
+                        volumeSlider.controlsVisible
 
                         anchors.bottom: playerMouseArea.bottom
                         // Window color with 128/255 alpha
@@ -182,96 +184,13 @@ ColumnLayout {
                                 from: 0
                                 to: mxcmedia.duration
                             }
-                            // Volume slider activator
-                            Image {
-                                property color controlColor: (volumeImageArea.containsMouse) ?
-                                Nheko.colors.highlight : Nheko.colors.text
 
-                                // TODO: add icons for different volume levels
-                                id: volumeImage
-                                source: (mxcmedia.volume > 0 && !mxcmedia.muted) ?
-                                "image://colorimage/:/icons/icons/ui/volume-up.png?"+ controlColor :
-                                "image://colorimage/:/icons/icons/ui/volume-off-indicator.png?"+ controlColor
+                            VolumeControl {
+								id: volumeSlider
+								orientation: Qt.Vertical
                                 Layout.rightMargin: 5
                                 Layout.preferredHeight: controlRect.controlHeight
-                                fillMode: Image.PreserveAspectFit
-                                MouseArea {
-                                    id: volumeImageArea	
-                                    anchors.fill: parent
-                                    hoverEnabled: true
-                                    onClicked: mxcmedia.muted = !mxcmedia.muted
-                                    onExited: volumeSliderHideTimer.start()
-                                    onPositionChanged: volumeSliderHideTimer.start()
-                                    // For hiding volume slider after a while
-                                    Timer {
-                                        id: volumeSliderHideTimer
-                                        interval: 1500
-                                        repeat: false
-                                        running: false
-                                    }
-                                }
-                                Rectangle {
-                                    id: volumeSliderRect
-                                    opacity: (visible) ? 1 : 0
-                                    Behavior on opacity {
-                                        OpacityAnimator {
-                                            duration: 100
-                                        }
-                                    }
-                                    // TODO: figure out a better way to put the slider popup above controlRect
-                                    anchors.bottom: volumeImage.top
-                                    anchors.bottomMargin: 10
-                                    anchors.horizontalCenter: volumeImage.horizontalCenter
-                                    color: {
-                                        var wc = Nheko.colors.window
-                                        return Qt.rgba(wc.r, wc.g, wc.b, 0.5)
-                                    }
-                                    /* TODO: base width on the slider width (some issue with it not having a geometry
-                                     when using the width here?) */
-                                     width: volumeImage.width * 0.7
-                                     radius: volumeSlider.width / 2
-                                     height: controlRect.height * 2 //100
-                                     visible: volumeImageArea.containsMouse ||
-                                     volumeSliderHideTimer.running ||
-                                     volumeSliderRectMouseArea.containsMouse
-                                     Slider {
-                                         // TODO: the slider is slightly off-center on the left for some reason...
-                                         id: volumeSlider
-
-										 value: 1.0
-                                         // Desired value to avoid loop onMoved -> media.volume -> value -> onMoved...
-                                         property real desiredVolume: QtMultimedia.convertVolume(volumeSlider.value,
-                                         QtMultimedia.LogarithmicVolumeScale,
-                                         QtMultimedia.LinearVolumeScale)
-
-                                         anchors.fill: parent
-                                         anchors.bottomMargin: parent.height * 0.1
-                                         anchors.topMargin: parent.height * 0.1
-                                         anchors.horizontalCenter: parent.horizontalCenter
-                                         orientation: Qt.Vertical
-										 onDesiredVolumeChanged: {
-											 mxcmedia.muted = !(desiredVolume > 0.0)
-										 }
-                                      }
-                                      // Used for resetting the timer on mouse moves on volumeSliderRect
-                                      MouseArea {
-                                          id: volumeSliderRectMouseArea
-                                          anchors.fill: parent
-                                          hoverEnabled: true
-                                          propagateComposedEvents: true
-                                          onExited: volumeSliderHideTimer.start()
-
-                                          onClicked: mouse.accepted = false
-                                          onPressed: mouse.accepted = false
-                                          onReleased: mouse.accepted = false
-                                          onPressAndHold: mouse.accepted = false
-                                          onPositionChanged: {
-                                              mouse.accepted = false
-                                              volumeSliderHideTimer.start()
-                                          }
-                                      }
-                                  }
-                              }
+                            }
 
                           }
                       }
diff --git a/resources/qml/ui/media/VolumeControl.qml b/resources/qml/ui/media/VolumeControl.qml
new file mode 100644
index 000000000..b826dfc69
--- /dev/null
+++ b/resources/qml/ui/media/VolumeControl.qml
@@ -0,0 +1,105 @@
+// SPDX-FileCopyrightText: 2021 Nheko Contributors
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+import QtMultimedia 5.15
+import QtQuick 2.15
+import QtQuick.Controls 2.15
+
+import im.nheko 1.0
+
+// Volume slider activator
+Image {
+    property alias desiredVolume: volumeSlider.desiredVolume
+    property alias orientation: volumeSlider.orientation
+    property alias controlsVisible: volumeSliderRect.visible
+    property bool muted: false
+    property color controlColor: (volumeImageArea.containsMouse) ?
+    Nheko.colors.highlight : Nheko.colors.text
+
+    // TODO: add icons for different volume levels
+    id: volumeImage
+    source: (desiredVolume > 0 && !muted) ?
+    "image://colorimage/:/icons/icons/ui/volume-up.png?"+ controlColor :
+    "image://colorimage/:/icons/icons/ui/volume-off-indicator.png?"+ controlColor
+
+    fillMode: Image.PreserveAspectFit
+
+    MouseArea {
+        id: volumeImageArea	
+        anchors.fill: parent
+        hoverEnabled: true
+        onExited: volumeSliderHideTimer.start()
+        onPositionChanged: volumeSliderHideTimer.start()
+        onClicked: volumeImage.muted = !volumeImage.muted
+        // For hiding volume slider after a while
+        Timer {
+            id: volumeSliderHideTimer
+            interval: 1500
+            repeat: false
+            running: false
+        }
+    }
+    Rectangle {
+        id: volumeSliderRect
+        opacity: (visible) ? 1 : 0
+        Behavior on opacity {
+            OpacityAnimator {
+                duration: 100
+            }
+        }
+        // TODO: figure out a better way to put the slider popup above controlRect
+        anchors.bottom: volumeImage.top
+        anchors.bottomMargin: 10
+        anchors.horizontalCenter: volumeImage.horizontalCenter
+        color: {
+            var wc = Nheko.colors.window
+            return Qt.rgba(wc.r, wc.g, wc.b, 0.5)
+        }
+        /* TODO: base width on the slider width (some issue with it not having a geometry
+        when using the width here?) */
+        width: volumeImage.width * 0.7
+        radius: volumeSlider.width / 2
+        height: controlRect.height * 2 //100
+        visible: volumeImageArea.containsMouse ||
+        volumeSliderHideTimer.running ||
+        volumeSliderRectMouseArea.containsMouse
+        Slider {
+            // TODO: the slider is slightly off-center on the left for some reason...
+            id: volumeSlider
+
+            value: 1.0
+            // Desired value to avoid loop onMoved -> media.volume -> value -> onMoved...
+            property real desiredVolume: QtMultimedia.convertVolume(volumeSlider.value,
+            QtMultimedia.LogarithmicVolumeScale,
+            QtMultimedia.LinearVolumeScale)
+
+            anchors.fill: parent
+            anchors.bottomMargin: volumeSliderRect.height * 0.1
+            anchors.topMargin: volumeSliderRect.height * 0.1
+            anchors.horizontalCenter: volumeSliderRect.horizontalCenter
+            orientation: Qt.Vertical
+            onDesiredVolumeChanged: {
+                volumeImage.muted = !(desiredVolume > 0.0)
+            }
+
+        }
+        // Used for resetting the timer on mouse moves on volumeSliderRect
+        MouseArea {
+            id: volumeSliderRectMouseArea
+            anchors.fill: parent
+            hoverEnabled: true
+            propagateComposedEvents: true
+            onExited: volumeSliderHideTimer.start()
+
+            onClicked: mouse.accepted = false
+            onPressed: mouse.accepted = false
+            onReleased: mouse.accepted = false
+            onPressAndHold: mouse.accepted = false
+            onPositionChanged: {
+                mouse.accepted = false
+                volumeSliderHideTimer.start()
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/resources/qml/ui/media/qmldir b/resources/qml/ui/media/qmldir
new file mode 100644
index 000000000..14df35df4
--- /dev/null
+++ b/resources/qml/ui/media/qmldir
@@ -0,0 +1,2 @@
+module im.nheko.UI.Media
+VolumeSlider 1.0 VolumeSlider.qml
\ No newline at end of file
diff --git a/resources/res.qrc b/resources/res.qrc
index ccb5a6375..1e6a22fc3 100644
--- a/resources/res.qrc
+++ b/resources/res.qrc
@@ -183,6 +183,7 @@
         <file>qml/ui/Ripple.qml</file>
         <file>qml/ui/Spinner.qml</file>
         <file>qml/ui/animations/BlinkAnimation.qml</file>
+        <file>qml/ui/media/VolumeControl.qml</file>
         <file>qml/voip/ActiveCallBar.qml</file>
         <file>qml/voip/CallDevices.qml</file>
         <file>qml/voip/CallInvite.qml</file>
-- 
GitLab


From c1c9c71b08915a27538e34e422255d53b7bf1fdf Mon Sep 17 00:00:00 2001
From: Joseph Donofry <joedonofry@gmail.com>
Date: Tue, 9 Nov 2021 22:17:00 -0500
Subject: [PATCH 07/13] Move rest of controls to separate file

---
 .../qml/delegates/PlayableMediaMessage.qml    | 336 +++++++-----------
 resources/qml/ui/media/MediaControls.qml      | 141 ++++++++
 resources/qml/ui/media/VolumeControl.qml      |  67 ++--
 resources/qml/ui/media/qmldir                 |   3 +-
 resources/res.qrc                             |   1 +
 5 files changed, 311 insertions(+), 237 deletions(-)
 create mode 100644 resources/qml/ui/media/MediaControls.qml

diff --git a/resources/qml/delegates/PlayableMediaMessage.qml b/resources/qml/delegates/PlayableMediaMessage.qml
index ceeeeb1ae..3af3a9935 100644
--- a/resources/qml/delegates/PlayableMediaMessage.qml
+++ b/resources/qml/delegates/PlayableMediaMessage.qml
@@ -11,6 +11,8 @@ import QtQuick.Layouts 1.2
 import im.nheko 1.0
 
 ColumnLayout {
+    id: content
+
     required property double proportionalHeight
     required property int type
     required property int originalWidth
@@ -22,52 +24,51 @@ ColumnLayout {
 
     function durationToString(duration) {
         function maybeZeroPrepend(time) {
-            return (time < 10) ? "0" + time.toString() :
-            time.toString()
+            return (time < 10) ? "0" + time.toString() : time.toString();
         }
-        var totalSeconds = Math.floor(duration / 1000)
-        var seconds = totalSeconds % 60
-        var minutes = (Math.floor(totalSeconds / 60)) % 60
-        var hours = (Math.floor(totalSeconds / (60 * 24))) % 24
+
+        var totalSeconds = Math.floor(duration / 1000);
+        var seconds = totalSeconds % 60;
+        var minutes = (Math.floor(totalSeconds / 60)) % 60;
+        var hours = (Math.floor(totalSeconds / (60 * 24))) % 24;
         // Always show minutes and don't prepend zero into the leftmost element
-        var ss = maybeZeroPrepend(seconds)
-        var mm = (hours > 0) ? maybeZeroPrepend(minutes) : minutes.toString()
-        var hh = hours.toString()
-
-        if (hours < 1) {
-        	return mm + ":" + ss
-		}
-        return hh + ":" + mm + ":" + ss
-    }
+        var ss = maybeZeroPrepend(seconds);
+        var mm = (hours > 0) ? maybeZeroPrepend(minutes) : minutes.toString();
+        var hh = hours.toString();
+        if (hours < 1)
+            return mm + ":" + ss;
 
-    id: content
+        return hh + ":" + mm + ":" + ss;
+    }
 
     Layout.fillWidth: true
+
     MxcMedia {
         id: mxcmedia
+
         // TODO: Show error in overlay or so?
         onError: console.log(error)
         roomm: room
-		// desiredVolume is a float from 0.0 -> 1.0, MediaPlayer volume is an int from 0 to 100
-		// this value automatically gets clamped for us between these two values.
-		volume: volumeSlider.desiredVolume * 100
-        muted: volumeSlider.muted
+        // desiredVolume is a float from 0.0 -> 1.0, MediaPlayer volume is an int from 0 to 100
+        // this value automatically gets clamped for us between these two values.
+        volume: mediaControls.desiredVolume * 100
+        muted: mediaControls.muted
     }
 
     Rectangle {
         id: videoContainer
-        visible: type == MtxEvent.VideoMessage
+
         //property double tempWidth: Math.min(parent ? parent.width : undefined, model.data.width < 1 ? 400 : /////model.data.width)
         // property double tempWidth: (model.data.width < 1) ? 400 : model.data.width
         // property double tempHeight: tempWidth * model.data.proportionalHeight
         //property double tempWidth: Math.min(parent ? parent.width : undefined, originalWidth < 1 ? 400 : originalWidth)
-        property double tempWidth: Math.min(parent ? parent.width: undefined, originalWidth < 1 ? 400 : originalWidth)
+        property double tempWidth: Math.min(parent ? parent.width : undefined, originalWidth < 1 ? 400 : originalWidth)
         property double tempHeight: tempWidth * proportionalHeight
-
         property double divisor: isReply ? 4 : 2
         property bool tooHigh: tempHeight > timelineRoot.height / divisor
-        color: Nheko.colors.window
 
+        visible: type == MtxEvent.VideoMessage
+        color: Nheko.colors.window
         Layout.preferredHeight: tooHigh ? timelineRoot.height / divisor : tempHeight
         Layout.preferredWidth: tooHigh ? (timelineRoot.height / divisor) / proportionalHeight : tempWidth
         Layout.maximumWidth: Layout.preferredWidth
@@ -77,217 +78,144 @@ ColumnLayout {
             source: thumbnailUrl.replace("mxc://", "image://MxcImage/")
             asynchronous: true
             fillMode: Image.PreserveAspectFit
+
             // Button and window colored overlay to cache media
             Item {
                 // Display over video controls
                 z: videoOutput.z + 1
                 visible: !mxcmedia.loaded
                 anchors.fill: parent
+
                 //color: Nheko.colors.window
                 //opacity: 0.5
                 Image {
-                    property color buttonColor: (cacheVideoArea.containsMouse) ? Nheko.colors.highlight :
-                    Nheko.colors.text
+                    property color buttonColor: (cacheVideoArea.containsMouse) ? Nheko.colors.highlight : Nheko.colors.text
 
                     anchors.verticalCenter: parent.verticalCenter
                     anchors.horizontalCenter: parent.horizontalCenter
-                    source: "image://colorimage/:/icons/icons/ui/arrow-pointing-down.png?"+buttonColor						
+                    source: "image://colorimage/:/icons/icons/ui/arrow-pointing-down.png?" + buttonColor
                 }
+
                 MouseArea {
                     id: cacheVideoArea
+
                     anchors.fill: parent
                     hoverEnabled: true
                     enabled: !mxcmedia.loaded
                     onClicked: mxcmedia.eventId = eventId
                 }
+
             }
+
             VideoOutput {
                 id: videoOutput
+
                 clip: true
                 anchors.fill: parent
                 fillMode: VideoOutput.PreserveAspectFit
                 source: mxcmedia
                 flushMode: VideoOutput.FirstFrame
 
-                // TODO: once we can use Qt 5.12, use HoverHandler
+                MediaControls {
+                    id: mediaControls
+
+                    anchors.fill: parent
+                    x: videoOutput.contentRect.x
+                    y: videoOutput.contentRect.y
+                    width: videoOutput.contentRect.width
+                    height: videoOutput.contentRect.height
+                    positionValue: mxcmedia.position
+                    duration: mxcmedia.duration
+                    mediaLoaded: mxcmedia.loaded
+                    mediaState: mxcmedia.state
+                    volumeOrientation: Qt.Vertical
+                    onPositionChanged: mxcmedia.position = position
+                    onActivated: mxcmedia.state == MediaPlayer.PlayingState ? mxcmedia.pause() : mxcmedia.play()
+                }
+
+            }
+
+        }
+
+    }
+    // Audio player
+
+    // TODO: share code with the video player
+    Rectangle {
+        id: audioControlRect
+
+        property int controlHeight: 25
+
+        visible: type != MtxEvent.VideoMessage
+        Layout.preferredHeight: 40
+
+        RowLayout {
+            anchors.fill: parent
+            width: parent.width
+
+            // Play/pause button
+            Image {
+                id: audioPlaybackStateImage
+
+                property color controlColor: (audioPlaybackStateArea.containsMouse) ? Nheko.colors.highlight : Nheko.colors.text
+
+                fillMode: Image.PreserveAspectFit
+                Layout.preferredHeight: controlRect.controlHeight
+                Layout.alignment: Qt.AlignVCenter
+                source: {
+                    if (!mxcmedia.loaded)
+                        return "image://colorimage/:/icons/icons/ui/arrow-pointing-down.png?" + controlColor;
+
+                    return (mxcmedia.state == MediaPlayer.PlayingState) ? "image://colorimage/:/icons/icons/ui/pause-symbol.png?" + controlColor : "image://colorimage/:/icons/icons/ui/play-sign.png?" + controlColor;
+                }
+
                 MouseArea {
-                    id: playerMouseArea
-                    // Toggle play state on clicks
+                    id: audioPlaybackStateArea
+
+                    anchors.fill: parent
+                    hoverEnabled: true
                     onClicked: {
-                        if (controlRect.shouldShowControls &&
-                        !controlRect.contains(mapToItem(controlRect, mouseX, mouseY))) {
-                            (mxcmedia.state == MediaPlayer.PlayingState) ?
-                            mxcmedia.pause() :
-                            mxcmedia.play()
+                        if (!mxcmedia.loaded) {
+                            mxcmedia.eventId = eventId;
+                            return ;
                         }
+                        (mxcmedia.state == MediaPlayer.PlayingState) ? mxcmedia.pause() : mxcmedia.play();
                     }
-                    Rectangle {
-                        id: controlRect
-                        property int controlHeight: 25
-                        property bool shouldShowControls: playerMouseArea.shouldShowControls ||
-                        volumeSlider.controlsVisible
-
-                        anchors.bottom: playerMouseArea.bottom
-                        // Window color with 128/255 alpha
-                        color: {
-                            var wc = Nheko.colors.alternateBase
-                            return Qt.rgba(wc.r, wc.g, wc.b, 0.5)
-                        }
-                        height: 40
-                        width: playerMouseArea.width
-                        opacity: shouldShowControls ? 1 : 0
-                        // Fade controls in/out
-                        Behavior on opacity {
-                            OpacityAnimator {
-                                duration: 100
-                            }
-                        }
+                }
+
+            }
+
+            Label {
+                text: (!mxcmedia.loaded) ? "-/-" : durationToString(mxcmedia.position) + "/" + durationToString(mxcmedia.duration)
+            }
+
+            Slider {
+                Layout.fillWidth: true
+                Layout.minimumWidth: 50
+                height: controlRect.controlHeight
+                value: mxcmedia.position
+                onMoved: mxcmedia.position = value
+                from: 0
+                to: mxcmedia.duration
+            }
+
+        }
+
+    }
+
+    Label {
+        id: fileInfoLabel
+
+        Layout.fillWidth: true
+        text: body + " [" + filesize + "]"
+        textFormat: Text.PlainText
+        elide: Text.ElideRight
+        color: Nheko.colors.text
+
+        background: Rectangle {
+            color: Nheko.colors.base
+        }
+
+    }
 
-                        RowLayout {
-                            anchors.fill: parent
-                            width: parent.width
-                            // Play/pause button
-                            Image {
-                                id: playbackStateImage
-                                fillMode: Image.PreserveAspectFit
-                                Layout.preferredHeight: controlRect.controlHeight
-                                Layout.alignment: Qt.AlignVCenter
-                                property color controlColor: (playbackStateArea.containsMouse) ?
-                                Nheko.colors.highlight : Nheko.colors.text
-
-                                source: (mxcmedia.state == MediaPlayer.PlayingState) ?
-                                "image://colorimage/:/icons/icons/ui/pause-symbol.png?"+controlColor :
-                                "image://colorimage/:/icons/icons/ui/play-sign.png?"+controlColor
-                                MouseArea {
-                                    id: playbackStateArea
-
-                                    anchors.fill: parent
-                                    hoverEnabled: true
-                                    onClicked: {
-                                        (mxcmedia.state == MediaPlayer.PlayingState) ?
-                                        mxcmedia.pause() :
-                                        mxcmedia.play()
-                                    }
-                                }
-                            }
-                            Label {
-                                text: (!mxcmedia.loaded) ? "-/-" : (durationToString(mxcmedia.position) + "/" + durationToString(mxcmedia.duration))
-                                color: Nheko.colors.text
-                            }
-
-                            Slider {
-                                Layout.fillWidth: true
-                                Layout.minimumWidth: 50
-                                height: controlRect.controlHeight
-                                value: mxcmedia.position
-                                onMoved: mxcmedia.position = value
-                                from: 0
-                                to: mxcmedia.duration
-                            }
-
-                            VolumeControl {
-								id: volumeSlider
-								orientation: Qt.Vertical
-                                Layout.rightMargin: 5
-                                Layout.preferredHeight: controlRect.controlHeight
-                            }
-
-                          }
-                      }
-                      // This breaks separation of concerns but this same thing doesn't work when called from controlRect...
-                      property bool shouldShowControls: (containsMouse && controlHideTimer.running) ||
-                      (mxcmedia.state != MediaPlayer.PlayingState) ||
-                      controlRect.contains(mapToItem(controlRect, mouseX, mouseY))
-
-                      // For hiding controls on stationary cursor
-                      Timer {
-                          id: controlHideTimer
-                          interval: 1500 //ms
-                          repeat: false
-                      }
-
-                      hoverEnabled: true
-                      onPositionChanged: controlHideTimer.start()
-
-                      x: videoOutput.contentRect.x
-                      y: videoOutput.contentRect.y
-                      width: videoOutput.contentRect.width
-                      height: videoOutput.contentRect.height
-                      propagateComposedEvents: true
-                  }
-              }
-          }
-      }
-      // Audio player
-      // TODO: share code with the video player
-      Rectangle {
-          id: audioControlRect
-
-          visible: type != MtxEvent.VideoMessage
-          property int controlHeight: 25
-          Layout.preferredHeight: 40
-          RowLayout {
-              anchors.fill: parent
-              width: parent.width
-              // Play/pause button
-              Image {
-                  id: audioPlaybackStateImage
-                  fillMode: Image.PreserveAspectFit
-                  Layout.preferredHeight: controlRect.controlHeight
-                  Layout.alignment: Qt.AlignVCenter
-                  property color controlColor: (audioPlaybackStateArea.containsMouse) ?
-                  Nheko.colors.highlight : Nheko.colors.text
-
-                  source: {
-                      if (!mxcmedia.loaded)
-                      return "image://colorimage/:/icons/icons/ui/arrow-pointing-down.png?"+controlColor
-                      return (mxcmedia.state == MediaPlayer.PlayingState) ?
-                      "image://colorimage/:/icons/icons/ui/pause-symbol.png?"+controlColor :
-                      "image://colorimage/:/icons/icons/ui/play-sign.png?"+controlColor
-                  }
-                  MouseArea {
-                      id: audioPlaybackStateArea
-
-                      anchors.fill: parent
-                      hoverEnabled: true
-                      onClicked: {
-                          if (!mxcmedia.loaded) {
-                              mxcmedia.eventId = eventId
-                              return
-                          }
-                          (mxcmedia.state == MediaPlayer.PlayingState) ?
-                          mxcmedia.pause() :
-                          mxcmedia.play()
-                      }
-                  }
-              }
-              Label {
-                  text: (!mxcmedia.loaded) ? "-/-" :
-                  durationToString(mxcmedia.position) + "/" + durationToString(mxcmedia.duration)
-              }
-
-              Slider {
-                  Layout.fillWidth: true
-                  Layout.minimumWidth: 50
-                  height: controlRect.controlHeight
-                  value: mxcmedia.position
-                  onMoved: mxcmedia.seek(value)
-                  from: 0
-                  to: mxcmedia.duration
-              }
-          }
-      }
-
-      Label {
-          id: fileInfoLabel
-
-          background: Rectangle {
-              color: Nheko.colors.base
-          }
-          Layout.fillWidth: true
-          text: body + " [" + filesize + "]"
-          textFormat: Text.PlainText
-          elide: Text.ElideRight
-          color: Nheko.colors.text
-      }
-  }
+}
diff --git a/resources/qml/ui/media/MediaControls.qml b/resources/qml/ui/media/MediaControls.qml
new file mode 100644
index 000000000..de3e98a75
--- /dev/null
+++ b/resources/qml/ui/media/MediaControls.qml
@@ -0,0 +1,141 @@
+// SPDX-FileCopyrightText: 2021 Nheko Contributors
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+import QtMultimedia 5.15
+import QtQuick 2.15
+import QtQuick.Controls 2.15
+import QtQuick.Layouts 1.2
+import im.nheko 1.0
+
+Item {
+    id: control
+
+    property alias desiredVolume: volumeSlider.desiredVolume
+    property alias muted: volumeSlider.muted
+    property alias volumeOrientation: volumeSlider.orientation
+    property var mediaState
+    property bool mediaLoaded: false
+    property var duration
+    property var positionValue: 0
+    property var position
+    property int controlHeight: 25
+    property bool shouldShowControls: playerMouseArea.shouldShowControls || volumeSlider.controlsVisible
+
+    signal activated(real mouseX, real mouseY)
+
+    function durationToString(duration) {
+        function maybeZeroPrepend(time) {
+            return (time < 10) ? "0" + time.toString() : time.toString();
+        }
+
+        var totalSeconds = Math.floor(duration / 1000);
+        var seconds = totalSeconds % 60;
+        var minutes = (Math.floor(totalSeconds / 60)) % 60;
+        var hours = (Math.floor(totalSeconds / (60 * 24))) % 24;
+        // Always show minutes and don't prepend zero into the leftmost element
+        var ss = maybeZeroPrepend(seconds);
+        var mm = (hours > 0) ? maybeZeroPrepend(minutes) : minutes.toString();
+        var hh = hours.toString();
+        if (hours < 1)
+            return mm + ":" + ss;
+
+        return hh + ":" + mm + ":" + ss;
+    }
+
+    MouseArea {
+        id: playerMouseArea
+
+        property bool shouldShowControls: (containsMouse && controlHideTimer.running) || (control.mediaState != MediaPlayer.PlayingState) || controlRect.contains(mapToItem(controlRect, mouseX, mouseY))
+
+        onClicked: control.activated(mouseX, mouseY)
+        hoverEnabled: true
+        onPositionChanged: controlHideTimer.start()
+        onExited: controlHideTimer.start()
+        onEntered: controlHideTimer.start()
+        anchors.fill: control
+        propagateComposedEvents: true
+    }
+
+    Rectangle {
+        id: controlRect
+
+        // Window color with 128/255 alpha
+        color: {
+            var wc = Nheko.colors.alternateBase;
+            return Qt.rgba(wc.r, wc.g, wc.b, 0.5);
+        }
+        anchors.bottom: control.bottom
+        anchors.left: control.left
+        anchors.right: control.right
+        height: 40
+        opacity: control.shouldShowControls ? 1 : 0
+
+        RowLayout {
+            anchors.fill: parent
+            width: parent.width
+
+            // Play/pause button
+            Image {
+                id: playbackStateImage
+
+                property color controlColor: (playbackStateArea.containsMouse) ? Nheko.colors.highlight : Nheko.colors.text
+
+                fillMode: Image.PreserveAspectFit
+                Layout.preferredHeight: control.controlHeight
+                Layout.alignment: Qt.AlignVCenter
+                source: (control.mediaState == MediaPlayer.PlayingState) ? "image://colorimage/:/icons/icons/ui/pause-symbol.png?" + controlColor : "image://colorimage/:/icons/icons/ui/play-sign.png?" + controlColor
+
+                MouseArea {
+                    id: playbackStateArea
+
+                    anchors.fill: parent
+                    hoverEnabled: true
+                    onClicked: control.activated(mouseX, mouseY)
+                }
+
+            }
+
+            Label {
+                text: (!control.mediaLoaded) ? "-/-" : (durationToString(control.positionValue) + "/" + durationToString(control.duration))
+                color: Nheko.colors.text
+            }
+
+            Slider {
+                Layout.fillWidth: true
+                Layout.minimumWidth: 50
+                height: control.controlHeight
+                value: control.positionValue
+                onMoved: control.position = value
+                from: 0
+                to: control.duration
+            }
+
+            VolumeControl {
+                id: volumeSlider
+
+                Layout.rightMargin: 5
+                Layout.preferredHeight: control.controlHeight
+            }
+
+        }
+
+        // Fade controls in/out
+        Behavior on opacity {
+            OpacityAnimator {
+                duration: 100
+            }
+
+        }
+
+    }
+
+    // For hiding controls on stationary cursor
+    Timer {
+        id: controlHideTimer
+
+        interval: 1500 //ms
+        repeat: false
+    }
+
+}
diff --git a/resources/qml/ui/media/VolumeControl.qml b/resources/qml/ui/media/VolumeControl.qml
index b826dfc69..cd844ed59 100644
--- a/resources/qml/ui/media/VolumeControl.qml
+++ b/resources/qml/ui/media/VolumeControl.qml
@@ -5,101 +5,104 @@
 import QtMultimedia 5.15
 import QtQuick 2.15
 import QtQuick.Controls 2.15
-
 import im.nheko 1.0
 
 // Volume slider activator
 Image {
+    // TODO: add icons for different volume levels
+    id: volumeImage
+
     property alias desiredVolume: volumeSlider.desiredVolume
     property alias orientation: volumeSlider.orientation
     property alias controlsVisible: volumeSliderRect.visible
     property bool muted: false
-    property color controlColor: (volumeImageArea.containsMouse) ?
-    Nheko.colors.highlight : Nheko.colors.text
-
-    // TODO: add icons for different volume levels
-    id: volumeImage
-    source: (desiredVolume > 0 && !muted) ?
-    "image://colorimage/:/icons/icons/ui/volume-up.png?"+ controlColor :
-    "image://colorimage/:/icons/icons/ui/volume-off-indicator.png?"+ controlColor
+    property color controlColor: (volumeImageArea.containsMouse) ? Nheko.colors.highlight : Nheko.colors.text
 
+    source: (desiredVolume > 0 && !muted) ? "image://colorimage/:/icons/icons/ui/volume-up.png?" + controlColor : "image://colorimage/:/icons/icons/ui/volume-off-indicator.png?" + controlColor
     fillMode: Image.PreserveAspectFit
 
     MouseArea {
-        id: volumeImageArea	
+        id: volumeImageArea
+
         anchors.fill: parent
         hoverEnabled: true
         onExited: volumeSliderHideTimer.start()
         onPositionChanged: volumeSliderHideTimer.start()
         onClicked: volumeImage.muted = !volumeImage.muted
+
         // For hiding volume slider after a while
         Timer {
             id: volumeSliderHideTimer
+
             interval: 1500
             repeat: false
             running: false
         }
+
     }
+
     Rectangle {
         id: volumeSliderRect
+
         opacity: (visible) ? 1 : 0
-        Behavior on opacity {
-            OpacityAnimator {
-                duration: 100
-            }
-        }
-        // TODO: figure out a better way to put the slider popup above controlRect
         anchors.bottom: volumeImage.top
         anchors.bottomMargin: 10
         anchors.horizontalCenter: volumeImage.horizontalCenter
         color: {
-            var wc = Nheko.colors.window
-            return Qt.rgba(wc.r, wc.g, wc.b, 0.5)
+            var wc = Nheko.colors.window;
+            return Qt.rgba(wc.r, wc.g, wc.b, 0.5);
         }
         /* TODO: base width on the slider width (some issue with it not having a geometry
         when using the width here?) */
         width: volumeImage.width * 0.7
         radius: volumeSlider.width / 2
         height: controlRect.height * 2 //100
-        visible: volumeImageArea.containsMouse ||
-        volumeSliderHideTimer.running ||
-        volumeSliderRectMouseArea.containsMouse
+        visible: volumeImageArea.containsMouse || volumeSliderHideTimer.running || volumeSliderRectMouseArea.containsMouse
+
         Slider {
             // TODO: the slider is slightly off-center on the left for some reason...
             id: volumeSlider
 
-            value: 1.0
             // Desired value to avoid loop onMoved -> media.volume -> value -> onMoved...
-            property real desiredVolume: QtMultimedia.convertVolume(volumeSlider.value,
-            QtMultimedia.LogarithmicVolumeScale,
-            QtMultimedia.LinearVolumeScale)
+            property real desiredVolume: QtMultimedia.convertVolume(volumeSlider.value, QtMultimedia.LogarithmicVolumeScale, QtMultimedia.LinearVolumeScale)
 
-            anchors.fill: parent
+            value: 1
+            anchors.fill: volumeSliderRect
             anchors.bottomMargin: volumeSliderRect.height * 0.1
             anchors.topMargin: volumeSliderRect.height * 0.1
             anchors.horizontalCenter: volumeSliderRect.horizontalCenter
             orientation: Qt.Vertical
             onDesiredVolumeChanged: {
-                volumeImage.muted = !(desiredVolume > 0.0)
+                volumeImage.muted = !(desiredVolume > 0);
             }
-
         }
         // Used for resetting the timer on mouse moves on volumeSliderRect
+
         MouseArea {
             id: volumeSliderRectMouseArea
+
             anchors.fill: parent
             hoverEnabled: true
             propagateComposedEvents: true
             onExited: volumeSliderHideTimer.start()
-
             onClicked: mouse.accepted = false
             onPressed: mouse.accepted = false
             onReleased: mouse.accepted = false
             onPressAndHold: mouse.accepted = false
             onPositionChanged: {
-                mouse.accepted = false
-                volumeSliderHideTimer.start()
+                mouse.accepted = false;
+                volumeSliderHideTimer.start();
             }
         }
+
+        Behavior on opacity {
+            OpacityAnimator {
+                duration: 100
+            }
+
+        }
+        // TODO: figure out a better way to put the slider popup above controlRect
+
     }
-}
\ No newline at end of file
+
+}
diff --git a/resources/qml/ui/media/qmldir b/resources/qml/ui/media/qmldir
index 14df35df4..143b603da 100644
--- a/resources/qml/ui/media/qmldir
+++ b/resources/qml/ui/media/qmldir
@@ -1,2 +1,3 @@
 module im.nheko.UI.Media
-VolumeSlider 1.0 VolumeSlider.qml
\ No newline at end of file
+VolumeSlider 1.0 VolumeSlider.qml
+MediaControls 1.0 MediaControls.qml
\ No newline at end of file
diff --git a/resources/res.qrc b/resources/res.qrc
index 1e6a22fc3..538095abb 100644
--- a/resources/res.qrc
+++ b/resources/res.qrc
@@ -183,6 +183,7 @@
         <file>qml/ui/Ripple.qml</file>
         <file>qml/ui/Spinner.qml</file>
         <file>qml/ui/animations/BlinkAnimation.qml</file>
+        <file>qml/ui/media/MediaControls.qml</file>
         <file>qml/ui/media/VolumeControl.qml</file>
         <file>qml/voip/ActiveCallBar.qml</file>
         <file>qml/voip/CallDevices.qml</file>
-- 
GitLab


From df17e4e28b92dcef5154043a0eadaa5c39aeba92 Mon Sep 17 00:00:00 2001
From: Joseph Donofry <joedonofry@gmail.com>
Date: Tue, 9 Nov 2021 22:33:16 -0500
Subject: [PATCH 08/13] Fix audio player and use same controls for video and
 audio

---
 .../qml/delegates/PlayableMediaMessage.qml    | 157 +++++++++---------
 1 file changed, 77 insertions(+), 80 deletions(-)

diff --git a/resources/qml/delegates/PlayableMediaMessage.qml b/resources/qml/delegates/PlayableMediaMessage.qml
index 3af3a9935..183452457 100644
--- a/resources/qml/delegates/PlayableMediaMessage.qml
+++ b/resources/qml/delegates/PlayableMediaMessage.qml
@@ -67,7 +67,6 @@ ColumnLayout {
         property double divisor: isReply ? 4 : 2
         property bool tooHigh: tempHeight > timelineRoot.height / divisor
 
-        visible: type == MtxEvent.VideoMessage
         color: Nheko.colors.window
         Layout.preferredHeight: tooHigh ? timelineRoot.height / divisor : tempHeight
         Layout.preferredWidth: tooHigh ? (timelineRoot.height / divisor) / proportionalHeight : tempWidth
@@ -109,99 +108,97 @@ ColumnLayout {
 
             VideoOutput {
                 id: videoOutput
-
+                visible: type == MtxEvent.VideoMessage
                 clip: true
                 anchors.fill: parent
                 fillMode: VideoOutput.PreserveAspectFit
                 source: mxcmedia
                 flushMode: VideoOutput.FirstFrame
+            }
 
-                MediaControls {
-                    id: mediaControls
-
-                    anchors.fill: parent
-                    x: videoOutput.contentRect.x
-                    y: videoOutput.contentRect.y
-                    width: videoOutput.contentRect.width
-                    height: videoOutput.contentRect.height
-                    positionValue: mxcmedia.position
-                    duration: mxcmedia.duration
-                    mediaLoaded: mxcmedia.loaded
-                    mediaState: mxcmedia.state
-                    volumeOrientation: Qt.Vertical
-                    onPositionChanged: mxcmedia.position = position
-                    onActivated: mxcmedia.state == MediaPlayer.PlayingState ? mxcmedia.pause() : mxcmedia.play()
-                }
+            MediaControls {
+                id: mediaControls
 
+                anchors.fill: parent
+                x: type == MtxEvent.VideoMessage ? videoOutput.contentRect.x : videoContainer.x
+                y: type == MtxEvent.VideoMessage ? videoOutput.contentRect.y : videoContainer.y
+                width: type == MtxEvent.VideoMessage ? videoOutput.contentRect.width : videoContainer.width
+                height: type == MtxEvent.VideoMessage ? videoOutput.contentRect.height : videoContainer.height
+                positionValue: mxcmedia.position
+                duration: mxcmedia.duration
+                mediaLoaded: mxcmedia.loaded
+                mediaState: mxcmedia.state
+                volumeOrientation: Qt.Vertical
+                onPositionChanged: mxcmedia.position = position
+                onActivated: mxcmedia.state == MediaPlayer.PlayingState ? mxcmedia.pause() : mxcmedia.play()
             }
 
         }
 
     }
     // Audio player
-
     // TODO: share code with the video player
-    Rectangle {
-        id: audioControlRect
-
-        property int controlHeight: 25
-
-        visible: type != MtxEvent.VideoMessage
-        Layout.preferredHeight: 40
-
-        RowLayout {
-            anchors.fill: parent
-            width: parent.width
-
-            // Play/pause button
-            Image {
-                id: audioPlaybackStateImage
-
-                property color controlColor: (audioPlaybackStateArea.containsMouse) ? Nheko.colors.highlight : Nheko.colors.text
-
-                fillMode: Image.PreserveAspectFit
-                Layout.preferredHeight: controlRect.controlHeight
-                Layout.alignment: Qt.AlignVCenter
-                source: {
-                    if (!mxcmedia.loaded)
-                        return "image://colorimage/:/icons/icons/ui/arrow-pointing-down.png?" + controlColor;
-
-                    return (mxcmedia.state == MediaPlayer.PlayingState) ? "image://colorimage/:/icons/icons/ui/pause-symbol.png?" + controlColor : "image://colorimage/:/icons/icons/ui/play-sign.png?" + controlColor;
-                }
-
-                MouseArea {
-                    id: audioPlaybackStateArea
-
-                    anchors.fill: parent
-                    hoverEnabled: true
-                    onClicked: {
-                        if (!mxcmedia.loaded) {
-                            mxcmedia.eventId = eventId;
-                            return ;
-                        }
-                        (mxcmedia.state == MediaPlayer.PlayingState) ? mxcmedia.pause() : mxcmedia.play();
-                    }
-                }
-
-            }
-
-            Label {
-                text: (!mxcmedia.loaded) ? "-/-" : durationToString(mxcmedia.position) + "/" + durationToString(mxcmedia.duration)
-            }
-
-            Slider {
-                Layout.fillWidth: true
-                Layout.minimumWidth: 50
-                height: controlRect.controlHeight
-                value: mxcmedia.position
-                onMoved: mxcmedia.position = value
-                from: 0
-                to: mxcmedia.duration
-            }
-
-        }
-
-    }
+    // Rectangle {
+    //     id: audioControlRect
+
+    //     property int controlHeight: 25
+
+    //     visible: type != MtxEvent.VideoMessage
+    //     Layout.preferredHeight: 40
+
+    //     RowLayout {
+    //         anchors.fill: parent
+    //         width: parent.width
+
+    //         // Play/pause button
+    //         Image {
+    //             id: audioPlaybackStateImage
+
+    //             property color controlColor: (audioPlaybackStateArea.containsMouse) ? Nheko.colors.highlight : Nheko.colors.text
+
+    //             fillMode: Image.PreserveAspectFit
+    //             Layout.preferredHeight: controlRect.controlHeight
+    //             Layout.alignment: Qt.AlignVCenter
+    //             source: {
+    //                 if (!mxcmedia.loaded)
+    //                     return "image://colorimage/:/icons/icons/ui/arrow-pointing-down.png?" + controlColor;
+
+    //                 return (mxcmedia.state == MediaPlayer.PlayingState) ? "image://colorimage/:/icons/icons/ui/pause-symbol.png?" + controlColor : "image://colorimage/:/icons/icons/ui/play-sign.png?" + controlColor;
+    //             }
+
+    //             MouseArea {
+    //                 id: audioPlaybackStateArea
+
+    //                 anchors.fill: parent
+    //                 hoverEnabled: true
+    //                 onClicked: {
+    //                     if (!mxcmedia.loaded) {
+    //                         mxcmedia.eventId = eventId;
+    //                         return ;
+    //                     }
+    //                     (mxcmedia.state == MediaPlayer.PlayingState) ? mxcmedia.pause() : mxcmedia.play();
+    //                 }
+    //             }
+
+    //         }
+
+    //         Label {
+    //             text: (!mxcmedia.loaded) ? "-/-" : durationToString(mxcmedia.position) + "/" + durationToString(mxcmedia.duration)
+    //         }
+
+    //         Slider {
+    //             Layout.fillWidth: true
+    //             Layout.minimumWidth: 50
+    //             height: controlRect.controlHeight
+    //             value: mxcmedia.position
+    //             onMoved: mxcmedia.position = value
+    //             from: 0
+    //             to: mxcmedia.duration
+    //         }
+
+    //     }
+
+    // }
 
     Label {
         id: fileInfoLabel
-- 
GitLab


From e3eb87cc214337dbf3e629706857a78c6bfda284 Mon Sep 17 00:00:00 2001
From: Joseph Donofry <joedonofry@gmail.com>
Date: Tue, 9 Nov 2021 23:52:59 -0500
Subject: [PATCH 09/13] More player fixes

---
 .../qml/delegates/PlayableMediaMessage.qml    | 124 +-----------------
 resources/qml/ui/media/MediaControls.qml      |  24 +++-
 2 files changed, 25 insertions(+), 123 deletions(-)

diff --git a/resources/qml/delegates/PlayableMediaMessage.qml b/resources/qml/delegates/PlayableMediaMessage.qml
index 183452457..e6bcdcacd 100644
--- a/resources/qml/delegates/PlayableMediaMessage.qml
+++ b/resources/qml/delegates/PlayableMediaMessage.qml
@@ -22,25 +22,6 @@ ColumnLayout {
     required property string body
     required property string filesize
 
-    function durationToString(duration) {
-        function maybeZeroPrepend(time) {
-            return (time < 10) ? "0" + time.toString() : time.toString();
-        }
-
-        var totalSeconds = Math.floor(duration / 1000);
-        var seconds = totalSeconds % 60;
-        var minutes = (Math.floor(totalSeconds / 60)) % 60;
-        var hours = (Math.floor(totalSeconds / (60 * 24))) % 24;
-        // Always show minutes and don't prepend zero into the leftmost element
-        var ss = maybeZeroPrepend(seconds);
-        var mm = (hours > 0) ? maybeZeroPrepend(minutes) : minutes.toString();
-        var hh = hours.toString();
-        if (hours < 1)
-            return mm + ":" + ss;
-
-        return hh + ":" + mm + ":" + ss;
-    }
-
     Layout.fillWidth: true
 
     MxcMedia {
@@ -58,19 +39,14 @@ ColumnLayout {
     Rectangle {
         id: videoContainer
 
-        //property double tempWidth: Math.min(parent ? parent.width : undefined, model.data.width < 1 ? 400 : /////model.data.width)
-        // property double tempWidth: (model.data.width < 1) ? 400 : model.data.width
-        // property double tempHeight: tempWidth * model.data.proportionalHeight
-        //property double tempWidth: Math.min(parent ? parent.width : undefined, originalWidth < 1 ? 400 : originalWidth)
         property double tempWidth: Math.min(parent ? parent.width : undefined, originalWidth < 1 ? 400 : originalWidth)
         property double tempHeight: tempWidth * proportionalHeight
         property double divisor: isReply ? 4 : 2
         property bool tooHigh: tempHeight > timelineRoot.height / divisor
 
-        color: Nheko.colors.window
-        Layout.preferredHeight: tooHigh ? timelineRoot.height / divisor : tempHeight
+        color: type == MtxEvent.VideoMessage ? Nheko.colors.window : "transparent"
+        Layout.preferredHeight: type == MtxEvent.VideoMessage ? tooHigh ? timelineRoot.height / divisor : tempHeight : 40
         Layout.preferredWidth: tooHigh ? (timelineRoot.height / divisor) / proportionalHeight : tempWidth
-        Layout.maximumWidth: Layout.preferredWidth
 
         Image {
             anchors.fill: parent
@@ -78,36 +54,9 @@ ColumnLayout {
             asynchronous: true
             fillMode: Image.PreserveAspectFit
 
-            // Button and window colored overlay to cache media
-            Item {
-                // Display over video controls
-                z: videoOutput.z + 1
-                visible: !mxcmedia.loaded
-                anchors.fill: parent
-
-                //color: Nheko.colors.window
-                //opacity: 0.5
-                Image {
-                    property color buttonColor: (cacheVideoArea.containsMouse) ? Nheko.colors.highlight : Nheko.colors.text
-
-                    anchors.verticalCenter: parent.verticalCenter
-                    anchors.horizontalCenter: parent.horizontalCenter
-                    source: "image://colorimage/:/icons/icons/ui/arrow-pointing-down.png?" + buttonColor
-                }
-
-                MouseArea {
-                    id: cacheVideoArea
-
-                    anchors.fill: parent
-                    hoverEnabled: true
-                    enabled: !mxcmedia.loaded
-                    onClicked: mxcmedia.eventId = eventId
-                }
-
-            }
-
             VideoOutput {
                 id: videoOutput
+
                 visible: type == MtxEvent.VideoMessage
                 clip: true
                 anchors.fill: parent
@@ -130,76 +79,15 @@ ColumnLayout {
                 mediaState: mxcmedia.state
                 volumeOrientation: Qt.Vertical
                 onPositionChanged: mxcmedia.position = position
-                onActivated: mxcmedia.state == MediaPlayer.PlayingState ? mxcmedia.pause() : mxcmedia.play()
+                onPlayPauseActivated: mxcmedia.state == MediaPlayer.PlayingState ? mxcmedia.pause() : mxcmedia.play()
+                onLoadActivated: mxcmedia.eventId = eventId
             }
 
         }
 
     }
-    // Audio player
-    // TODO: share code with the video player
-    // Rectangle {
-    //     id: audioControlRect
-
-    //     property int controlHeight: 25
-
-    //     visible: type != MtxEvent.VideoMessage
-    //     Layout.preferredHeight: 40
-
-    //     RowLayout {
-    //         anchors.fill: parent
-    //         width: parent.width
-
-    //         // Play/pause button
-    //         Image {
-    //             id: audioPlaybackStateImage
-
-    //             property color controlColor: (audioPlaybackStateArea.containsMouse) ? Nheko.colors.highlight : Nheko.colors.text
-
-    //             fillMode: Image.PreserveAspectFit
-    //             Layout.preferredHeight: controlRect.controlHeight
-    //             Layout.alignment: Qt.AlignVCenter
-    //             source: {
-    //                 if (!mxcmedia.loaded)
-    //                     return "image://colorimage/:/icons/icons/ui/arrow-pointing-down.png?" + controlColor;
-
-    //                 return (mxcmedia.state == MediaPlayer.PlayingState) ? "image://colorimage/:/icons/icons/ui/pause-symbol.png?" + controlColor : "image://colorimage/:/icons/icons/ui/play-sign.png?" + controlColor;
-    //             }
-
-    //             MouseArea {
-    //                 id: audioPlaybackStateArea
-
-    //                 anchors.fill: parent
-    //                 hoverEnabled: true
-    //                 onClicked: {
-    //                     if (!mxcmedia.loaded) {
-    //                         mxcmedia.eventId = eventId;
-    //                         return ;
-    //                     }
-    //                     (mxcmedia.state == MediaPlayer.PlayingState) ? mxcmedia.pause() : mxcmedia.play();
-    //                 }
-    //             }
-
-    //         }
-
-    //         Label {
-    //             text: (!mxcmedia.loaded) ? "-/-" : durationToString(mxcmedia.position) + "/" + durationToString(mxcmedia.duration)
-    //         }
-
-    //         Slider {
-    //             Layout.fillWidth: true
-    //             Layout.minimumWidth: 50
-    //             height: controlRect.controlHeight
-    //             value: mxcmedia.position
-    //             onMoved: mxcmedia.position = value
-    //             from: 0
-    //             to: mxcmedia.duration
-    //         }
-
-    //     }
-
-    // }
 
+    // information about file name and file size
     Label {
         id: fileInfoLabel
 
diff --git a/resources/qml/ui/media/MediaControls.qml b/resources/qml/ui/media/MediaControls.qml
index de3e98a75..ec5223918 100644
--- a/resources/qml/ui/media/MediaControls.qml
+++ b/resources/qml/ui/media/MediaControls.qml
@@ -22,7 +22,8 @@ Item {
     property int controlHeight: 25
     property bool shouldShowControls: playerMouseArea.shouldShowControls || volumeSlider.controlsVisible
 
-    signal activated(real mouseX, real mouseY)
+    signal playPauseActivated(real mouseX, real mouseY)
+    signal loadActivated(real mouseX, real mouseY)
 
     function durationToString(duration) {
         function maybeZeroPrepend(time) {
@@ -48,7 +49,9 @@ Item {
 
         property bool shouldShowControls: (containsMouse && controlHideTimer.running) || (control.mediaState != MediaPlayer.PlayingState) || controlRect.contains(mapToItem(controlRect, mouseX, mouseY))
 
-        onClicked: control.activated(mouseX, mouseY)
+        onClicked: {
+            control.mediaLoaded ? control.playPauseActivated(mouseX, mouseY) : control.loadActivated(mouseX, mouseY);
+        }
         hoverEnabled: true
         onPositionChanged: controlHideTimer.start()
         onExited: controlHideTimer.start()
@@ -75,7 +78,7 @@ Item {
             anchors.fill: parent
             width: parent.width
 
-            // Play/pause button
+            // Cache/Play/pause button
             Image {
                 id: playbackStateImage
 
@@ -84,14 +87,25 @@ Item {
                 fillMode: Image.PreserveAspectFit
                 Layout.preferredHeight: control.controlHeight
                 Layout.alignment: Qt.AlignVCenter
-                source: (control.mediaState == MediaPlayer.PlayingState) ? "image://colorimage/:/icons/icons/ui/pause-symbol.png?" + controlColor : "image://colorimage/:/icons/icons/ui/play-sign.png?" + controlColor
+                source: {
+                    if (control.mediaLoaded) {
+                        if (control.mediaState == MediaPlayer.PlayingState)
+                            return "image://colorimage/:/icons/icons/ui/pause-symbol.png?" + controlColor;
+                        else
+                            return "image://colorimage/:/icons/icons/ui/play-sign.png?" + controlColor;
+                    } else {
+                        return "image://colorimage/:/icons/icons/ui/arrow-pointing-down.png?" + controlColor;
+                    }
+                }
 
                 MouseArea {
                     id: playbackStateArea
 
                     anchors.fill: parent
                     hoverEnabled: true
-                    onClicked: control.activated(mouseX, mouseY)
+                    onClicked: {
+                        control.mediaLoaded ? control.playPauseActivated(mouseX, mouseY) : control.loadActivated(mouseX, mouseY);
+                    }
                 }
 
             }
-- 
GitLab


From c5e8b2da15a65622d1783c19afeeb0ad4aebc4b8 Mon Sep 17 00:00:00 2001
From: Joseph Donofry <joedonofry@gmail.com>
Date: Thu, 11 Nov 2021 00:16:25 -0500
Subject: [PATCH 10/13] More refactoring and layout updates

---
 .../qml/delegates/PlayableMediaMessage.qml    |   8 +-
 resources/qml/ui/NhekoSlider.qml              |  77 +++++++++
 resources/qml/ui/media/MediaControls.qml      | 146 +++++++++++-------
 resources/qml/ui/media/VolumeControl.qml      |  33 ++--
 resources/qml/ui/qmldir                       |   1 +
 resources/res.qrc                             |   1 +
 6 files changed, 191 insertions(+), 75 deletions(-)
 create mode 100644 resources/qml/ui/NhekoSlider.qml

diff --git a/resources/qml/delegates/PlayableMediaMessage.qml b/resources/qml/delegates/PlayableMediaMessage.qml
index e6bcdcacd..2b4c4d49e 100644
--- a/resources/qml/delegates/PlayableMediaMessage.qml
+++ b/resources/qml/delegates/PlayableMediaMessage.qml
@@ -7,7 +7,7 @@ import "../ui/media"
 import QtMultimedia 5.15
 import QtQuick 2.15
 import QtQuick.Controls 2.15
-import QtQuick.Layouts 1.2
+import QtQuick.Layouts 1.15
 import im.nheko 1.0
 
 ColumnLayout {
@@ -45,8 +45,8 @@ ColumnLayout {
         property bool tooHigh: tempHeight > timelineRoot.height / divisor
 
         color: type == MtxEvent.VideoMessage ? Nheko.colors.window : "transparent"
-        Layout.preferredHeight: type == MtxEvent.VideoMessage ? tooHigh ? timelineRoot.height / divisor : tempHeight : 40
-        Layout.preferredWidth: tooHigh ? (timelineRoot.height / divisor) / proportionalHeight : tempWidth
+        Layout.preferredHeight: type == MtxEvent.VideoMessage ? tooHigh ? timelineRoot.height / divisor : tempHeight : 80
+        Layout.preferredWidth: type == MtxEvent.VideoMessage ? tooHigh ? (timelineRoot.height / divisor) / proportionalHeight : tempWidth : 250
 
         Image {
             anchors.fill: parent
@@ -73,11 +73,11 @@ ColumnLayout {
                 y: type == MtxEvent.VideoMessage ? videoOutput.contentRect.y : videoContainer.y
                 width: type == MtxEvent.VideoMessage ? videoOutput.contentRect.width : videoContainer.width
                 height: type == MtxEvent.VideoMessage ? videoOutput.contentRect.height : videoContainer.height
+                playingVideo: type == MtxEvent.VideoMessage
                 positionValue: mxcmedia.position
                 duration: mxcmedia.duration
                 mediaLoaded: mxcmedia.loaded
                 mediaState: mxcmedia.state
-                volumeOrientation: Qt.Vertical
                 onPositionChanged: mxcmedia.position = position
                 onPlayPauseActivated: mxcmedia.state == MediaPlayer.PlayingState ? mxcmedia.pause() : mxcmedia.play()
                 onLoadActivated: mxcmedia.eventId = eventId
diff --git a/resources/qml/ui/NhekoSlider.qml b/resources/qml/ui/NhekoSlider.qml
new file mode 100644
index 000000000..887cb80c1
--- /dev/null
+++ b/resources/qml/ui/NhekoSlider.qml
@@ -0,0 +1,77 @@
+// SPDX-FileCopyrightText: 2021 Nheko Contributors
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+import QtQuick 2.15
+import QtQuick.Controls 2.15
+import im.nheko 1.0
+
+Slider {
+    id: slider
+
+    property real sliderWidth
+    property real sliderHeight
+    property bool alwaysShowSlider: true
+
+    anchors.bottomMargin: orientation == Qt.Vertical ? Nheko.paddingMedium : undefined
+    anchors.topMargin: orientation == Qt.Vertical ? Nheko.paddingMedium : undefined
+    anchors.leftMargin: orientation == Qt.Vertical ? undefined : Nheko.paddingMedium
+    anchors.rightMargin: orientation == Qt.Vertical ? undefined : Nheko.paddingMedium
+
+    background: Rectangle {
+        x: slider.leftPadding + (slider.orientation == Qt.Vertical ? slider.availableWidth / 2 - width / 2 : 0)
+        y: slider.topPadding + (slider.orientation == Qt.Vertical ? 0 : slider.availableHeight / 2 - height / 2)
+        // implicitWidth: slider.orientation == Qt.Vertical ? 8 : 100
+        // implicitHeight: slider.orientation == Qt.Vertical ? 100 : 8
+        width: slider.orientation == Qt.Vertical ? sliderWidth : slider.availableWidth
+        height: slider.orientation == Qt.Vertical ? slider.availableHeight : sliderHeight
+        radius: 2
+        color: {
+            if (slider.orientation == Qt.Vertical) {
+                return Nheko.colors.highlight;
+            } else {
+                var col = Nheko.colors.buttonText;
+                return Qt.rgba(col.r, col.g, col.b, 0.5);
+            }
+        }
+        border.color: {
+            var col = Nheko.colors.base;
+            return Qt.rgba(col.r, col.g, col.b, 0.5);
+        }
+
+        Rectangle {
+            width: slider.orientation == Qt.Vertical ? parent.width : slider.visualPosition * parent.width
+            height: slider.orientation == Qt.Vertical ? slider.visualPosition * parent.height : parent.height
+            color: {
+                if (slider.orientation == Qt.Vertical) {
+                    return Nheko.colors.buttonText;
+                } else {
+                    return Nheko.colors.highlight;
+                }
+            }
+            radius: 2
+        }
+
+    }
+
+    handle: Rectangle {
+        x: {
+            if (slider.orientation == Qt.Vertical)
+                return slider.leftPadding + slider.availableWidth / 2 - width / 2;
+            else
+                return slider.leftPadding + slider.visualPosition * (slider.availableWidth - width);
+        }
+        y: {
+            if (slider.orientation == Qt.Vertical)
+                return slider.topPadding + slider.visualPosition * (slider.availableHeight - height);
+            else
+                return slider.topPadding + slider.availableHeight / 2 - height / 2;
+        }
+        implicitWidth: 16
+        implicitHeight: 16
+        radius: slider.width / 2
+        color: Nheko.colors.highlight
+        visible:  alwaysShowSlider || slider.hovered || slider.pressed || Settings.mobileMode
+    }
+
+}
diff --git a/resources/qml/ui/media/MediaControls.qml b/resources/qml/ui/media/MediaControls.qml
index ec5223918..b529462da 100644
--- a/resources/qml/ui/media/MediaControls.qml
+++ b/resources/qml/ui/media/MediaControls.qml
@@ -2,10 +2,11 @@
 //
 // SPDX-License-Identifier: GPL-3.0-or-later
 
+import "../"
 import QtMultimedia 5.15
 import QtQuick 2.15
 import QtQuick.Controls 2.15
-import QtQuick.Layouts 1.2
+import QtQuick.Layouts 1.15
 import im.nheko 1.0
 
 Item {
@@ -13,14 +14,14 @@ Item {
 
     property alias desiredVolume: volumeSlider.desiredVolume
     property alias muted: volumeSlider.muted
-    property alias volumeOrientation: volumeSlider.orientation
+    property bool playingVideo: false
     property var mediaState
     property bool mediaLoaded: false
     property var duration
     property var positionValue: 0
     property var position
     property int controlHeight: 25
-    property bool shouldShowControls: playerMouseArea.shouldShowControls || volumeSlider.controlsVisible
+    property bool shouldShowControls: !playingVideo || playerMouseArea.shouldShowControls || volumeSlider.controlsVisible
 
     signal playPauseActivated(real mouseX, real mouseY)
     signal loadActivated(real mouseX, real mouseY)
@@ -47,7 +48,7 @@ Item {
     MouseArea {
         id: playerMouseArea
 
-        property bool shouldShowControls: (containsMouse && controlHideTimer.running) || (control.mediaState != MediaPlayer.PlayingState) || controlRect.contains(mapToItem(controlRect, mouseX, mouseY))
+        property bool shouldShowControls: (containsMouse && controlHideTimer.running) || (control.mediaState != MediaPlayer.PlayingState) || controlLayout.contains(mapToItem(controlLayout, mouseX, mouseY))
 
         onClicked: {
             control.mediaLoaded ? control.playPauseActivated(mouseX, mouseY) : control.loadActivated(mouseX, mouseY);
@@ -60,76 +61,103 @@ Item {
         propagateComposedEvents: true
     }
 
-    Rectangle {
-        id: controlRect
+    ColumnLayout {
 
-        // Window color with 128/255 alpha
-        color: {
-            var wc = Nheko.colors.alternateBase;
-            return Qt.rgba(wc.r, wc.g, wc.b, 0.5);
-        }
+        id: controlLayout
+        opacity: control.shouldShowControls ? 1 : 0
+
+        // spacing: Nheko.paddingSmall
         anchors.bottom: control.bottom
         anchors.left: control.left
         anchors.right: control.right
-        height: 40
-        opacity: control.shouldShowControls ? 1 : 0
 
-        RowLayout {
-            anchors.fill: parent
-            width: parent.width
-
-            // Cache/Play/pause button
-            Image {
-                id: playbackStateImage
-
-                property color controlColor: (playbackStateArea.containsMouse) ? Nheko.colors.highlight : Nheko.colors.text
-
-                fillMode: Image.PreserveAspectFit
-                Layout.preferredHeight: control.controlHeight
-                Layout.alignment: Qt.AlignVCenter
-                source: {
-                    if (control.mediaLoaded) {
-                        if (control.mediaState == MediaPlayer.PlayingState)
-                            return "image://colorimage/:/icons/icons/ui/pause-symbol.png?" + controlColor;
-                        else
-                            return "image://colorimage/:/icons/icons/ui/play-sign.png?" + controlColor;
-                    } else {
-                        return "image://colorimage/:/icons/icons/ui/arrow-pointing-down.png?" + controlColor;
+        NhekoSlider {
+            Layout.fillWidth: true
+            Layout.minimumWidth: 50
+            Layout.leftMargin: Nheko.paddingMedium
+            Layout.rightMargin: Nheko.paddingMedium
+            height: control.controlHeight
+            value: control.positionValue
+            onMoved: control.position = value
+            from: 0
+            to: control.duration
+            sliderHeight: 8
+            alwaysShowSlider: false
+        }
+
+        Rectangle {
+            id: controlRect
+
+            // Window color with 128/255 alpha
+            color: {
+                var wc = Nheko.colors.alternateBase;
+                return Qt.rgba(wc.r, wc.g, wc.b, 0.5);
+            }
+
+            Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom
+
+            height: 35
+            Layout.fillWidth: true
+
+            RowLayout {
+                anchors.left: controlRect.left
+                anchors.bottom: controlRect.bottom
+                anchors.right: controlRect.right
+                anchors.margins: Nheko.paddingSmall
+                anchors.verticalCenter: controlRect.verticalCenter
+                spacing: Nheko.paddingSmall
+
+                // Cache/Play/pause button
+                Image {
+                    Layout.alignment: Qt.AlignLeft
+                    id: playbackStateImage
+
+                    property color controlColor: (playbackStateArea.containsMouse) ? Nheko.colors.highlight : Nheko.colors.text
+
+                    fillMode: Image.PreserveAspectFit
+                    Layout.preferredHeight: control.controlHeight
+                    source: {
+                        if (control.mediaLoaded) {
+                            if (control.mediaState == MediaPlayer.PlayingState)
+                                return "image://colorimage/:/icons/icons/ui/pause-symbol.png?" + controlColor;
+                            else
+                                return "image://colorimage/:/icons/icons/ui/play-sign.png?" + controlColor;
+                        } else {
+                            return "image://colorimage/:/icons/icons/ui/arrow-pointing-down.png?" + controlColor;
+                        }
                     }
-                }
 
-                MouseArea {
-                    id: playbackStateArea
+                    MouseArea {
+                        id: playbackStateArea
 
-                    anchors.fill: parent
-                    hoverEnabled: true
-                    onClicked: {
-                        control.mediaLoaded ? control.playPauseActivated(mouseX, mouseY) : control.loadActivated(mouseX, mouseY);
+                        anchors.fill: parent
+                        hoverEnabled: true
+                        onClicked: {
+                            control.mediaLoaded ? control.playPauseActivated(mouseX, mouseY) : control.loadActivated(mouseX, mouseY);
+                        }
                     }
+
                 }
 
-            }
+                VolumeControl {
+                    Layout.alignment: Qt.AlignLeft
+                    id: volumeSlider
+                    orientation: Qt.Horizontal
+                    Layout.rightMargin: 5
+                    Layout.preferredHeight: control.controlHeight
+                }
 
-            Label {
-                text: (!control.mediaLoaded) ? "-/-" : (durationToString(control.positionValue) + "/" + durationToString(control.duration))
-                color: Nheko.colors.text
-            }
+                Label {
+                    Layout.alignment: Qt.AlignRight
 
-            Slider {
-                Layout.fillWidth: true
-                Layout.minimumWidth: 50
-                height: control.controlHeight
-                value: control.positionValue
-                onMoved: control.position = value
-                from: 0
-                to: control.duration
-            }
+                    text: (!control.mediaLoaded) ? "-/-" : (durationToString(control.positionValue) + "/" + durationToString(control.duration))
+                    color: Nheko.colors.text
+                }
 
-            VolumeControl {
-                id: volumeSlider
+                Item {
+                    Layout.fillWidth: true
+                }
 
-                Layout.rightMargin: 5
-                Layout.preferredHeight: control.controlHeight
             }
 
         }
diff --git a/resources/qml/ui/media/VolumeControl.qml b/resources/qml/ui/media/VolumeControl.qml
index cd844ed59..e87550ac4 100644
--- a/resources/qml/ui/media/VolumeControl.qml
+++ b/resources/qml/ui/media/VolumeControl.qml
@@ -2,9 +2,12 @@
 //
 // SPDX-License-Identifier: GPL-3.0-or-later
 
+import "../"
+
 import QtMultimedia 5.15
 import QtQuick 2.15
 import QtQuick.Controls 2.15
+
 import im.nheko 1.0
 
 // Volume slider activator
@@ -17,6 +20,7 @@ Image {
     property alias controlsVisible: volumeSliderRect.visible
     property bool muted: false
     property color controlColor: (volumeImageArea.containsMouse) ? Nheko.colors.highlight : Nheko.colors.text
+    width: sourceSize.width + volumeSliderRect.implicitWidth
 
     source: (desiredVolume > 0 && !muted) ? "image://colorimage/:/icons/icons/ui/volume-up.png?" + controlColor : "image://colorimage/:/icons/icons/ui/volume-off-indicator.png?" + controlColor
     fillMode: Image.PreserveAspectFit
@@ -45,32 +49,38 @@ Image {
         id: volumeSliderRect
 
         opacity: (visible) ? 1 : 0
-        anchors.bottom: volumeImage.top
-        anchors.bottomMargin: 10
-        anchors.horizontalCenter: volumeImage.horizontalCenter
+        anchors.bottom: volumeSlider.orientation == Qt.Vertical ? volumeImage.top : undefined
+        anchors.left: volumeSlider.orientation == Qt.Vertical ? undefined : volumeImage.right
+        anchors.horizontalCenter: volumeSlider.orientation == Qt.Vertical ? volumeImage.horizontalCenter : undefined
+        anchors.verticalCenter: volumeSlider.orientation == Qt.Vertical ? undefined : volumeImage.verticalCenter
         color: {
-            var wc = Nheko.colors.window;
-            return Qt.rgba(wc.r, wc.g, wc.b, 0.5);
+            if (volumeSlider.orientation == Qt.Vertical) {
+                var wc = Nheko.colors.window;
+                return Qt.rgba(wc.r, wc.g, wc.b, 0.5);
+            } else {
+                return "transparent";
+            }
         }
         /* TODO: base width on the slider width (some issue with it not having a geometry
         when using the width here?) */
-        width: volumeImage.width * 0.7
+        width: volumeSlider.orientation == Qt.Vertical ? volumeImage.width * 0.7 : 100
         radius: volumeSlider.width / 2
-        height: controlRect.height * 2 //100
+        height: volumeSlider.orientation == Qt.Vertical ? 100 : volumeImage.height * 0.7
         visible: volumeImageArea.containsMouse || volumeSliderHideTimer.running || volumeSliderRectMouseArea.containsMouse
 
-        Slider {
+        NhekoSlider {
             // TODO: the slider is slightly off-center on the left for some reason...
             id: volumeSlider
 
+            sliderWidth: 8
+            sliderHeight: 8
             // Desired value to avoid loop onMoved -> media.volume -> value -> onMoved...
             property real desiredVolume: QtMultimedia.convertVolume(volumeSlider.value, QtMultimedia.LogarithmicVolumeScale, QtMultimedia.LinearVolumeScale)
 
             value: 1
             anchors.fill: volumeSliderRect
-            anchors.bottomMargin: volumeSliderRect.height * 0.1
-            anchors.topMargin: volumeSliderRect.height * 0.1
-            anchors.horizontalCenter: volumeSliderRect.horizontalCenter
+            anchors.horizontalCenter: orientation == Qt.Vertical ? volumeSliderRect.horizontalCenter : undefined
+            anchors.verticalCenter: orientation == Qt.Vertical ? undefined : volumeSliderRect.verticalCenter
             orientation: Qt.Vertical
             onDesiredVolumeChanged: {
                 volumeImage.muted = !(desiredVolume > 0);
@@ -101,7 +111,6 @@ Image {
             }
 
         }
-        // TODO: figure out a better way to put the slider popup above controlRect
 
     }
 
diff --git a/resources/qml/ui/qmldir b/resources/qml/ui/qmldir
index 831a723dc..a2ce7514b 100644
--- a/resources/qml/ui/qmldir
+++ b/resources/qml/ui/qmldir
@@ -1,3 +1,4 @@
 module im.nheko.UI
+NhekoSlider 1.0 NhekoSlider.qml
 Ripple 1.0 Ripple.qml
 Spinner 1.0 Spinner.qml
\ No newline at end of file
diff --git a/resources/res.qrc b/resources/res.qrc
index 538095abb..4e243251c 100644
--- a/resources/res.qrc
+++ b/resources/res.qrc
@@ -180,6 +180,7 @@
         <file>qml/dialogs/UserProfile.qml</file>
         <file>qml/emoji/EmojiPicker.qml</file>
         <file>qml/emoji/StickerPicker.qml</file>
+        <file>qml/ui/NhekoSlider.qml</file>
         <file>qml/ui/Ripple.qml</file>
         <file>qml/ui/Spinner.qml</file>
         <file>qml/ui/animations/BlinkAnimation.qml</file>
-- 
GitLab


From ffc60180ded0c84387e3a3dbd81ab99ac8079edc Mon Sep 17 00:00:00 2001
From: Nicolas Werner <nicolas.werner@hotmail.de>
Date: Thu, 11 Nov 2021 19:18:45 +0100
Subject: [PATCH 11/13] Cleanup positioning of player elements

---
 .../qml/delegates/PlayableMediaMessage.qml    |  42 ++--
 resources/qml/ui/NhekoSlider.qml              |  79 +++----
 resources/qml/ui/media/MediaControls.qml      | 196 ++++++++++++------
 resources/qml/ui/media/VolumeControl.qml      | 117 -----------
 resources/res.qrc                             |   1 -
 5 files changed, 174 insertions(+), 261 deletions(-)
 delete mode 100644 resources/qml/ui/media/VolumeControl.qml

diff --git a/resources/qml/delegates/PlayableMediaMessage.qml b/resources/qml/delegates/PlayableMediaMessage.qml
index 2b4c4d49e..67214dd70 100644
--- a/resources/qml/delegates/PlayableMediaMessage.qml
+++ b/resources/qml/delegates/PlayableMediaMessage.qml
@@ -10,7 +10,7 @@ import QtQuick.Controls 2.15
 import QtQuick.Layouts 1.15
 import im.nheko 1.0
 
-ColumnLayout {
+Item {
     id: content
 
     required property double proportionalHeight
@@ -22,7 +22,13 @@ ColumnLayout {
     required property string body
     required property string filesize
 
-    Layout.fillWidth: true
+        property double tempWidth: Math.min(parent ? parent.width : undefined, originalWidth < 1 ? 400 : originalWidth)
+        property double tempHeight: tempWidth * proportionalHeight
+        property double divisor: isReply ? 4 : 2
+        property bool tooHigh: tempHeight > timelineRoot.height / divisor
+
+        height: (type == MtxEvent.VideoMessage ? tooHigh ? timelineRoot.height / divisor : tempHeight : 80) + fileInfoLabel.height
+        width: type == MtxEvent.VideoMessage ? tooHigh ? (timelineRoot.height / divisor) / proportionalHeight : tempWidth : 250
 
     MxcMedia {
         id: mxcmedia
@@ -38,15 +44,10 @@ ColumnLayout {
 
     Rectangle {
         id: videoContainer
-
-        property double tempWidth: Math.min(parent ? parent.width : undefined, originalWidth < 1 ? 400 : originalWidth)
-        property double tempHeight: tempWidth * proportionalHeight
-        property double divisor: isReply ? 4 : 2
-        property bool tooHigh: tempHeight > timelineRoot.height / divisor
-
         color: type == MtxEvent.VideoMessage ? Nheko.colors.window : "transparent"
-        Layout.preferredHeight: type == MtxEvent.VideoMessage ? tooHigh ? timelineRoot.height / divisor : tempHeight : 80
-        Layout.preferredWidth: type == MtxEvent.VideoMessage ? tooHigh ? (timelineRoot.height / divisor) / proportionalHeight : tempWidth : 250
+        width: parent.width
+        height: parent.height - fileInfoLabel.height
+
 
         Image {
             anchors.fill: parent
@@ -65,14 +66,18 @@ ColumnLayout {
                 flushMode: VideoOutput.FirstFrame
             }
 
+
+        }
+
+    }
+
             MediaControls {
                 id: mediaControls
 
-                anchors.fill: parent
-                x: type == MtxEvent.VideoMessage ? videoOutput.contentRect.x : videoContainer.x
-                y: type == MtxEvent.VideoMessage ? videoOutput.contentRect.y : videoContainer.y
-                width: type == MtxEvent.VideoMessage ? videoOutput.contentRect.width : videoContainer.width
-                height: type == MtxEvent.VideoMessage ? videoOutput.contentRect.height : videoContainer.height
+                anchors.left: content.left
+                anchors.right: content.right
+                anchors.bottom: fileInfoLabel.top
+
                 playingVideo: type == MtxEvent.VideoMessage
                 positionValue: mxcmedia.position
                 duration: mxcmedia.duration
@@ -83,15 +88,12 @@ ColumnLayout {
                 onLoadActivated: mxcmedia.eventId = eventId
             }
 
-        }
-
-    }
-
     // information about file name and file size
     Label {
         id: fileInfoLabel
 
-        Layout.fillWidth: true
+        anchors.bottom: content.bottom
+
         text: body + " [" + filesize + "]"
         textFormat: Text.PlainText
         elide: Text.ElideRight
diff --git a/resources/qml/ui/NhekoSlider.qml b/resources/qml/ui/NhekoSlider.qml
index 887cb80c1..6cf1fd2d7 100644
--- a/resources/qml/ui/NhekoSlider.qml
+++ b/resources/qml/ui/NhekoSlider.qml
@@ -7,71 +7,42 @@ import QtQuick.Controls 2.15
 import im.nheko 1.0
 
 Slider {
-    id: slider
+    id: control
+		value: 0
 
-    property real sliderWidth
-    property real sliderHeight
+		property color progressColor: Nheko.colors.highlight
     property bool alwaysShowSlider: true
+    property int sliderRadius: 16
+    implicitHeight: sliderRadius
 
-    anchors.bottomMargin: orientation == Qt.Vertical ? Nheko.paddingMedium : undefined
-    anchors.topMargin: orientation == Qt.Vertical ? Nheko.paddingMedium : undefined
-    anchors.leftMargin: orientation == Qt.Vertical ? undefined : Nheko.paddingMedium
-    anchors.rightMargin: orientation == Qt.Vertical ? undefined : Nheko.paddingMedium
+		padding: 0
 
     background: Rectangle {
-        x: slider.leftPadding + (slider.orientation == Qt.Vertical ? slider.availableWidth / 2 - width / 2 : 0)
-        y: slider.topPadding + (slider.orientation == Qt.Vertical ? 0 : slider.availableHeight / 2 - height / 2)
-        // implicitWidth: slider.orientation == Qt.Vertical ? 8 : 100
-        // implicitHeight: slider.orientation == Qt.Vertical ? 100 : 8
-        width: slider.orientation == Qt.Vertical ? sliderWidth : slider.availableWidth
-        height: slider.orientation == Qt.Vertical ? slider.availableHeight : sliderHeight
-        radius: 2
-        color: {
-            if (slider.orientation == Qt.Vertical) {
-                return Nheko.colors.highlight;
-            } else {
-                var col = Nheko.colors.buttonText;
-                return Qt.rgba(col.r, col.g, col.b, 0.5);
-            }
-        }
-        border.color: {
-            var col = Nheko.colors.base;
-            return Qt.rgba(col.r, col.g, col.b, 0.5);
-        }
+        x: control.leftPadding + handle.width/2
+        y: control.topPadding + control.availableHeight / 2 - height / 2
+        implicitWidth: 200
+        implicitHeight: control.sliderRadius/4
+        width: control.availableWidth - handle.width
+        height: implicitHeight
+        radius: height/2
+        color: Nheko.colors.buttonText
 
         Rectangle {
-            width: slider.orientation == Qt.Vertical ? parent.width : slider.visualPosition * parent.width
-            height: slider.orientation == Qt.Vertical ? slider.visualPosition * parent.height : parent.height
-            color: {
-                if (slider.orientation == Qt.Vertical) {
-                    return Nheko.colors.buttonText;
-                } else {
-                    return Nheko.colors.highlight;
-                }
-            }
+            width: control.visualPosition * parent.width
+            height: parent.height
+            color: control.progressColor
             radius: 2
         }
-
     }
 
     handle: Rectangle {
-        x: {
-            if (slider.orientation == Qt.Vertical)
-                return slider.leftPadding + slider.availableWidth / 2 - width / 2;
-            else
-                return slider.leftPadding + slider.visualPosition * (slider.availableWidth - width);
-        }
-        y: {
-            if (slider.orientation == Qt.Vertical)
-                return slider.topPadding + slider.visualPosition * (slider.availableHeight - height);
-            else
-                return slider.topPadding + slider.availableHeight / 2 - height / 2;
-        }
-        implicitWidth: 16
-        implicitHeight: 16
-        radius: slider.width / 2
-        color: Nheko.colors.highlight
-        visible:  alwaysShowSlider || slider.hovered || slider.pressed || Settings.mobileMode
+        x: control.leftPadding + control.visualPosition * background.width
+        y: control.topPadding + control.availableHeight / 2 - height / 2
+        implicitWidth: control.sliderRadius
+        implicitHeight: control.sliderRadius
+        radius: control.sliderRadius/2
+				color: control.progressColor
+        visible:  Settings.mobileMode || control.alwaysShowSlider || control.hovered || control.pressed
+        border.color: control.progressColor
     }
-
 }
diff --git a/resources/qml/ui/media/MediaControls.qml b/resources/qml/ui/media/MediaControls.qml
index b529462da..f5321dca6 100644
--- a/resources/qml/ui/media/MediaControls.qml
+++ b/resources/qml/ui/media/MediaControls.qml
@@ -3,28 +3,33 @@
 // SPDX-License-Identifier: GPL-3.0-or-later
 
 import "../"
+import "../../"
 import QtMultimedia 5.15
 import QtQuick 2.15
 import QtQuick.Controls 2.15
 import QtQuick.Layouts 1.15
 import im.nheko 1.0
 
-Item {
+Rectangle {
     id: control
 
     property alias desiredVolume: volumeSlider.desiredVolume
-    property alias muted: volumeSlider.muted
+    property bool muted: false
     property bool playingVideo: false
     property var mediaState
     property bool mediaLoaded: false
     property var duration
     property var positionValue: 0
     property var position
-    property int controlHeight: 25
     property bool shouldShowControls: !playingVideo || playerMouseArea.shouldShowControls || volumeSlider.controlsVisible
+    color: {
+        var wc = Nheko.colors.alternateBase;
+        return Qt.rgba(wc.r, wc.g, wc.b, 0.5);
+    }
+    height: controlLayout.implicitHeight
 
-    signal playPauseActivated(real mouseX, real mouseY)
-    signal loadActivated(real mouseX, real mouseY)
+    signal playPauseActivated()
+    signal loadActivated()
 
     function durationToString(duration) {
         function maybeZeroPrepend(time) {
@@ -51,7 +56,7 @@ Item {
         property bool shouldShowControls: (containsMouse && controlHideTimer.running) || (control.mediaState != MediaPlayer.PlayingState) || controlLayout.contains(mapToItem(controlLayout, mouseX, mouseY))
 
         onClicked: {
-            control.mediaLoaded ? control.playPauseActivated(mouseX, mouseY) : control.loadActivated(mouseX, mouseY);
+            control.mediaLoaded ? control.playPauseActivated() : control.loadActivated();
         }
         hoverEnabled: true
         onPositionChanged: controlHideTimer.start()
@@ -66,102 +71,155 @@ Item {
         id: controlLayout
         opacity: control.shouldShowControls ? 1 : 0
 
-        // spacing: Nheko.paddingSmall
+        spacing: 0
         anchors.bottom: control.bottom
         anchors.left: control.left
         anchors.right: control.right
 
         NhekoSlider {
             Layout.fillWidth: true
-            Layout.minimumWidth: 50
-            Layout.leftMargin: Nheko.paddingMedium
-            Layout.rightMargin: Nheko.paddingMedium
-            height: control.controlHeight
+            Layout.leftMargin: Nheko.paddingSmall
+            Layout.rightMargin: Nheko.paddingSmall
+
+            enabled: control.mediaLoaded
+
             value: control.positionValue
             onMoved: control.position = value
             from: 0
             to: control.duration
-            sliderHeight: 8
             alwaysShowSlider: false
         }
 
-        Rectangle {
-            id: controlRect
 
-            // Window color with 128/255 alpha
-            color: {
-                var wc = Nheko.colors.alternateBase;
-                return Qt.rgba(wc.r, wc.g, wc.b, 0.5);
+        RowLayout {
+            Layout.margins: Nheko.paddingSmall
+            spacing: Nheko.paddingSmall
+            Layout.fillWidth: true
+
+            // Cache/Play/pause button
+            ImageButton {
+                Layout.alignment: Qt.AlignLeft
+                id: playbackStateImage
+
+                buttonTextColor: Nheko.colors.text
+                Layout.preferredHeight: 24
+                Layout.preferredWidth: 24
+
+                image: {
+                    if (control.mediaLoaded) {
+                        if (control.mediaState == MediaPlayer.PlayingState)
+                        return ":/icons/icons/ui/pause-symbol.png";
+                        else
+                        return ":/icons/icons/ui/play-sign.png";
+                    } else {
+                        return ":/icons/icons/ui/arrow-pointing-down.png";
+                    }
+                }
+
+                onClicked: control.mediaLoaded ? control.playPauseActivated() : control.loadActivated();
+
             }
 
-            Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom
+            ImageButton {
+                Layout.alignment: Qt.AlignLeft
+                id: volumeButton
 
-            height: 35
-            Layout.fillWidth: true
+                buttonTextColor: Nheko.colors.text
+                Layout.preferredHeight: 24
+                Layout.preferredWidth: 24
 
-            RowLayout {
-                anchors.left: controlRect.left
-                anchors.bottom: controlRect.bottom
-                anchors.right: controlRect.right
-                anchors.margins: Nheko.paddingSmall
-                anchors.verticalCenter: controlRect.verticalCenter
-                spacing: Nheko.paddingSmall
-
-                // Cache/Play/pause button
-                Image {
-                    Layout.alignment: Qt.AlignLeft
-                    id: playbackStateImage
-
-                    property color controlColor: (playbackStateArea.containsMouse) ? Nheko.colors.highlight : Nheko.colors.text
-
-                    fillMode: Image.PreserveAspectFit
-                    Layout.preferredHeight: control.controlHeight
-                    source: {
-                        if (control.mediaLoaded) {
-                            if (control.mediaState == MediaPlayer.PlayingState)
-                                return "image://colorimage/:/icons/icons/ui/pause-symbol.png?" + controlColor;
-                            else
-                                return "image://colorimage/:/icons/icons/ui/play-sign.png?" + controlColor;
-                        } else {
-                            return "image://colorimage/:/icons/icons/ui/arrow-pointing-down.png?" + controlColor;
-                        }
+                image: {
+                    if (control.muted || control.desiredVolume <= 0) {
+                        return ":/icons/icons/ui/volume-off-indicator.png";
+                    } else {
+                        return ":/icons/icons/ui/volume-up.png";
                     }
+                }
 
-                    MouseArea {
-                        id: playbackStateArea
+                onClicked: control.muted = !control.muted
 
-                        anchors.fill: parent
-                        hoverEnabled: true
-                        onClicked: {
-                            control.mediaLoaded ? control.playPauseActivated(mouseX, mouseY) : control.loadActivated(mouseX, mouseY);
-                        }
-                    }
+            }
 
-                }
+            NhekoSlider {
+                state: ""
 
-                VolumeControl {
-                    Layout.alignment: Qt.AlignLeft
-                    id: volumeSlider
-                    orientation: Qt.Horizontal
-                    Layout.rightMargin: 5
-                    Layout.preferredHeight: control.controlHeight
+                states: State {
+                    name: "shown"
+                    when: Settings.mobileMode || volumeButton.hovered || volumeSlider.hovered || volumeSlider.pressed
+                    PropertyChanges {target: volumeSlider; Layout.preferredWidth: 100}
+                    PropertyChanges {target: volumeSlider; opacity: 1}
                 }
 
-                Label {
-                    Layout.alignment: Qt.AlignRight
+                Layout.alignment: Qt.AlignLeft
+                Layout.preferredWidth: 0
+                opacity: 0
+                id: volumeSlider
+                orientation: Qt.Horizontal
+                property real desiredVolume: QtMultimedia.convertVolume(volumeSlider.value, QtMultimedia.LogarithmicVolumeScale, QtMultimedia.LinearVolumeScale)
+                value: 1
 
-                    text: (!control.mediaLoaded) ? "-/-" : (durationToString(control.positionValue) + "/" + durationToString(control.duration))
-                    color: Nheko.colors.text
+                onDesiredVolumeChanged: {
+                    control.muted = !(desiredVolume > 0);
                 }
 
-                Item {
-                    Layout.fillWidth: true
-                }
+                transitions: [
+                    Transition {
+                        from: ""
+                        to: "shown"
+
+                        SequentialAnimation {
+                            PauseAnimation { duration: 50 }
+                            NumberAnimation {
+                                duration: 100
+                                properties: "opacity"
+                                    easing.type: Easing.InQuad
+                            }
+                        }
+
+                        NumberAnimation {
+                            properties: "Layout.preferredWidth"
+                            duration: 150
+                        }
+                    },
+                    Transition {
+                        from: "shown"
+                        to: ""
+
+                        SequentialAnimation {
+                            PauseAnimation { duration: 100 }
+
+                            ParallelAnimation {
+                                NumberAnimation {
+                                    duration: 100
+                                    properties: "opacity"
+                                    easing.type: Easing.InQuad
+                                }
+
+                                NumberAnimation {
+                                    properties: "Layout.preferredWidth"
+                                    duration: 150
+                                }
+                            }
+                        }
 
+                    }
+                ]
+            }
+
+            Label {
+                Layout.alignment: Qt.AlignRight
+
+                text: (!control.mediaLoaded) ? "-- / --" : (durationToString(control.positionValue) + " / " + durationToString(control.duration))
+                color: Nheko.colors.text
+            }
+
+            Item {
+                Layout.fillWidth: true
             }
 
         }
 
+
         // Fade controls in/out
         Behavior on opacity {
             OpacityAnimator {
diff --git a/resources/qml/ui/media/VolumeControl.qml b/resources/qml/ui/media/VolumeControl.qml
deleted file mode 100644
index e87550ac4..000000000
--- a/resources/qml/ui/media/VolumeControl.qml
+++ /dev/null
@@ -1,117 +0,0 @@
-// SPDX-FileCopyrightText: 2021 Nheko Contributors
-//
-// SPDX-License-Identifier: GPL-3.0-or-later
-
-import "../"
-
-import QtMultimedia 5.15
-import QtQuick 2.15
-import QtQuick.Controls 2.15
-
-import im.nheko 1.0
-
-// Volume slider activator
-Image {
-    // TODO: add icons for different volume levels
-    id: volumeImage
-
-    property alias desiredVolume: volumeSlider.desiredVolume
-    property alias orientation: volumeSlider.orientation
-    property alias controlsVisible: volumeSliderRect.visible
-    property bool muted: false
-    property color controlColor: (volumeImageArea.containsMouse) ? Nheko.colors.highlight : Nheko.colors.text
-    width: sourceSize.width + volumeSliderRect.implicitWidth
-
-    source: (desiredVolume > 0 && !muted) ? "image://colorimage/:/icons/icons/ui/volume-up.png?" + controlColor : "image://colorimage/:/icons/icons/ui/volume-off-indicator.png?" + controlColor
-    fillMode: Image.PreserveAspectFit
-
-    MouseArea {
-        id: volumeImageArea
-
-        anchors.fill: parent
-        hoverEnabled: true
-        onExited: volumeSliderHideTimer.start()
-        onPositionChanged: volumeSliderHideTimer.start()
-        onClicked: volumeImage.muted = !volumeImage.muted
-
-        // For hiding volume slider after a while
-        Timer {
-            id: volumeSliderHideTimer
-
-            interval: 1500
-            repeat: false
-            running: false
-        }
-
-    }
-
-    Rectangle {
-        id: volumeSliderRect
-
-        opacity: (visible) ? 1 : 0
-        anchors.bottom: volumeSlider.orientation == Qt.Vertical ? volumeImage.top : undefined
-        anchors.left: volumeSlider.orientation == Qt.Vertical ? undefined : volumeImage.right
-        anchors.horizontalCenter: volumeSlider.orientation == Qt.Vertical ? volumeImage.horizontalCenter : undefined
-        anchors.verticalCenter: volumeSlider.orientation == Qt.Vertical ? undefined : volumeImage.verticalCenter
-        color: {
-            if (volumeSlider.orientation == Qt.Vertical) {
-                var wc = Nheko.colors.window;
-                return Qt.rgba(wc.r, wc.g, wc.b, 0.5);
-            } else {
-                return "transparent";
-            }
-        }
-        /* TODO: base width on the slider width (some issue with it not having a geometry
-        when using the width here?) */
-        width: volumeSlider.orientation == Qt.Vertical ? volumeImage.width * 0.7 : 100
-        radius: volumeSlider.width / 2
-        height: volumeSlider.orientation == Qt.Vertical ? 100 : volumeImage.height * 0.7
-        visible: volumeImageArea.containsMouse || volumeSliderHideTimer.running || volumeSliderRectMouseArea.containsMouse
-
-        NhekoSlider {
-            // TODO: the slider is slightly off-center on the left for some reason...
-            id: volumeSlider
-
-            sliderWidth: 8
-            sliderHeight: 8
-            // Desired value to avoid loop onMoved -> media.volume -> value -> onMoved...
-            property real desiredVolume: QtMultimedia.convertVolume(volumeSlider.value, QtMultimedia.LogarithmicVolumeScale, QtMultimedia.LinearVolumeScale)
-
-            value: 1
-            anchors.fill: volumeSliderRect
-            anchors.horizontalCenter: orientation == Qt.Vertical ? volumeSliderRect.horizontalCenter : undefined
-            anchors.verticalCenter: orientation == Qt.Vertical ? undefined : volumeSliderRect.verticalCenter
-            orientation: Qt.Vertical
-            onDesiredVolumeChanged: {
-                volumeImage.muted = !(desiredVolume > 0);
-            }
-        }
-        // Used for resetting the timer on mouse moves on volumeSliderRect
-
-        MouseArea {
-            id: volumeSliderRectMouseArea
-
-            anchors.fill: parent
-            hoverEnabled: true
-            propagateComposedEvents: true
-            onExited: volumeSliderHideTimer.start()
-            onClicked: mouse.accepted = false
-            onPressed: mouse.accepted = false
-            onReleased: mouse.accepted = false
-            onPressAndHold: mouse.accepted = false
-            onPositionChanged: {
-                mouse.accepted = false;
-                volumeSliderHideTimer.start();
-            }
-        }
-
-        Behavior on opacity {
-            OpacityAnimator {
-                duration: 100
-            }
-
-        }
-
-    }
-
-}
diff --git a/resources/res.qrc b/resources/res.qrc
index 4e243251c..a60f4ab03 100644
--- a/resources/res.qrc
+++ b/resources/res.qrc
@@ -185,7 +185,6 @@
         <file>qml/ui/Spinner.qml</file>
         <file>qml/ui/animations/BlinkAnimation.qml</file>
         <file>qml/ui/media/MediaControls.qml</file>
-        <file>qml/ui/media/VolumeControl.qml</file>
         <file>qml/voip/ActiveCallBar.qml</file>
         <file>qml/voip/CallDevices.qml</file>
         <file>qml/voip/CallInvite.qml</file>
-- 
GitLab


From 435047b1baeb9a6fa33034f855393c0064f69b2c Mon Sep 17 00:00:00 2001
From: Nicolas Werner <nicolas.werner@hotmail.de>
Date: Thu, 11 Nov 2021 19:56:51 +0100
Subject: [PATCH 12/13] Make it possible to unhide the controls on mobile

---
 .../qml/delegates/PlayableMediaMessage.qml    |  6 ++++
 resources/qml/ui/media/MediaControls.qml      | 34 ++++++++-----------
 2 files changed, 21 insertions(+), 19 deletions(-)

diff --git a/resources/qml/delegates/PlayableMediaMessage.qml b/resources/qml/delegates/PlayableMediaMessage.qml
index 67214dd70..99c82a9b2 100644
--- a/resources/qml/delegates/PlayableMediaMessage.qml
+++ b/resources/qml/delegates/PlayableMediaMessage.qml
@@ -48,6 +48,12 @@ Item {
         width: parent.width
         height: parent.height - fileInfoLabel.height
 
+        
+    TapHandler {
+        onTapped: mediaControls.showControls();
+    }
+
+
 
         Image {
             anchors.fill: parent
diff --git a/resources/qml/ui/media/MediaControls.qml b/resources/qml/ui/media/MediaControls.qml
index f5321dca6..00ccdd268 100644
--- a/resources/qml/ui/media/MediaControls.qml
+++ b/resources/qml/ui/media/MediaControls.qml
@@ -21,16 +21,21 @@ Rectangle {
     property var duration
     property var positionValue: 0
     property var position
-    property bool shouldShowControls: !playingVideo || playerMouseArea.shouldShowControls || volumeSlider.controlsVisible
+    property bool shouldShowControls: !playingVideo || playerMouseArea.shouldShowControls || volumeSlider.state == "shown"
     color: {
         var wc = Nheko.colors.alternateBase;
         return Qt.rgba(wc.r, wc.g, wc.b, 0.5);
     }
+    opacity: control.shouldShowControls ? 1 : 0
     height: controlLayout.implicitHeight
 
     signal playPauseActivated()
     signal loadActivated()
 
+    function showControls() {
+        controlHideTimer.restart();
+    }
+
     function durationToString(duration) {
         function maybeZeroPrepend(time) {
             return (time < 10) ? "0" + time.toString() : time.toString();
@@ -50,26 +55,18 @@ Rectangle {
         return hh + ":" + mm + ":" + ss;
     }
 
-    MouseArea {
+    HoverHandler {
         id: playerMouseArea
 
-        property bool shouldShowControls: (containsMouse && controlHideTimer.running) || (control.mediaState != MediaPlayer.PlayingState) || controlLayout.contains(mapToItem(controlLayout, mouseX, mouseY))
+        property bool shouldShowControls: hovered || controlHideTimer.running || control.mediaState != MediaPlayer.PlayingState
 
-        onClicked: {
-            control.mediaLoaded ? control.playPauseActivated() : control.loadActivated();
-        }
-        hoverEnabled: true
-        onPositionChanged: controlHideTimer.start()
-        onExited: controlHideTimer.start()
-        onEntered: controlHideTimer.start()
-        anchors.fill: control
-        propagateComposedEvents: true
+        onHoveredChanged: showControls();
     }
 
     ColumnLayout {
 
         id: controlLayout
-        opacity: control.shouldShowControls ? 1 : 0
+        enabled: control.shouldShowControls
 
         spacing: 0
         anchors.bottom: control.bottom
@@ -219,13 +216,12 @@ Rectangle {
 
         }
 
+    }
 
-        // Fade controls in/out
-        Behavior on opacity {
-            OpacityAnimator {
-                duration: 100
-            }
-
+    // Fade controls in/out
+    Behavior on opacity {
+        OpacityAnimator {
+            duration: 100
         }
 
     }
-- 
GitLab


From b7b4fd0e9bd7c6a6ec0312014102c479264b3f0f Mon Sep 17 00:00:00 2001
From: Nicolas Werner <nicolas.werner@hotmail.de>
Date: Thu, 11 Nov 2021 21:32:38 +0100
Subject: [PATCH 13/13] Format qml

---
 resources/qml/CommunitiesList.qml             |  13 +--
 .../qml/delegates/PlayableMediaMessage.qml    |  56 +++++-----
 resources/qml/dialogs/ReadReceipts.qml        |   7 +-
 resources/qml/ui/NhekoSlider.qml              |  22 ++--
 resources/qml/ui/media/MediaControls.qml      | 105 ++++++++++--------
 5 files changed, 103 insertions(+), 100 deletions(-)

diff --git a/resources/qml/CommunitiesList.qml b/resources/qml/CommunitiesList.qml
index 6a2c642c6..0a8587b3d 100644
--- a/resources/qml/CommunitiesList.qml
+++ b/resources/qml/CommunitiesList.qml
@@ -56,15 +56,13 @@ Page {
             property color bubbleBackground: Nheko.colors.highlight
             property color bubbleText: Nheko.colors.highlightedText
 
-            background: Rectangle {
-                color: backgroundColor
-            }
-
             height: avatarSize + 2 * Nheko.paddingMedium
             width: ListView.view.width
             state: "normal"
             ToolTip.visible: hovered && collapsed
             ToolTip.text: model.tooltip
+            onClicked: Communities.setCurrentTagId(model.id)
+            onPressAndHold: communityContextMenu.show(model.id)
             states: [
                 State {
                     name: "highlight"
@@ -108,9 +106,6 @@ Page {
 
             }
 
-            onClicked: Communities.setCurrentTagId(model.id)
-            onPressAndHold: communityContextMenu.show(model.id)
-
             RowLayout {
                 spacing: Nheko.paddingMedium
                 anchors.fill: parent
@@ -149,6 +144,10 @@ Page {
 
             }
 
+            background: Rectangle {
+                color: backgroundColor
+            }
+
         }
 
     }
diff --git a/resources/qml/delegates/PlayableMediaMessage.qml b/resources/qml/delegates/PlayableMediaMessage.qml
index 99c82a9b2..c738e5b4d 100644
--- a/resources/qml/delegates/PlayableMediaMessage.qml
+++ b/resources/qml/delegates/PlayableMediaMessage.qml
@@ -21,14 +21,13 @@ Item {
     required property string url
     required property string body
     required property string filesize
+    property double tempWidth: Math.min(parent ? parent.width : undefined, originalWidth < 1 ? 400 : originalWidth)
+    property double tempHeight: tempWidth * proportionalHeight
+    property double divisor: isReply ? 4 : 2
+    property bool tooHigh: tempHeight > timelineRoot.height / divisor
 
-        property double tempWidth: Math.min(parent ? parent.width : undefined, originalWidth < 1 ? 400 : originalWidth)
-        property double tempHeight: tempWidth * proportionalHeight
-        property double divisor: isReply ? 4 : 2
-        property bool tooHigh: tempHeight > timelineRoot.height / divisor
-
-        height: (type == MtxEvent.VideoMessage ? tooHigh ? timelineRoot.height / divisor : tempHeight : 80) + fileInfoLabel.height
-        width: type == MtxEvent.VideoMessage ? tooHigh ? (timelineRoot.height / divisor) / proportionalHeight : tempWidth : 250
+    height: (type == MtxEvent.VideoMessage ? tooHigh ? timelineRoot.height / divisor : tempHeight : 80) + fileInfoLabel.height
+    width: type == MtxEvent.VideoMessage ? tooHigh ? (timelineRoot.height / divisor) / proportionalHeight : tempWidth : 250
 
     MxcMedia {
         id: mxcmedia
@@ -44,16 +43,14 @@ Item {
 
     Rectangle {
         id: videoContainer
+
         color: type == MtxEvent.VideoMessage ? Nheko.colors.window : "transparent"
         width: parent.width
         height: parent.height - fileInfoLabel.height
 
-        
-    TapHandler {
-        onTapped: mediaControls.showControls();
-    }
-
-
+        TapHandler {
+            onTapped: mediaControls.showControls()
+        }
 
         Image {
             anchors.fill: parent
@@ -72,34 +69,31 @@ Item {
                 flushMode: VideoOutput.FirstFrame
             }
 
-
         }
 
     }
 
-            MediaControls {
-                id: mediaControls
-
-                anchors.left: content.left
-                anchors.right: content.right
-                anchors.bottom: fileInfoLabel.top
-
-                playingVideo: type == MtxEvent.VideoMessage
-                positionValue: mxcmedia.position
-                duration: mxcmedia.duration
-                mediaLoaded: mxcmedia.loaded
-                mediaState: mxcmedia.state
-                onPositionChanged: mxcmedia.position = position
-                onPlayPauseActivated: mxcmedia.state == MediaPlayer.PlayingState ? mxcmedia.pause() : mxcmedia.play()
-                onLoadActivated: mxcmedia.eventId = eventId
-            }
+    MediaControls {
+        id: mediaControls
+
+        anchors.left: content.left
+        anchors.right: content.right
+        anchors.bottom: fileInfoLabel.top
+        playingVideo: type == MtxEvent.VideoMessage
+        positionValue: mxcmedia.position
+        duration: mxcmedia.duration
+        mediaLoaded: mxcmedia.loaded
+        mediaState: mxcmedia.state
+        onPositionChanged: mxcmedia.position = position
+        onPlayPauseActivated: mxcmedia.state == MediaPlayer.PlayingState ? mxcmedia.pause() : mxcmedia.play()
+        onLoadActivated: mxcmedia.eventId = eventId
+    }
 
     // information about file name and file size
     Label {
         id: fileInfoLabel
 
         anchors.bottom: content.bottom
-
         text: body + " [" + filesize + "]"
         textFormat: Text.PlainText
         elide: Text.ElideRight
diff --git a/resources/qml/dialogs/ReadReceipts.qml b/resources/qml/dialogs/ReadReceipts.qml
index f551bae9d..1bfdae849 100644
--- a/resources/qml/dialogs/ReadReceipts.qml
+++ b/resources/qml/dialogs/ReadReceipts.qml
@@ -66,9 +66,6 @@ ApplicationWindow {
                     hoverEnabled: true
                     ToolTip.visible: hovered
                     ToolTip.text: model.mxid
-                    background: Rectangle {
-                        color: readReceiptsRoot.color
-                    }
 
                     RowLayout {
                         id: receiptLayout
@@ -113,6 +110,10 @@ ApplicationWindow {
                         cursorShape: Qt.PointingHandCursor
                     }
 
+                    background: Rectangle {
+                        color: readReceiptsRoot.color
+                    }
+
                 }
 
             }
diff --git a/resources/qml/ui/NhekoSlider.qml b/resources/qml/ui/NhekoSlider.qml
index 6cf1fd2d7..23e22f51b 100644
--- a/resources/qml/ui/NhekoSlider.qml
+++ b/resources/qml/ui/NhekoSlider.qml
@@ -8,23 +8,23 @@ import im.nheko 1.0
 
 Slider {
     id: control
-		value: 0
 
-		property color progressColor: Nheko.colors.highlight
+    property color progressColor: Nheko.colors.highlight
     property bool alwaysShowSlider: true
     property int sliderRadius: 16
-    implicitHeight: sliderRadius
 
-		padding: 0
+    value: 0
+    implicitHeight: sliderRadius
+    padding: 0
 
     background: Rectangle {
-        x: control.leftPadding + handle.width/2
+        x: control.leftPadding + handle.width / 2
         y: control.topPadding + control.availableHeight / 2 - height / 2
         implicitWidth: 200
-        implicitHeight: control.sliderRadius/4
+        implicitHeight: control.sliderRadius / 4
         width: control.availableWidth - handle.width
         height: implicitHeight
-        radius: height/2
+        radius: height / 2
         color: Nheko.colors.buttonText
 
         Rectangle {
@@ -33,6 +33,7 @@ Slider {
             color: control.progressColor
             radius: 2
         }
+
     }
 
     handle: Rectangle {
@@ -40,9 +41,10 @@ Slider {
         y: control.topPadding + control.availableHeight / 2 - height / 2
         implicitWidth: control.sliderRadius
         implicitHeight: control.sliderRadius
-        radius: control.sliderRadius/2
-				color: control.progressColor
-        visible:  Settings.mobileMode || control.alwaysShowSlider || control.hovered || control.pressed
+        radius: control.sliderRadius / 2
+        color: control.progressColor
+        visible: Settings.mobileMode || control.alwaysShowSlider || control.hovered || control.pressed
         border.color: control.progressColor
     }
+
 }
diff --git a/resources/qml/ui/media/MediaControls.qml b/resources/qml/ui/media/MediaControls.qml
index 00ccdd268..7216e5529 100644
--- a/resources/qml/ui/media/MediaControls.qml
+++ b/resources/qml/ui/media/MediaControls.qml
@@ -22,12 +22,6 @@ Rectangle {
     property var positionValue: 0
     property var position
     property bool shouldShowControls: !playingVideo || playerMouseArea.shouldShowControls || volumeSlider.state == "shown"
-    color: {
-        var wc = Nheko.colors.alternateBase;
-        return Qt.rgba(wc.r, wc.g, wc.b, 0.5);
-    }
-    opacity: control.shouldShowControls ? 1 : 0
-    height: controlLayout.implicitHeight
 
     signal playPauseActivated()
     signal loadActivated()
@@ -55,19 +49,25 @@ Rectangle {
         return hh + ":" + mm + ":" + ss;
     }
 
+    color: {
+        var wc = Nheko.colors.alternateBase;
+        return Qt.rgba(wc.r, wc.g, wc.b, 0.5);
+    }
+    opacity: control.shouldShowControls ? 1 : 0
+    height: controlLayout.implicitHeight
+
     HoverHandler {
         id: playerMouseArea
 
         property bool shouldShowControls: hovered || controlHideTimer.running || control.mediaState != MediaPlayer.PlayingState
 
-        onHoveredChanged: showControls();
+        onHoveredChanged: showControls()
     }
 
     ColumnLayout {
-
         id: controlLayout
-        enabled: control.shouldShowControls
 
+        enabled: control.shouldShowControls
         spacing: 0
         anchors.bottom: control.bottom
         anchors.left: control.left
@@ -77,9 +77,7 @@ Rectangle {
             Layout.fillWidth: true
             Layout.leftMargin: Nheko.paddingSmall
             Layout.rightMargin: Nheko.paddingSmall
-
             enabled: control.mediaLoaded
-
             value: control.positionValue
             onMoved: control.position = value
             from: 0
@@ -87,7 +85,6 @@ Rectangle {
             alwaysShowSlider: false
         }
 
-
         RowLayout {
             Layout.margins: Nheko.paddingSmall
             spacing: Nheko.paddingSmall
@@ -95,95 +92,87 @@ Rectangle {
 
             // Cache/Play/pause button
             ImageButton {
-                Layout.alignment: Qt.AlignLeft
                 id: playbackStateImage
 
+                Layout.alignment: Qt.AlignLeft
                 buttonTextColor: Nheko.colors.text
                 Layout.preferredHeight: 24
                 Layout.preferredWidth: 24
-
                 image: {
                     if (control.mediaLoaded) {
                         if (control.mediaState == MediaPlayer.PlayingState)
-                        return ":/icons/icons/ui/pause-symbol.png";
+                            return ":/icons/icons/ui/pause-symbol.png";
                         else
-                        return ":/icons/icons/ui/play-sign.png";
+                            return ":/icons/icons/ui/play-sign.png";
                     } else {
                         return ":/icons/icons/ui/arrow-pointing-down.png";
                     }
                 }
-
-                onClicked: control.mediaLoaded ? control.playPauseActivated() : control.loadActivated();
-
+                onClicked: control.mediaLoaded ? control.playPauseActivated() : control.loadActivated()
             }
 
             ImageButton {
-                Layout.alignment: Qt.AlignLeft
                 id: volumeButton
 
+                Layout.alignment: Qt.AlignLeft
                 buttonTextColor: Nheko.colors.text
                 Layout.preferredHeight: 24
                 Layout.preferredWidth: 24
-
                 image: {
-                    if (control.muted || control.desiredVolume <= 0) {
+                    if (control.muted || control.desiredVolume <= 0)
                         return ":/icons/icons/ui/volume-off-indicator.png";
-                    } else {
+                    else
                         return ":/icons/icons/ui/volume-up.png";
-                    }
                 }
-
                 onClicked: control.muted = !control.muted
-
             }
 
             NhekoSlider {
-                state: ""
+                id: volumeSlider
 
-                states: State {
-                    name: "shown"
-                    when: Settings.mobileMode || volumeButton.hovered || volumeSlider.hovered || volumeSlider.pressed
-                    PropertyChanges {target: volumeSlider; Layout.preferredWidth: 100}
-                    PropertyChanges {target: volumeSlider; opacity: 1}
-                }
+                property real desiredVolume: QtMultimedia.convertVolume(volumeSlider.value, QtMultimedia.LogarithmicVolumeScale, QtMultimedia.LinearVolumeScale)
 
+                state: ""
                 Layout.alignment: Qt.AlignLeft
                 Layout.preferredWidth: 0
                 opacity: 0
-                id: volumeSlider
                 orientation: Qt.Horizontal
-                property real desiredVolume: QtMultimedia.convertVolume(volumeSlider.value, QtMultimedia.LogarithmicVolumeScale, QtMultimedia.LinearVolumeScale)
                 value: 1
-
                 onDesiredVolumeChanged: {
                     control.muted = !(desiredVolume > 0);
                 }
-
                 transitions: [
                     Transition {
                         from: ""
                         to: "shown"
 
                         SequentialAnimation {
-                            PauseAnimation { duration: 50 }
+                            PauseAnimation {
+                                duration: 50
+                            }
+
                             NumberAnimation {
                                 duration: 100
                                 properties: "opacity"
-                                    easing.type: Easing.InQuad
+                                easing.type: Easing.InQuad
                             }
+
                         }
 
                         NumberAnimation {
                             properties: "Layout.preferredWidth"
                             duration: 150
                         }
+
                     },
                     Transition {
                         from: "shown"
                         to: ""
 
                         SequentialAnimation {
-                            PauseAnimation { duration: 100 }
+                            PauseAnimation {
+                                duration: 100
+                            }
 
                             ParallelAnimation {
                                 NumberAnimation {
@@ -196,16 +185,34 @@ Rectangle {
                                     properties: "Layout.preferredWidth"
                                     duration: 150
                                 }
+
                             }
+
                         }
 
                     }
                 ]
+
+                states: State {
+                    name: "shown"
+                    when: Settings.mobileMode || volumeButton.hovered || volumeSlider.hovered || volumeSlider.pressed
+
+                    PropertyChanges {
+                        target: volumeSlider
+                        Layout.preferredWidth: 100
+                    }
+
+                    PropertyChanges {
+                        target: volumeSlider
+                        opacity: 1
+                    }
+
+                }
+
             }
 
             Label {
                 Layout.alignment: Qt.AlignRight
-
                 text: (!control.mediaLoaded) ? "-- / --" : (durationToString(control.positionValue) + " / " + durationToString(control.duration))
                 color: Nheko.colors.text
             }
@@ -218,14 +225,6 @@ Rectangle {
 
     }
 
-    // Fade controls in/out
-    Behavior on opacity {
-        OpacityAnimator {
-            duration: 100
-        }
-
-    }
-
     // For hiding controls on stationary cursor
     Timer {
         id: controlHideTimer
@@ -234,4 +233,12 @@ Rectangle {
         repeat: false
     }
 
+    // Fade controls in/out
+    Behavior on opacity {
+        OpacityAnimator {
+            duration: 100
+        }
+
+    }
+
 }
-- 
GitLab