Warning, /multimedia/kasts/src/qml/Mobile/MobilePlayerControls.qml is written in an unsupported language. File is not indexed.
0001 /** 0002 * SPDX-FileCopyrightText: 2021-2023 Bart De Vries <bart@mogwai.be> 0003 * SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org> 0004 * 0005 * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0006 */ 0007 0008 import QtQuick 0009 import QtQuick.Controls as Controls 0010 import QtQuick.Layouts 0011 import QtQuick.Effects 0012 0013 import org.kde.kirigami as Kirigami 0014 import org.kde.kmediasession 0015 0016 import org.kde.kasts 0017 import org.kde.kasts.settings 0018 0019 import ".." 0020 0021 Kirigami.Page { 0022 id: playerControls 0023 0024 title: AudioManager.entry ? AudioManager.entry.title : i18n("No Track Loaded") 0025 clip: true 0026 Layout.margins: 0 0027 0028 leftPadding: 0 0029 rightPadding: 0 0030 topPadding: 0 0031 bottomPadding: Kirigami.Units.gridUnit 0032 0033 Kirigami.Theme.colorSet: Kirigami.Theme.View 0034 Kirigami.Theme.inherit: false 0035 0036 Component { 0037 id: slider 0038 ChapterSlider { 0039 model: chapterModel 0040 } 0041 } 0042 0043 background: MultiEffect { 0044 source: backgroundImage 0045 anchors.fill: parent 0046 opacity: 0.2 0047 0048 brightness: 0.3 0049 saturation: 2 0050 contrast: -0.7 0051 blurMax: 64 0052 blur: 1.0 0053 blurEnabled: true 0054 autoPaddingEnabled: false 0055 0056 Image { 0057 id: backgroundImage 0058 source: AudioManager.entry.cachedImage 0059 asynchronous: true 0060 visible: false 0061 anchors.fill: parent 0062 fillMode: Image.PreserveAspectCrop 0063 } 0064 } 0065 0066 ColumnLayout { 0067 id: playerControlsColumn 0068 anchors.fill: parent 0069 anchors.topMargin: Kirigami.Units.largeSpacing * 2 0070 anchors.bottomMargin: Kirigami.Units.largeSpacing 0071 spacing: 0 0072 0073 Controls.Button { 0074 id: swipeUpButton 0075 property int swipeUpButtonSize: Kirigami.Units.iconSizes.smallMedium 0076 icon.name: "arrow-down" 0077 icon.height: swipeUpButtonSize 0078 icon.width: swipeUpButtonSize 0079 flat: true 0080 Layout.alignment: Qt.AlignHCenter 0081 Layout.topMargin: 0 0082 Layout.bottomMargin: Kirigami.Units.largeSpacing 0083 onClicked: toClose.restart() 0084 } 0085 0086 Controls.SwipeView { 0087 id: swipeView 0088 0089 currentIndex: 0 0090 Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter 0091 Layout.preferredWidth: parent.width 0092 Layout.preferredHeight: parent.height - media.height - swipeUpButton.height 0093 Layout.margins: 0 0094 0095 // we are unable to use Controls.Control here to set padding since it seems to eat touch events on the parent flickable 0096 Item { 0097 Item { 0098 property int textMargin: Kirigami.Units.largeSpacing // margin above the text below the image 0099 anchors.fill: parent 0100 anchors.leftMargin: Kirigami.Units.largeSpacing * 2 0101 anchors.rightMargin: Kirigami.Units.largeSpacing * 2 0102 0103 Item { 0104 id: coverImage 0105 anchors { 0106 top: parent.top 0107 bottom: kastsMainWindow.isWidescreen ? parent.bottom : undefined 0108 left: parent.left 0109 right: kastsMainWindow.isWidescreen ? undefined : parent.right 0110 margins: 0 0111 topMargin: kastsMainWindow.isWidescreen ? 0 : (parent.height - Math.min(height, width) - imageLabels.implicitHeight - 2 * parent.textMargin) / 2 0112 } 0113 height: Math.min(parent.height - (kastsMainWindow.isWidescreen ? 0 : imageLabels.implicitHeight + 2 * parent.textMargin), parent.width) 0114 width: kastsMainWindow.isWidescreen ? Math.min(parent.height, parent.width / 2) : Math.min(parent.width, height) 0115 0116 ImageWithFallback { 0117 imageSource: AudioManager.entry ? ((chapterModel.currentChapter && chapterModel.currentChapter !== undefined) ? chapterModel.currentChapter.cachedImage : AudioManager.entry.cachedImage) : "no-image" 0118 imageFillMode: Image.PreserveAspectCrop 0119 anchors.centerIn: parent 0120 anchors.margins: 0 0121 width: Math.min(parent.width, parent.height) 0122 height: Math.min(parent.height, parent.width) 0123 fractionalRadius: 1 / 20 0124 } 0125 } 0126 0127 Item { 0128 anchors { 0129 top: kastsMainWindow.isWidescreen ? parent.top : coverImage.bottom 0130 bottom: parent.bottom 0131 left: kastsMainWindow.isWidescreen ? coverImage.right : parent.left 0132 right: parent.right 0133 leftMargin: kastsMainWindow.isWidescreen ? parent.textMargin : 0 0134 topMargin: kastsMainWindow.isWidescreen ? 0 : parent.textMargin 0135 bottomMargin: 0 0136 } 0137 0138 ColumnLayout { 0139 id: imageLabels 0140 anchors.verticalCenter: parent.verticalCenter 0141 anchors.left: parent.left 0142 anchors.right: parent.right 0143 anchors.margins: 0 0144 Controls.Label { 0145 text: AudioManager.entry ? AudioManager.entry.title : i18n("No Title") 0146 elide: Text.ElideRight 0147 Layout.alignment: Qt.AlignHCenter 0148 Layout.maximumWidth: parent.width 0149 font.weight: Font.Medium 0150 } 0151 0152 Controls.Label { 0153 text: AudioManager.entry ? AudioManager.entry.feed.name : i18n("No podcast title") 0154 elide: Text.ElideRight 0155 Layout.alignment: Qt.AlignHCenter 0156 Layout.maximumWidth: parent.width 0157 opacity: 0.6 0158 } 0159 } 0160 } 0161 } 0162 } 0163 0164 Item { 0165 Flickable { 0166 anchors.fill: parent 0167 anchors.leftMargin: Kirigami.Units.largeSpacing * 2 + playerControlsColumn.anchors.margins 0168 anchors.rightMargin: Kirigami.Units.largeSpacing * 2 0169 clip: true 0170 contentHeight: description.height 0171 ColumnLayout { 0172 id: description 0173 width: parent.width 0174 Kirigami.Heading { 0175 text: AudioManager.entry ? AudioManager.entry.title : i18n("No Track Title") 0176 level: 3 0177 wrapMode: Text.WordWrap 0178 Layout.fillWidth: true 0179 Layout.bottomMargin: Kirigami.Units.largeSpacing 0180 } 0181 0182 Controls.Label { 0183 id: text 0184 Layout.fillWidth: true 0185 text: AudioManager.entry ? AudioManager.entry.adjustedContent(width, font.pixelSize) : i18n("No track loaded") 0186 verticalAlignment: Text.AlignTop 0187 baseUrl: AudioManager.entry ? AudioManager.entry.baseUrl : "" 0188 textFormat: Text.RichText 0189 wrapMode: Text.WordWrap 0190 onLinkHovered: { 0191 cursorShape: Qt.PointingHandCursor; 0192 } 0193 onLinkActivated: (link) => { 0194 if (link.split("://")[0] === "timestamp") { 0195 if (AudioManager.entry && AudioManager.entry.enclosure) { 0196 AudioManager.seek(link.split("://")[1]); 0197 } 0198 } else { 0199 Qt.openUrlExternally(link); 0200 } 0201 } 0202 } 0203 } 0204 } 0205 } 0206 0207 Item { 0208 Item { 0209 anchors.fill: parent 0210 anchors.leftMargin: Kirigami.Units.largeSpacing * 2 0211 anchors.rightMargin: Kirigami.Units.largeSpacing * 2 0212 0213 Kirigami.PlaceholderMessage { 0214 visible: chapterList.count === 0 0215 0216 width: parent.width 0217 anchors.centerIn: parent 0218 0219 text: i18n("No chapters found") 0220 } 0221 0222 ListView { 0223 id: chapterList 0224 model: ChapterModel { 0225 id: chapterModel 0226 entry: AudioManager.entry ? AudioManager.entry : null 0227 duration: AudioManager.duration / 1000 0228 } 0229 clip: true 0230 visible: chapterList.count !== 0 0231 anchors.fill: parent 0232 delegate: ChapterListDelegate { 0233 entry: AudioManager.entry ? AudioManager.entry : null 0234 } 0235 } 0236 } 0237 } 0238 } 0239 0240 Item { 0241 id: media 0242 0243 implicitHeight: mediaControls.height 0244 Layout.leftMargin: Kirigami.Units.largeSpacing * 2 0245 Layout.rightMargin: Kirigami.Units.largeSpacing * 2 0246 Layout.bottomMargin: 0 0247 Layout.topMargin: 0 0248 Layout.fillWidth: true 0249 0250 ColumnLayout { 0251 id: mediaControls 0252 0253 anchors.left: parent.left 0254 anchors.right: parent.right 0255 anchors.bottom: parent.bottom 0256 anchors.margins: 0 0257 spacing: 0 0258 0259 RowLayout { 0260 id: contextButtons 0261 Layout.preferredHeight: Kirigami.Units.gridUnit * 3 0262 Layout.leftMargin: Kirigami.Units.largeSpacing 0263 Layout.rightMargin: Kirigami.Units.largeSpacing 0264 Layout.topMargin: Kirigami.Units.smallSpacing 0265 Layout.bottomMargin: Kirigami.Units.smallSpacing 0266 Layout.fillWidth: true 0267 property int iconSize: Math.floor(Kirigami.Units.gridUnit * 1.3) 0268 property int buttonSize: bottomRow.buttonSize 0269 0270 Controls.ToolButton { 0271 visible: AudioManager.entry 0272 checked: swipeView.currentIndex === 0 0273 Layout.maximumHeight: parent.height 0274 Layout.preferredHeight: contextButtons.buttonSize 0275 Layout.maximumWidth: height 0276 Layout.preferredWidth: height 0277 icon.name: "viewimage" 0278 icon.width: contextButtons.iconSize 0279 icon.height: contextButtons.iconSize 0280 onClicked: { 0281 swipeView.currentIndex = 0; 0282 } 0283 } 0284 0285 Controls.ToolButton { 0286 visible: AudioManager.entry 0287 checked: swipeView.currentIndex === 1 0288 Layout.maximumHeight: parent.height 0289 Layout.preferredHeight: contextButtons.buttonSize 0290 Layout.maximumWidth: height 0291 Layout.preferredWidth: height 0292 icon.name: "documentinfo" 0293 icon.width: contextButtons.iconSize 0294 icon.height: contextButtons.iconSize 0295 onClicked: { 0296 swipeView.currentIndex = 1; 0297 } 0298 } 0299 0300 Controls.ToolButton { 0301 visible: AudioManager.entry && chapterList.count !== 0 0302 checked: swipeView.currentIndex === 2 0303 Layout.maximumHeight: parent.height 0304 Layout.preferredHeight: contextButtons.buttonSize 0305 Layout.maximumWidth: height 0306 Layout.preferredWidth: height 0307 icon.name: "view-media-playlist" 0308 icon.width: contextButtons.iconSize 0309 icon.height: contextButtons.iconSize 0310 onClicked: { 0311 swipeView.currentIndex = 2; 0312 } 0313 } 0314 0315 Item { 0316 Layout.fillWidth: true 0317 } 0318 0319 Controls.ToolButton { 0320 checkable: true 0321 checked: AudioManager.remainingSleepTime > 0 0322 Layout.maximumHeight: parent.height 0323 Layout.preferredHeight: contextButtons.buttonSize 0324 Layout.maximumWidth: height 0325 Layout.preferredWidth: height 0326 icon.name: "clock" 0327 icon.width: contextButtons.iconSize 0328 icon.height: contextButtons.iconSize 0329 onClicked: { 0330 toggle(); // only set the on/off state based on sleep timer state 0331 sleepTimerDialog.open() 0332 } 0333 } 0334 Controls.ToolButton { 0335 id: volumeButton 0336 Layout.maximumHeight: parent.height 0337 Layout.preferredHeight: contextButtons.buttonSize 0338 Layout.maximumWidth: height 0339 Layout.preferredWidth: height 0340 icon.name: AudioManager.muted ? "player-volume-muted" : "player-volume" 0341 icon.width: contextButtons.iconSize 0342 icon.height: contextButtons.iconSize 0343 enabled: AudioManager.PlaybackState != AudioManager.StoppedState && AudioManager.canPlay 0344 checked: volumePopup.visible 0345 0346 Controls.ToolTip.visible: hovered 0347 Controls.ToolTip.delay: Kirigami.Units.toolTipDelay 0348 Controls.ToolTip.text: i18nc("@action:button", "Open volume settings") 0349 0350 onClicked: { 0351 if (volumePopup.visible) { 0352 volumePopup.close(); 0353 } else { 0354 volumePopup.open(); 0355 } 0356 } 0357 0358 Controls.Popup { 0359 id: volumePopup 0360 x: -padding 0361 y: -volumePopup.height 0362 0363 focus: true 0364 padding: Kirigami.Units.smallSpacing 0365 contentWidth: muteButton.implicitWidth 0366 0367 contentItem: ColumnLayout { 0368 id: popupContent 0369 0370 Controls.ToolButton { 0371 id: muteButton 0372 enabled: AudioManager.PlaybackState != AudioManager.StoppedState && AudioManager.canPlay 0373 icon.name: AudioManager.muted ? "player-volume-muted" : "player-volume" 0374 onClicked: AudioManager.muted = !AudioManager.muted 0375 0376 Controls.ToolTip.visible: hovered 0377 Controls.ToolTip.delay: Kirigami.Units.toolTipDelay 0378 Controls.ToolTip.text: i18nc("@action:button", "Toggle mute") 0379 0380 } 0381 0382 VolumeSlider { 0383 id: volumeSlider 0384 } 0385 } 0386 } 0387 } 0388 } 0389 0390 Loader { 0391 active: !kastsMainWindow.isWidescreen 0392 visible: !kastsMainWindow.isWidescreen 0393 sourceComponent: slider 0394 Layout.fillWidth: true 0395 Layout.leftMargin: Kirigami.Units.largeSpacing 0396 Layout.rightMargin: Kirigami.Units.largeSpacing 0397 } 0398 0399 RowLayout { 0400 id: controls 0401 Layout.leftMargin: Kirigami.Units.largeSpacing 0402 Layout.rightMargin: Kirigami.Units.largeSpacing 0403 Layout.fillWidth: true 0404 Controls.Label { 0405 Layout.alignment: Qt.AlignLeft 0406 padding: Kirigami.Units.largeSpacing 0407 text: AudioManager.formattedPosition 0408 font: Kirigami.Theme.smallFont 0409 } 0410 Loader { 0411 active: kastsMainWindow.isWidescreen 0412 sourceComponent: slider 0413 Layout.fillWidth: true 0414 0415 } 0416 Item { 0417 Layout.alignment: Qt.AlignRight 0418 Layout.preferredHeight: endLabel.implicitHeight 0419 Layout.preferredWidth: endLabel.implicitWidth 0420 Controls.Label { 0421 id: endLabel 0422 padding: Kirigami.Units.largeSpacing 0423 anchors.right: parent.right 0424 anchors.verticalCenter: parent.verticalCenter 0425 text: (SettingsManager.toggleRemainingTime) ? 0426 "-" + AudioManager.formattedLeftDuration 0427 : AudioManager.formattedDuration 0428 font: Kirigami.Theme.smallFont 0429 0430 } 0431 MouseArea { 0432 anchors.fill: parent 0433 hoverEnabled: true 0434 onClicked: { 0435 SettingsManager.toggleRemainingTime = !SettingsManager.toggleRemainingTime; 0436 SettingsManager.save(); 0437 } 0438 } 0439 } 0440 } 0441 0442 RowLayout { 0443 id: bottomRow 0444 Layout.leftMargin: Kirigami.Units.largeSpacing 0445 Layout.rightMargin: Kirigami.Units.largeSpacing 0446 Layout.maximumWidth: Number.POSITIVE_INFINITY //TODO ? 0447 Layout.fillWidth: true 0448 Layout.bottomMargin: 0 0449 Layout.topMargin: 0 0450 0451 // Make button width scale properly on narrow windows instead of overflowing 0452 property int buttonSize: Math.min(playButton.implicitWidth, ((playerControlsColumn.width - 4 * spacing) / 5 - playButton.leftPadding - playButton.rightPadding)) 0453 property int iconSize: Kirigami.Units.gridUnit * 1.5 0454 0455 // left section 0456 Controls.Button { 0457 id: playbackRateButton 0458 text: AudioManager.playbackRate.toFixed(2) + "x" 0459 checkable: true 0460 checked: playbackRateMenu.visible 0461 onClicked: { 0462 if (playbackRateMenu.visible) { 0463 playbackRateMenu.dismiss(); 0464 } else { 0465 playbackRateMenu.popup(playbackRateButton, 0, -playbackRateMenu.height); 0466 } 0467 } 0468 flat: true 0469 padding: 0 0470 implicitWidth: playButton.width * 1.5 0471 implicitHeight: playButton.height 0472 } 0473 0474 // middle section 0475 RowLayout { 0476 spacing: Kirigami.Units.largeSpacing 0477 Layout.alignment: Qt.AlignHCenter 0478 Controls.Button { 0479 icon.name: "media-seek-backward" 0480 icon.height: bottomRow.iconSize 0481 icon.width: bottomRow.iconSize 0482 flat: true 0483 Layout.alignment: Qt.AlignRight 0484 Layout.preferredWidth: bottomRow.buttonSize 0485 onClicked: AudioManager.skipBackward() 0486 enabled: AudioManager.canSkipBackward 0487 } 0488 Controls.Button { 0489 id: playButton 0490 icon.name: AudioManager.playbackState === KMediaSession.PlayingState ? "media-playback-pause" : "media-playback-start" 0491 icon.height: bottomRow.iconSize 0492 icon.width: bottomRow.iconSize 0493 flat: true 0494 Layout.alignment: Qt.AlignHCenter 0495 Layout.preferredWidth: bottomRow.buttonSize 0496 onClicked: AudioManager.playbackState === KMediaSession.PlayingState ? AudioManager.pause() : AudioManager.play() 0497 enabled: AudioManager.canPlay 0498 } 0499 Controls.Button { 0500 icon.name: "media-seek-forward" 0501 icon.height: bottomRow.iconSize 0502 icon.width: bottomRow.iconSize 0503 flat: true 0504 Layout.alignment: Qt.AlignLeft 0505 Layout.preferredWidth: bottomRow.buttonSize 0506 onClicked: AudioManager.skipForward() 0507 enabled: AudioManager.canSkipForward 0508 } 0509 } 0510 0511 // right section 0512 Controls.Button { 0513 icon.name: "media-skip-forward" 0514 icon.height: bottomRow.iconSize 0515 icon.width: bottomRow.iconSize 0516 flat: true 0517 Layout.alignment: Qt.AlignRight 0518 Layout.preferredWidth: bottomRow.buttonSize 0519 onClicked: AudioManager.next() 0520 enabled: AudioManager.canGoNext 0521 } 0522 } 0523 } 0524 } 0525 } 0526 0527 PlaybackRateMenu { 0528 id: playbackRateMenu 0529 parentButton: playbackRateButton 0530 } 0531 }