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