Warning, /multimedia/audiotube/src/contents/ui/main.qml is written in an unsupported language. File is not indexed.

0001 // SPDX-FileCopyrightText: 2021 Jonah BrĂ¼chert <jbb@kaidan.im>
0002 //
0003 // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0004 
0005 import QtQuick 2.1
0006 import QtQuick.Controls 2.12 as Controls
0007 import QtQuick.Layouts 1.3
0008 import org.kde.kirigami as Kirigami
0009 import QtMultimedia
0010 
0011 import org.kde.ytmusic 1.0
0012 
0013 import org.nemomobile.qtmpris 1.0
0014 
0015 Kirigami.ApplicationWindow {
0016     id: root
0017     minimumWidth: 300
0018     minimumHeight: 300
0019     pageStack.globalToolBar.style: wideScreen? Kirigami.ApplicationHeaderStyle.None: Kirigami.ApplicationHeaderStyle.Breadcrumb
0020 
0021     color: "transparent"
0022     Blur{id:blur}
0023     Component.onCompleted: {
0024         blur.setBlur(sidebar, true);
0025     }
0026 
0027     pageStack.columnView.columnResizeMode: Kirigami.ColumnView.SingleColumn
0028 
0029     property alias searchField: searchLoader // TODO
0030     property bool wideScreen: width >= 600
0031     property bool showSearch: false // only applicable if not widescreen
0032 
0033     // so that there is still a separator, since the header style is none
0034     Kirigami.Separator {
0035         anchors.top: parent.top
0036         anchors.left: parent.left
0037         anchors.right: parent.right
0038         visible: wideScreen
0039     }
0040 
0041     Kirigami.PagePool {
0042         id: pool
0043     }
0044 
0045     Loader {
0046         id: shareMenu
0047         active: false
0048         sourceComponent: ShareMenu {}
0049     }
0050 
0051     function openShareMenu(inputTitle: string, url: string) {
0052         shareMenu.active = true
0053         shareMenu.item.inputTitle = inputTitle
0054         shareMenu.item.url = url
0055         shareMenu.item.open()
0056     }
0057 
0058     Sidebar {
0059         id:sidebar
0060         visible: wideScreen
0061         height: pageStack.height - playerFooter.implicitHeight
0062 
0063     }
0064     NavigationBar{
0065         id: bottombar
0066         visible: !wideScreen
0067     }
0068 
0069     header: Controls.Control {
0070         visible: wideScreen || root.showSearch
0071         padding: Kirigami.Units.largeSpacing
0072 
0073         background: Rectangle {
0074              width: parent.width
0075              height: parent.height
0076              anchors.fill: parent
0077              Kirigami.Theme.inherit: false
0078              Kirigami.Theme.colorSet: Kirigami.Theme.Header
0079              color:  Kirigami.Theme.backgroundColor
0080          }
0081 
0082         contentItem: RowLayout {
0083             spacing: 0
0084             Row {
0085                 Layout.alignment: Qt.AlignLeft
0086 
0087                 Controls.ToolButton {
0088                     id: back
0089                     enabled: applicationWindow().pageStack.layers.depth > 1 || (applicationWindow().pageStack.depth > 1 && (applicationWindow().pageStack.currentIndex > 0 || applicationWindow().pageStack.contentItem.contentX > 0)) // Copied from https://invent.kde.org/frameworks/kirigami/-/blob/master/src/controls/templates/private/BackButton.qml#L16
0090                     icon.name: "draw-arrow-back"
0091                     onClicked: pageStack.goBack()
0092                     visible: wideScreen
0093                 }
0094                 Controls.ToolButton {
0095                     icon.name: "draw-arrow-forward"
0096                     enabled: applicationWindow().pageStack.currentIndex < applicationWindow().pageStack.depth-1
0097                     onClicked: pageStack.goForward()
0098                     visible: wideScreen
0099                 }
0100             }
0101 
0102             // spacer
0103             Item {
0104                 visible: wideScreen
0105                 Layout.fillWidth: !root.wideScreen
0106             }
0107             SearchWithDropdown {
0108                 id: searchLoader
0109                 visible: root.wideScreen || root.showSearch
0110                 Layout.alignment: Qt.AlignHCenter
0111                 Layout.fillWidth: true
0112                 Layout.fillHeight: true
0113                 height: back.height
0114                 width: wideScreen ? null : root.width
0115                 Layout.maximumWidth: wideScreen ? 400 : root.width
0116             }
0117 
0118             Item {
0119                 width: 2*back.width
0120                 visible: wideScreen
0121 
0122             }
0123         }
0124     }
0125 
0126     title: i18n("AudioTube")
0127 
0128     contextDrawer: Kirigami.ContextDrawer {
0129         id: contextDrawer
0130     }
0131 
0132     pageStack.initialPage: pool.loadPage(Qt.resolvedUrl("qrc:/LibraryPage.qml"))
0133     pageStack.clip: true
0134 
0135     function play(videoId) {
0136         UserPlaylistModel.initialVideoId = videoId
0137     }
0138 
0139     function playPlaylist(playlistId) {
0140         UserPlaylistModel.playlistId = playlistId
0141     }
0142 
0143     function playShufflePlaylist(playlistId) {
0144         UserPlaylistModel.shuffle = true
0145         UserPlaylistModel.playlistId = playlistId
0146     }
0147 
0148     function playFavourites(shuffle) {
0149         UserPlaylistModel.playFavourites(Library.favourites, shuffle)
0150     }
0151 
0152     function playPlaybackHistory(model, shuffle) {
0153         UserPlaylistModel.playPlaybackHistory(model, shuffle)
0154     }
0155 
0156     function focusSearch(){searchLoader.forceFocus()}
0157 
0158     Connections {
0159         target: ErrorHandler
0160 
0161         function onErrorOccurred(error) {
0162             showPassiveNotification(error)
0163         }
0164     }
0165 
0166     Component {
0167         id: searchAlbum
0168 
0169         ColumnLayout {
0170             id: mpdelegateItem
0171 
0172             required property string videoId
0173             required property string title
0174 
0175             width: 90
0176             Layout.maximumWidth: 70
0177 
0178             Kirigami.ShadowedRectangle {
0179                 id: recCover
0180                 MouseArea {
0181                     id: recArea
0182                     anchors.fill: parent
0183                     onClicked: play(mpdelegateItem.videoId)
0184                     hoverEnabled: !Kirigami.Settings.hasTransientTouchInput
0185                     onEntered: {
0186                         if (!Kirigami.Settings.hasTransientTouchInput) {
0187                             recSelected.visible = true
0188                             searchTitle.color = Kirigami.Theme.hoverColor
0189                             searchTitle.font.bold = true
0190                         }
0191                     }
0192                     onExited: {
0193                         recSelected.visible = false
0194                         searchTitle.color = Kirigami.Theme.textColor
0195                         searchTitle.font.bold = false
0196                     }
0197                 }
0198                 Layout.margins: 5
0199 
0200                 width: 70
0201                 height: 70
0202                 radius: 10
0203                 shadow.size: 15
0204                 shadow.xOffset: 5
0205                 shadow.yOffset: 5
0206                 shadow.color: Qt.rgba(0, 0, 0, 0.2)
0207                 Rectangle {
0208                     width: 70
0209                     height: 70
0210 
0211                     color: "transparent"
0212 
0213                     ThumbnailSource {
0214                         id: thumbnailSource
0215                         videoId: mpdelegateItem.videoId
0216                     }
0217                     RoundedImage {
0218                         source: thumbnailSource.cachedPath
0219                         height: parent.height
0220                         width: height
0221                         radius: 10
0222                     }
0223                     Rectangle {
0224                         id: recSelected
0225 
0226                         Rectangle {
0227                             anchors.fill: parent
0228                             color: Kirigami.Theme.hoverColor
0229                             radius: 10
0230                             opacity: 0.2
0231                         }
0232 
0233                         visible: false
0234                         anchors.fill: parent
0235 
0236                         radius: 9
0237 
0238                         border.color: Kirigami.Theme.hoverColor
0239                         border.width: 2
0240                         color: "transparent"
0241                     }
0242                 }
0243             }
0244             Controls.Label {
0245                 id: searchTitle
0246                 leftPadding:5
0247                 Layout.maximumWidth: 70
0248                 text: mpdelegateItem.title
0249                 elide: Qt.ElideRight
0250                 wrapMode: Text.WordWrap
0251                 Layout.maximumHeight: 40
0252             }
0253             Item {
0254                 height: 5
0255             }
0256         }
0257     }
0258 
0259 
0260     pageStack.anchors.bottomMargin: wideScreen ? playerFooter.minimizedPlayerHeight: playerFooter.minimizedPlayerHeight+bottombar.height
0261     pageStack.anchors.leftMargin: wideScreen ? sidebar.width:0
0262 
0263     // media player
0264     PlayerFooter {
0265         id: playerFooter
0266         anchors.topMargin: -root.header.height
0267         anchors.fill: parent
0268         footerSpacing: wideScreen ? 0 : bottombar.height
0269 
0270         // only expand flicking area to full screen when it is open
0271         z: (contentY === 0) ? -1 : 999
0272     }
0273 
0274     MprisPlayer {
0275         id: mprisPlayer
0276         serviceName: "AudioTube"
0277 
0278         property string artist: playerFooter.videoInfoExtractor.artist
0279         property string songTitle: playerFooter.videoInfoExtractor.title ? playerFooter.videoInfoExtractor.title : i18n("No song playing")
0280         property int songLength: playerFooter.audioPlayer.duration * 1000
0281         property alias thumbnail: playerFooter.thumbnail
0282 
0283         function next() {
0284             if(UserPlaylistModel.canSkip) {
0285                 UserPlaylistModel.next()
0286             }
0287             else{
0288                 playerFooter.audioPlayer.stop()
0289             }
0290         }
0291 
0292         //Mpris2 Root interface
0293         identity: root.title
0294         supportedUriSchemes: []
0295         supportedMimeTypes: []
0296         desktopEntry: "org.kde.audiotube"
0297 
0298         //Mpris2 Player Interface
0299         canControl: true
0300         canGoNext: UserPlaylistModel.canSkip
0301         canGoPrevious: UserPlaylistModel.canSkipBack
0302         canPause: playerFooter.audioPlayer.status !== MediaPlayer.NoMedia
0303         canPlay: playerFooter.audioPlayer.status !== MediaPlayer.NoMedia
0304         canSeek: false
0305 
0306         playbackStatus: playerFooter.audioPlayer.playbackState === MediaPlayer.PlayingState ? Mpris.Playing : (playerFooter.audioPlayer.playbackState == MediaPlayer.PausedState ? Mpris.Paused : Mpris.Stopped)
0307         shuffle: false
0308         volume: playerFooter.audioOutput.muted ? 0.0 : playerFooter.audioOutput.volume
0309         position: playerFooter.audioPlayer.position * 1000
0310 
0311         onPauseRequested: playerFooter.audioPlayer.pause()
0312         onPlayRequested: playerFooter.audioPlayer.play()
0313         onPlayPauseRequested: {
0314             if(playerFooter.audioPlayer.playbackStatus === MediaPlayer.PlayingState) {
0315                 playerFooter.audioPlayer.pause()
0316             }
0317             else if(playerFooter.PlayingState === MediaPlayer.PausedState) {
0318                 PlayerFooter.audioPlayer.play()
0319             }
0320         }
0321         onStopRequested: playerFooter.audioPlayer.stop()
0322         onNextRequested: {
0323             next()
0324         }
0325         onPreviousRequested: {
0326             if(UserPlaylistModel.canSkipBack) {
0327                 UserPlaylistModel.previous()
0328             }
0329             else{
0330                 playerFooter.audioPlayer.stop()
0331             }
0332         }
0333         onSeekRequested: {
0334             if(canSeek) {
0335                 if(playerFooter.audioPlayer.position + offset/1000 < 0) {
0336                     playerFooter.audioPlayer.seek(0)
0337                 }
0338                 else if(playerFooter.audioPlayer.position + offset/1000 > playerFooter.audioPlayer.duration) {
0339                     next()
0340                 }
0341                 else {
0342                     playerFooter.audioPlayer.seek(Math.floor(playerFooter.audioPlayer.position + offset/1000));
0343                 }
0344                 emitSeeked()
0345             }
0346         }
0347         onSetPositionRequested: {
0348             if(canSeek) {
0349                 if(position >= 0 && position/1000 <= playerFooter.audioPlayer.duration) {
0350                     playerFooter.audioPlayer.seek(Math.floor(position/1000))
0351                     emitSeeked()
0352                 }
0353             }
0354         }
0355         onShuffleRequested: {
0356             UserPlaylistModel.shufflePlaylist()
0357         }
0358 
0359         onArtistChanged: {
0360             var metadata = mprisPlayer.metadata
0361             metadata[Mpris.metadataToString(Mpris.Artist)] = [artist]
0362             mprisPlayer.metadata = metadata
0363         }
0364         onSongTitleChanged: {
0365             var metadata = mprisPlayer.metadata
0366             metadata[Mpris.metadataToString(Mpris.Title)] = songTitle
0367             mprisPlayer.metadata = metadata
0368         }
0369         onSongLengthChanged: {
0370             var metadata = mprisPlayer.metadata
0371             metadata[Mpris.metadataToString(Mpris.Length)] = songLength
0372             mprisPlayer.metadata = metadata
0373         }
0374         onThumbnailChanged: {
0375             var metadata = mprisPlayer.metadata
0376             metadata[Mpris.metadataToString(Mpris.ArtUrl)] = thumbnail
0377             mprisPlayer.metadata = metadata
0378         }
0379     }
0380 }