1/* 2 SPDX-FileCopyrightText: 2016 (c) Matthieu Gallien <matthieu_gallien@yahoo.fr> 3 4 SPDX-License-Identifier: LGPL-3.0-or-later 5 */ 6 7import QtQuick 2.7 8import QtQuick.Controls 2.3 9import QtQuick.Layouts 1.1 10import QtQuick.Window 2.2 11import org.kde.kirigami 2.5 as Kirigami 12import org.kde.elisa 1.0 13import org.kde.elisa.host 1.0 14import Qt.labs.settings 1.0 15import Qt.labs.platform 1.1 16 17import "mobile" 18 19Kirigami.ApplicationWindow { 20 id: mainWindow 21 22 visible: true 23 24 contextDrawer: Kirigami.ContextDrawer { 25 id: playlistDrawer 26 handleClosedIcon.source: "view-media-playlist" 27 handleOpenIcon.source: "view-right-close" 28 29 handle.visible: !Kirigami.Settings.isMobile 30 Component.onCompleted: close() // drawer is opened when the layout is narrow, we want it closed 31 32 // without this drawer button is never shown 33 enabled: true 34 MediaPlayListView { 35 anchors.fill: parent 36 onStartPlayback: ElisaApplication.audioControl.ensurePlay() 37 onPausePlayback: ElisaApplication.audioControl.playPause() 38 } 39 } 40 41 // HACK: since elisa's main view hasn't been ported to a page, but page layers are used for mobile settings 42 // lower the main view and mobile footer's z to be behind the layer when there are layers added (normally it is in front) 43 property bool layerOnTop: pageStack.layers.depth > 1 44 45 minimumWidth: Kirigami.Settings.isMobile ? 320 : 620 46 property int minHeight: 320 47 48 LayoutMirroring.enabled: Qt.application.layoutDirection == Qt.RightToLeft 49 LayoutMirroring.childrenInherit: true 50 51 x: persistentSettings.x 52 y: persistentSettings.y 53 width: persistentSettings.width 54 height: persistentSettings.height 55 56 title: i18n("Elisa") 57 58 Accessible.role: Accessible.Application 59 Accessible.name: title 60 61 readonly property int initialViewIndex: 3 62 63 property var goBackAction: ElisaApplication.action("go_back") 64 property var seekAction: ElisaApplication.action("Seek") 65 property var scrubAction: ElisaApplication.action("Scrub") 66 property var nextTrackAction : ElisaApplication.action("NextTrack") 67 property var previousTrackAction: ElisaApplication.action("PreviousTrack") 68 property var playPauseAction: ElisaApplication.action("Play-Pause") 69 property var findAction: ElisaApplication.action("edit_find") 70 property var togglePartyModeAction: ElisaApplication.action("togglePartyMode") 71 72 property var mediaPlayerControl: Kirigami.Settings.isMobile ? mobileFooterBarLoader.item : headerBarLoader.item 73 74 Action { 75 shortcut: goBackAction.shortcut 76 onTriggered: contentView.goBack() 77 } 78 79 Action { 80 shortcut: seekAction.shortcut 81 onTriggered: ElisaApplication.audioControl.seek(mediaPlayerControl.playerControl.position + 10000) 82 } 83 84 Action { 85 shortcut: scrubAction.shortcut 86 onTriggered: ElisaApplication.audioControl.seek(mediaPlayerControl.playerControl.position - 10000) 87 } 88 89 Action { 90 shortcut: nextTrackAction.shortcut 91 onTriggered: ElisaApplication.mediaPlayListProxyModel.skipNextTrack(ElisaApplication.audioPlayer.position) 92 } 93 94 Action { 95 shortcut: previousTrackAction.shortcut 96 onTriggered: ElisaApplication.mediaPlayListProxyModel.skipPreviousTrack(ElisaApplication.audioPlayer.position) 97 } 98 99 Action { 100 shortcut: playPauseAction.shortcut 101 onTriggered: ElisaApplication.audioControl.playPause() 102 } 103 104 Action { 105 shortcut: findAction.shortcut 106 onTriggered: persistentSettings.expandedFilterView = !persistentSettings.expandedFilterView 107 } 108 109 Action { 110 shortcut: togglePartyModeAction.shortcut 111 onTriggered: mediaPlayerControl.isMaximized = !mediaPlayerControl.isMaximized 112 } 113 114 SystemPalette { 115 id: myPalette 116 colorGroup: SystemPalette.Active 117 } 118 119 Theme { 120 id: elisaTheme 121 } 122 123 Settings { 124 id: persistentSettings 125 126 property int x 127 property int y 128 property int width : 900 129 property int height : 650 130 131 property var playListState 132 133 property var audioPlayerState 134 135 property double playControlItemVolume : 100.0 136 property bool playControlItemMuted : false 137 138 property bool expandedFilterView: false 139 140 property bool showPlaylist: true 141 142 property bool headerBarIsMaximized: false 143 144 property bool nowPlayingPreferLyric: false 145 } 146 147 Connections { 148 target: Qt.application 149 function onAboutToQuit() { 150 persistentSettings.x = mainWindow.x; 151 persistentSettings.y = mainWindow.y; 152 persistentSettings.width = mainWindow.width; 153 persistentSettings.height = mainWindow.height; 154 155 persistentSettings.playListState = ElisaApplication.mediaPlayListProxyModel.persistentState; 156 persistentSettings.audioPlayerState = ElisaApplication.audioControl.persistentState 157 158 persistentSettings.playControlItemVolume = mediaPlayerControl.playerControl.volume 159 persistentSettings.playControlItemMuted = mediaPlayerControl.playerControl.muted 160 161 persistentSettings.showPlaylist = contentView.showPlaylist 162 163 if (Kirigami.Settings.isMobile) { 164 persistentSettings.headerBarIsMaximized = mobileFooterBarLoader.item.isMaximized 165 } else { 166 persistentSettings.headerBarIsMaximized = headerBarLoader.item.isMaximized 167 } 168 } 169 } 170 171 Loader { 172 id: mprisloader 173 active: false 174 175 sourceComponent: PlatformIntegration { 176 id: platformInterface 177 178 playListModel: ElisaApplication.mediaPlayListProxyModel 179 audioPlayerManager: ElisaApplication.audioControl 180 player: ElisaApplication.audioPlayer 181 headerBarManager: ElisaApplication.manageHeaderBar 182 manageMediaPlayerControl: ElisaApplication.playerControl 183 showProgressOnTaskBar: ElisaApplication.showProgressOnTaskBar 184 showSystemTrayIcon: ElisaApplication.showSystemTrayIcon 185 elisaMainWindow: mainWindow 186 187 function onRaisePlayer() { 188 mainWindow.visible = true 189 mainWindow.raise() 190 mainWindow.requestActivate() 191 } 192 193 } 194 } 195 196 Connections { 197 target: ElisaApplication.audioPlayer 198 function onVolumeChanged() { 199 if (mediaPlayerControl != null) { 200 mediaPlayerControl.playerControl.volume = ElisaApplication.audioPlayer.volume 201 } 202 } 203 function onMutedChanged() { 204 if (mediaPlayerControl != null) { 205 mediaPlayerControl.playerControl.muted = ElisaApplication.audioPlayer.muted 206 } 207 } 208 } 209 210 // track import notification 211 TrackImportNotification { 212 z: 2 213 id: importedTracksCountNotification 214 215 anchors { 216 right: mainContent.right 217 top: mainContent.top 218 rightMargin: Kirigami.Units.largeSpacing * 2 219 topMargin: Kirigami.Units.largeSpacing * 3 220 } 221 } 222 223 Binding { 224 id: indexerBusyBinding 225 226 target: importedTracksCountNotification 227 property: 'indexingRunning' 228 value: ElisaApplication.musicManager.indexerBusy 229 when: ElisaApplication.musicManager !== undefined 230 } 231 232 Binding { 233 target: importedTracksCountNotification 234 property: 'importedTracksCount' 235 value: ElisaApplication.musicManager.importedTracksCount 236 when: ElisaApplication.musicManager !== undefined 237 } 238 239 // mobile footer bar 240 Loader { 241 id: mobileFooterBarLoader 242 anchors.fill: parent 243 244 active: Kirigami.Settings.isMobile 245 visible: active 246 247 // footer bar fills the whole page, so only be in front of the main view when it is opened 248 // otherwise, it captures all mouse/touch events on the main view 249 z: (!item || item.contentY == 0) ? (mainWindow.layerOnTop ? -1 : 0) : 999 250 251 sourceComponent: MobileFooterBar { 252 id: mobileFooterBar 253 contentHeight: mainWindow.height * 2 254 255 focus: true 256 257 album: (ElisaApplication.manageHeaderBar.album !== undefined ? ElisaApplication.manageHeaderBar.album : '') 258 title: ElisaApplication.manageHeaderBar.title 259 artist: (ElisaApplication.manageHeaderBar.artist !== undefined ? ElisaApplication.manageHeaderBar.artist : '') 260 albumArtist: (ElisaApplication.manageHeaderBar.albumArtist !== undefined ? ElisaApplication.manageHeaderBar.albumArtist : '') 261 image: ElisaApplication.manageHeaderBar.image 262 albumID: ElisaApplication.manageHeaderBar.albumId 263 264 ratingVisible: false 265 266 // since we have multiple volume bars, and only one is linked directly to audio, sync the other one (trackControl) 267 Binding on playerControl.volume { 268 when: mobileFooterBar.trackControl.volumeSlider.moved 269 value: mobileFooterBar.trackControl.volume 270 } 271 Component.onCompleted: { 272 trackControl.volume = Qt.binding(function() { return mobileFooterBar.playerControl.volume }) 273 } 274 275 onOpenArtist: { contentView.openArtist(artist) } 276 onOpenNowPlaying: { contentView.openNowPlaying() } 277 onOpenAlbum: { contentView.openAlbum(album, albumArtist, image, albumID) } 278 } 279 } 280 281 Rectangle { 282 id: mainContent 283 284 visible: !mainWindow.layerOnTop 285 286 color: myPalette.base 287 anchors.fill: parent 288 anchors.bottomMargin: Kirigami.Settings.isMobile? elisaTheme.mediaPlayerControlHeight : 0 289 290 ColumnLayout { 291 anchors.fill: parent 292 spacing: 0 293 294 // desktop header bar 295 Loader { 296 id: headerBarLoader 297 active: !Kirigami.Settings.isMobile 298 visible: active 299 300 Layout.minimumHeight: Math.round(mainWindow.height * 0.2 + elisaTheme.mediaPlayerControlHeight) 301 Layout.maximumHeight: Layout.minimumHeight 302 Layout.fillWidth: true 303 304 sourceComponent: HeaderBar { 305 id: headerBar 306 307 focus: true 308 309 album: (ElisaApplication.manageHeaderBar.album !== undefined ? ElisaApplication.manageHeaderBar.album : '') 310 title: ElisaApplication.manageHeaderBar.title 311 artist: (ElisaApplication.manageHeaderBar.artist !== undefined ? ElisaApplication.manageHeaderBar.artist : '') 312 albumArtist: (ElisaApplication.manageHeaderBar.albumArtist !== undefined ? ElisaApplication.manageHeaderBar.albumArtist : '') 313 image: ElisaApplication.manageHeaderBar.image 314 albumID: ElisaApplication.manageHeaderBar.albumId 315 316 ratingVisible: false 317 318 playerControl.isMaximized: persistentSettings.headerBarIsMaximized 319 onOpenArtist: { contentView.openArtist(artist) } 320 onOpenNowPlaying: { contentView.openNowPlaying() } 321 onOpenAlbum: { contentView.openAlbum(album, albumArtist, image, albumID) } 322 323 // animations 324 StateGroup { 325 id: mainWindowState 326 states: [ 327 State { 328 name: "headerBarIsNormal" 329 when: !headerBar.isMaximized 330 changes: [ 331 PropertyChanges { 332 target: mainWindow 333 minimumHeight: mainWindow.minHeight * 1.5 334 explicit: true 335 }, 336 PropertyChanges { 337 target: headerBarLoader 338 Layout.minimumHeight: Math.round(mainWindow.height * 0.2 + elisaTheme.mediaPlayerControlHeight) 339 Layout.maximumHeight: Layout.minimumHeight 340 } 341 ] 342 }, 343 State { 344 name: "headerBarIsMaximized" 345 when: headerBar.isMaximized 346 changes: [ 347 PropertyChanges { 348 target: mainWindow 349 minimumHeight: mainWindow.minHeight 350 explicit: true 351 }, 352 PropertyChanges { 353 target: headerBarLoader 354 Layout.minimumHeight: mainWindow.height 355 Layout.maximumHeight: mainWindow.height 356 }, 357 PropertyChanges { 358 target: playlistDrawer 359 collapsed: true 360 visible: false 361 drawerOpen: false 362 handleVisible: false 363 } 364 ] 365 } 366 ] 367 transitions: Transition { 368 NumberAnimation { 369 properties: "Layout.minimumHeight, Layout.maximumHeight, minimumHeight" 370 easing.type: Easing.InOutQuad 371 duration: Kirigami.Units.longDuration 372 } 373 } 374 } 375 } 376 } 377 378 Kirigami.Separator { 379 visible: !Kirigami.Settings.isMobile 380 Layout.fillWidth: true 381 } 382 383 ContentView { 384 id: contentView 385 Layout.fillHeight: true 386 Layout.fillWidth: true 387 showPlaylist: persistentSettings.showPlaylist 388 showExpandedFilterView: persistentSettings.expandedFilterView 389 playlistDrawer: playlistDrawer 390 initialIndex: ElisaApplication.initialViewIndex 391 } 392 } 393 } 394 395 // capture mouse events behind flickable when it is open 396 MouseArea { 397 visible: Kirigami.Settings.isMobile && mobileFooterBarLoader.item.contentY != 0 // only capture when the mobile footer panel is open 398 anchors.fill: mobileFooterBarLoader 399 preventStealing: true 400 onClicked: mouse.accepted = true 401 } 402 403 Component.onCompleted: 404 { 405 ElisaApplication.initialize() 406 ElisaApplication.activateColorScheme(ElisaConfigurationDialog.colorScheme) 407 408 if (persistentSettings.playListState) { 409 ElisaApplication.mediaPlayListProxyModel.persistentState = persistentSettings.playListState 410 } 411 412 if (persistentSettings.audioPlayerState) { 413 ElisaApplication.audioControl.persistentState = persistentSettings.audioPlayerState 414 } 415 416 // it seems the header/footer bars load before settings are loaded, so we need to set their settings here 417 mediaPlayerControl.playerControl.volume = persistentSettings.playControlItemVolume; 418 mediaPlayerControl.playerControl.muted = persistentSettings.playControlItemMuted; 419 420 ElisaApplication.mediaPlayListProxyModel.shufflePlayList = Qt.binding(function() { return mediaPlayerControl.playerControl.shuffle }) 421 ElisaApplication.mediaPlayListProxyModel.repeatMode = Qt.binding(function() { return mediaPlayerControl.playerControl.repeat }) 422 ElisaApplication.audioPlayer.muted = Qt.binding(function() { return mediaPlayerControl.playerControl.muted }) 423 ElisaApplication.audioPlayer.volume = Qt.binding(function() { return mediaPlayerControl.playerControl.volume }) 424 425 mprisloader.active = true 426 427 ElisaApplication.arguments = ElisaArguments.arguments 428 429 // use global drawer on mobile 430 if (Kirigami.Settings.isMobile) { 431 globalDrawer = contentView.sidebar; 432 } 433 } 434} 435