Merge lp:~music-app-dev/music-app/prototype-now-playing-side-panel into lp:music-app

Proposed by Victor Thompson
Status: Work in progress
Proposed branch: lp:~music-app-dev/music-app/prototype-now-playing-side-panel
Merge into: lp:music-app
Diff against target: 1745 lines (+887/-799)
4 files modified
app/components/MusicPage.qml (+2/-0)
app/components/NowPlaying.qml (+550/-0)
app/music-app.qml (+332/-292)
app/ui/NowPlayingView.qml (+3/-507)
To merge this branch: bzr merge lp:~music-app-dev/music-app/prototype-now-playing-side-panel
Reviewer Review Type Date Requested Status
Music App Developers Pending
Review via email: mp+249144@code.launchpad.net

This proposal supersedes a proposal from 2015-02-10.

Commit message

* Conditional layouts for the music app

Description of the change

DO NOT MERGE, just want to keep this MP to see a diff of what's been added.

To post a comment you must log in.
842. By Victor Thompson

Add ItemLayout to the Layouts.

Unmerged revisions

842. By Victor Thompson

Add ItemLayout to the Layouts.

841. By Victor Thompson

start conditional layout prototype

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'app/components/MusicPage.qml'
2--- app/components/MusicPage.qml 2015-01-21 19:04:07 +0000
3+++ app/components/MusicPage.qml 2015-02-11 03:14:13 +0000
4@@ -19,6 +19,7 @@
5
6 import QtQuick 2.3
7 import Ubuntu.Components 1.1
8+import Ubuntu.Layouts 1.0
9
10
11 // generic page for music, could be useful for bottomedge implementation
12@@ -28,6 +29,7 @@
13 bottomMargin: musicToolbar.visible ? musicToolbar.height : 0
14 fill: parent
15 }
16+ Layouts.item: "MusicPage"
17
18 property bool searchable: false
19 property int searchResultsCount
20
21=== added file 'app/components/NowPlaying.qml'
22--- app/components/NowPlaying.qml 1970-01-01 00:00:00 +0000
23+++ app/components/NowPlaying.qml 2015-02-11 03:14:13 +0000
24@@ -0,0 +1,550 @@
25+/*
26+ * Copyright (C) 2013, 2014, 2015
27+ * Andrew Hayzen <ahayzen@gmail.com>
28+ * Daniel Holm <d.holmen@gmail.com>
29+ * Victor Thompson <victor.thompson@gmail.com>
30+ *
31+ * This program is free software; you can redistribute it and/or modify
32+ * it under the terms of the GNU General Public License as published by
33+ * the Free Software Foundation; version 3.
34+ *
35+ * This program is distributed in the hope that it will be useful,
36+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
37+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
38+ * GNU General Public License for more details.
39+ *
40+ * You should have received a copy of the GNU General Public License
41+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
42+ */
43+
44+import QtMultimedia 5.0
45+import QtQuick 2.3
46+import QtQuick.LocalStorage 2.0
47+import Ubuntu.Components 1.1
48+import Ubuntu.Thumbnailer 0.1
49+import "../components"
50+import "../components/Flickables"
51+import "../components/HeadState"
52+import "../components/ListItemActions"
53+import "../components/Themes/Ambiance"
54+import "../logic/meta-database.js" as Library
55+import "../logic/playlists.js" as Playlists
56+
57+Item {
58+ id: nowPlayingView
59+ objectName: "nowPlayingView"
60+
61+ anchors {
62+ fill: parent
63+ }
64+
65+ property bool isListView: false
66+
67+ Item {
68+ id: fullview
69+ anchors {
70+ top: parent.top
71+ topMargin: mainView.header.height
72+ }
73+ height: parent.height - mainView.header.height - units.gu(9.5)
74+ visible: !isListView || wideAspect
75+ width: parent.width
76+
77+ BlurredBackground {
78+ id: blurredBackground
79+ anchors {
80+ left: parent.left
81+ right: parent.right
82+ top: parent.top
83+ }
84+ art: albumImage.firstSource
85+ height: parent.height - units.gu(7)
86+
87+ Item {
88+ id: albumImageContainer
89+ anchors {
90+ horizontalCenter: parent.horizontalCenter
91+ top: parent.top
92+ }
93+ height: parent.height
94+ width: parent.width
95+
96+ CoverGrid {
97+ id: albumImage
98+ anchors.centerIn: parent
99+ covers: [{art: player.currentMetaArt, author: player.currentMetaArtist, album: player.currentMetaAlbum}]
100+ size: parent.width > parent.height ? parent.height : parent.width
101+ }
102+ }
103+
104+ Rectangle {
105+ id: nowPlayingWideAspectLabelsBackground
106+ anchors.bottom: parent.bottom
107+ color: styleMusic.common.black
108+ height: nowPlayingWideAspectTitle.lineCount === 1 ? units.gu(10) : units.gu(13)
109+ opacity: 0.8
110+ width: parent.width
111+ }
112+
113+ /* Column for labels in wideAspect */
114+ Column {
115+ id: nowPlayingWideAspectLabels
116+ spacing: units.gu(1)
117+ anchors {
118+ left: parent.left
119+ leftMargin: units.gu(2)
120+ right: parent.right
121+ rightMargin: units.gu(2)
122+ top: nowPlayingWideAspectLabelsBackground.top
123+ topMargin: nowPlayingWideAspectTitle.lineCount === 1 ? units.gu(2) : units.gu(1.5)
124+ }
125+
126+ /* Title of track */
127+ Label {
128+ id: nowPlayingWideAspectTitle
129+ anchors {
130+ left: parent.left
131+ leftMargin: units.gu(1)
132+ right: parent.right
133+ rightMargin: units.gu(1)
134+ }
135+ color: styleMusic.playerControls.labelColor
136+ elide: Text.ElideRight
137+ fontSize: "x-large"
138+ maximumLineCount: 2
139+ objectName: "playercontroltitle"
140+ text: trackQueue.model.count === 0 ? "" : player.currentMetaTitle === "" ? player.currentMetaFile : player.currentMetaTitle
141+ wrapMode: Text.WordWrap
142+ }
143+
144+ /* Artist of track */
145+ Label {
146+ id: nowPlayingWideAspectArtist
147+ anchors {
148+ left: parent.left
149+ leftMargin: units.gu(1)
150+ right: parent.right
151+ rightMargin: units.gu(1)
152+ }
153+ color: styleMusic.nowPlaying.labelSecondaryColor
154+ elide: Text.ElideRight
155+ fontSize: "small"
156+ text: trackQueue.model.count === 0 ? "" : player.currentMetaArtist
157+ }
158+ }
159+
160+ /* Detect cover art swipe */
161+ MouseArea {
162+ anchors.fill: parent
163+ property string direction: "None"
164+ property real lastX: -1
165+
166+ onPressed: lastX = mouse.x
167+
168+ onReleased: {
169+ var diff = mouse.x - lastX
170+ if (Math.abs(diff) < units.gu(4)) {
171+ return;
172+ } else if (diff < 0) {
173+ player.nextSong()
174+ } else if (diff > 0) {
175+ player.previousSong()
176+ }
177+ }
178+ }
179+ }
180+
181+ /* Background for progress bar component */
182+ Rectangle {
183+ id: musicToolbarFullProgressBackground
184+ anchors {
185+ bottom: parent.bottom
186+ left: parent.left
187+ right: parent.right
188+ top: blurredBackground.bottom
189+ }
190+ color: styleMusic.common.black
191+ }
192+
193+ /* Progress bar component */
194+ Item {
195+ id: musicToolbarFullProgressContainer
196+ anchors.left: parent.left
197+ anchors.leftMargin: units.gu(3)
198+ anchors.right: parent.right
199+ anchors.rightMargin: units.gu(3)
200+ anchors.top: blurredBackground.bottom
201+ anchors.topMargin: units.gu(1)
202+ height: units.gu(3)
203+ width: parent.width
204+
205+ /* Position label */
206+ Label {
207+ id: musicToolbarFullPositionLabel
208+ anchors.top: progressSliderMusic.bottom
209+ anchors.topMargin: units.gu(-2)
210+ anchors.left: parent.left
211+ color: styleMusic.nowPlaying.labelSecondaryColor
212+ fontSize: "small"
213+ height: parent.height
214+ horizontalAlignment: Text.AlignHCenter
215+ text: durationToString(player.position)
216+ verticalAlignment: Text.AlignVCenter
217+ width: units.gu(3)
218+ }
219+
220+ Slider {
221+ id: progressSliderMusic
222+ anchors.left: parent.left
223+ anchors.right: parent.right
224+ maximumValue: player.duration // load value at startup
225+ objectName: "progressSliderShape"
226+ style: UbuntuBlueSliderStyle {}
227+ value: player.position // load value at startup
228+
229+ function formatValue(v) {
230+ if (seeking) { // update position label while dragging
231+ musicToolbarFullPositionLabel.text = durationToString(v)
232+ }
233+
234+ return durationToString(v)
235+ }
236+
237+ property bool seeking: false
238+ property bool seeked: false
239+
240+ onSeekingChanged: {
241+ if (seeking === false) {
242+ musicToolbarFullPositionLabel.text = durationToString(player.position)
243+ }
244+ }
245+
246+ onPressedChanged: {
247+ seeking = pressed
248+
249+ if (!pressed) {
250+ seeked = true
251+ player.seek(value)
252+
253+ musicToolbarFullPositionLabel.text = durationToString(value)
254+ }
255+ }
256+
257+ Connections {
258+ target: player
259+ onPositionChanged: {
260+ // seeked is a workaround for bug 1310706 as the first position after a seek is sometimes invalid (0)
261+ if (progressSliderMusic.seeking === false && !progressSliderMusic.seeked) {
262+ musicToolbarFullPositionLabel.text = durationToString(player.position)
263+ musicToolbarFullDurationLabel.text = durationToString(player.duration)
264+
265+ progressSliderMusic.value = player.position
266+ progressSliderMusic.maximumValue = player.duration
267+ }
268+
269+ progressSliderMusic.seeked = false;
270+ }
271+ onStopped: {
272+ musicToolbarFullPositionLabel.text = durationToString(0);
273+ musicToolbarFullDurationLabel.text = durationToString(0);
274+ }
275+ }
276+ }
277+
278+ /* Duration label */
279+ Label {
280+ id: musicToolbarFullDurationLabel
281+ anchors.top: progressSliderMusic.bottom
282+ anchors.topMargin: units.gu(-2)
283+ anchors.right: parent.right
284+ color: styleMusic.nowPlaying.labelSecondaryColor
285+ fontSize: "small"
286+ height: parent.height
287+ horizontalAlignment: Text.AlignHCenter
288+ text: durationToString(player.duration)
289+ verticalAlignment: Text.AlignVCenter
290+ width: units.gu(3)
291+ }
292+ }
293+ }
294+
295+ Loader {
296+ id: queueListLoader
297+ anchors {
298+ fill: parent
299+ }
300+ asynchronous: true
301+ sourceComponent: MultiSelectListView {
302+ id: queueList
303+ anchors {
304+ bottomMargin: musicToolbarFullContainer.height + units.gu(2)
305+ fill: parent
306+ topMargin: units.gu(2)
307+ }
308+ delegate: queueDelegate
309+ footer: Item {
310+ height: mainView.height - (styleMusic.common.expandHeight + queueList.currentHeight) + units.gu(8)
311+ }
312+ model: trackQueue.model
313+ objectName: "nowPlayingqueueList"
314+
315+ property int normalHeight: units.gu(6)
316+ property int transitionDuration: 250 // transition length of animations
317+
318+ onCountChanged: customdebug("Queue: Now has: " + queueList.count + " tracks")
319+
320+ Component {
321+ id: queueDelegate
322+ ListItemWithActions {
323+ id: queueListItem
324+ color: player.currentIndex === index ? "#2c2c34" : styleMusic.mainView.backgroundColor
325+ height: queueList.normalHeight
326+ objectName: "nowPlayingListItem" + index
327+ state: ""
328+
329+ leftSideAction: Remove {
330+ onTriggered: trackQueue.removeQueueList([index])
331+ }
332+ multiselectable: true
333+ reorderable: true
334+ rightSideActions: [
335+ AddToPlaylist{
336+
337+ }
338+ ]
339+
340+ onItemClicked: {
341+ customdebug("File: " + model.filename) // debugger
342+ trackQueueClick(index); // toggle track state
343+ }
344+ onReorder: {
345+ console.debug("Move: ", from, to);
346+
347+ trackQueue.model.move(from, to, 1);
348+ Library.moveQueueItem(from, to);
349+
350+ // Maintain currentIndex with current song
351+ if (from === player.currentIndex) {
352+ player.currentIndex = to;
353+ }
354+ else if (from < player.currentIndex && to >= player.currentIndex) {
355+ player.currentIndex -= 1;
356+ }
357+ else if (from > player.currentIndex && to <= player.currentIndex) {
358+ player.currentIndex += 1;
359+ }
360+
361+ queueIndex = player.currentIndex
362+ }
363+
364+ Item {
365+ id: trackContainer;
366+ anchors {
367+ fill: parent
368+ }
369+
370+ NumberAnimation {
371+ id: trackContainerReorderAnimation
372+ target: trackContainer;
373+ property: "anchors.leftMargin";
374+ duration: queueList.transitionDuration;
375+ to: units.gu(2)
376+ }
377+
378+ NumberAnimation {
379+ id: trackContainerResetAnimation
380+ target: trackContainer;
381+ property: "anchors.leftMargin";
382+ duration: queueList.transitionDuration;
383+ to: units.gu(0.5)
384+ }
385+
386+ MusicRow {
387+ id: musicRow
388+ height: parent.height
389+ column: Column {
390+ Label {
391+ id: trackTitle
392+ color: player.currentIndex === index ? UbuntuColors.blue
393+ : styleMusic.common.music
394+ fontSize: "small"
395+ objectName: "titleLabel"
396+ text: model.title
397+ }
398+
399+ Label {
400+ id: trackArtist
401+ color: styleMusic.common.subtitle
402+ fontSize: "x-small"
403+ objectName: "artistLabel"
404+ text: model.author
405+ }
406+ }
407+ }
408+ }
409+ }
410+ }
411+ }
412+ visible: isListView || wideAspect
413+ }
414+
415+ /* Full toolbar */
416+ Rectangle {
417+ id: musicToolbarFullContainer
418+ anchors.bottom: parent.bottom
419+ color: styleMusic.common.black
420+ height: units.gu(10)
421+ width: parent.width
422+
423+ /* Repeat button */
424+ MouseArea {
425+ id: nowPlayingRepeatButton
426+ anchors.right: nowPlayingPreviousButton.left
427+ anchors.rightMargin: units.gu(1)
428+ anchors.verticalCenter: nowPlayingPlayButton.verticalCenter
429+ height: units.gu(6)
430+ opacity: player.repeat && !emptyPageLoader.noMusic ? 1 : .4
431+ width: height
432+ onClicked: player.repeat = !player.repeat
433+
434+ Icon {
435+ id: repeatIcon
436+ height: units.gu(3)
437+ width: height
438+ anchors.verticalCenter: parent.verticalCenter
439+ anchors.horizontalCenter: parent.horizontalCenter
440+ color: "white"
441+ name: "media-playlist-repeat"
442+ objectName: "repeatShape"
443+ opacity: player.repeat && !emptyPageLoader.noMusic ? 1 : .4
444+ }
445+ }
446+
447+ /* Previous button */
448+ MouseArea {
449+ id: nowPlayingPreviousButton
450+ anchors.right: nowPlayingPlayButton.left
451+ anchors.rightMargin: units.gu(1)
452+ anchors.verticalCenter: nowPlayingPlayButton.verticalCenter
453+ height: units.gu(6)
454+ opacity: trackQueue.model.count === 0 ? .4 : 1
455+ width: height
456+ onClicked: player.previousSong()
457+
458+ Icon {
459+ id: nowPlayingPreviousIndicator
460+ height: units.gu(3)
461+ width: height
462+ anchors.verticalCenter: parent.verticalCenter
463+ anchors.horizontalCenter: parent.horizontalCenter
464+ color: "white"
465+ name: "media-skip-backward"
466+ objectName: "previousShape"
467+ opacity: 1
468+ }
469+ }
470+
471+ /* Play/Pause button */
472+ MouseArea {
473+ id: nowPlayingPlayButton
474+ anchors.centerIn: parent
475+ height: units.gu(10)
476+ width: height
477+ onClicked: player.toggle()
478+
479+ Icon {
480+ id: nowPlayingPlayIndicator
481+ height: units.gu(6)
482+ width: height
483+ anchors.verticalCenter: parent.verticalCenter
484+ anchors.horizontalCenter: parent.horizontalCenter
485+ opacity: emptyPageLoader.noMusic ? .4 : 1
486+ color: "white"
487+ name: player.playbackState === MediaPlayer.PlayingState ? "media-playback-pause" : "media-playback-start"
488+ objectName: "playShape"
489+ }
490+ }
491+
492+ /* Next button */
493+ MouseArea {
494+ id: nowPlayingNextButton
495+ anchors.left: nowPlayingPlayButton.right
496+ anchors.leftMargin: units.gu(1)
497+ anchors.verticalCenter: nowPlayingPlayButton.verticalCenter
498+ height: units.gu(6)
499+ opacity: trackQueue.model.count === 0 ? .4 : 1
500+ width: height
501+ onClicked: player.nextSong()
502+
503+ Icon {
504+ id: nowPlayingNextIndicator
505+ height: units.gu(3)
506+ width: height
507+ anchors.verticalCenter: parent.verticalCenter
508+ anchors.horizontalCenter: parent.horizontalCenter
509+ color: "white"
510+ name: "media-skip-forward"
511+ objectName: "forwardShape"
512+ opacity: 1
513+ }
514+ }
515+
516+ /* Shuffle button */
517+ MouseArea {
518+ id: nowPlayingShuffleButton
519+ anchors.left: nowPlayingNextButton.right
520+ anchors.leftMargin: units.gu(1)
521+ anchors.verticalCenter: nowPlayingPlayButton.verticalCenter
522+ height: units.gu(6)
523+ opacity: player.shuffle && !emptyPageLoader.noMusic ? 1 : .4
524+ width: height
525+ onClicked: player.shuffle = !player.shuffle
526+
527+ Icon {
528+ id: shuffleIcon
529+ height: units.gu(3)
530+ width: height
531+ anchors.verticalCenter: parent.verticalCenter
532+ anchors.horizontalCenter: parent.horizontalCenter
533+ color: "white"
534+ name: "media-playlist-shuffle"
535+ objectName: "shuffleShape"
536+ opacity: player.shuffle && !emptyPageLoader.noMusic ? 1 : .4
537+ }
538+ }
539+
540+ /* Object which provides the progress bar when in the queue */
541+ Rectangle {
542+ id: playerControlsProgressBar
543+ anchors {
544+ bottom: parent.bottom
545+ left: parent.left
546+ right: parent.right
547+ }
548+ color: styleMusic.common.black
549+ height: units.gu(0.25)
550+ visible: isListView || wideAspect
551+
552+ Rectangle {
553+ id: playerControlsProgressBarHint
554+ anchors {
555+ left: parent.left
556+ bottom: parent.bottom
557+ }
558+ color: UbuntuColors.blue
559+ height: parent.height
560+ width: player.duration > 0 ? (player.position / player.duration) * playerControlsProgressBar.width : 0
561+
562+ Connections {
563+ target: player
564+ onPositionChanged: {
565+ playerControlsProgressBarHint.width = (player.position / player.duration) * playerControlsProgressBar.width
566+ }
567+ onStopped: {
568+ playerControlsProgressBarHint.width = 0;
569+ }
570+ }
571+ }
572+ }
573+ }
574+}
575
576=== modified file 'app/music-app.qml'
577--- app/music-app.qml 2015-02-08 04:40:52 +0000
578+++ app/music-app.qml 2015-02-11 03:14:13 +0000
579@@ -22,6 +22,7 @@
580 import Ubuntu.Components.Popups 1.0
581 import Ubuntu.Components.ListItems 1.0 as ListItem
582 import Ubuntu.Content 0.1
583+import Ubuntu.Layouts 1.0
584 import Ubuntu.MediaScanner 0.1
585 import Qt.labs.settings 1.0
586 import QtMultimedia 5.0
587@@ -966,300 +967,339 @@
588 }
589 }
590
591- Loader {
592- id: musicToolbar
593- anchors {
594- bottom: parent.bottom
595- left: parent.left
596- right: parent.right
597- }
598- asynchronous: true
599- source: "components/MusicToolbar.qml"
600- visible: mainPageStack.currentPage.title !== i18n.tr("Now playing") &&
601- mainPageStack.currentPage.title !== i18n.tr("Queue") &&
602- mainPageStack.currentPage.title !== i18n.tr("Select playlist") &&
603- !firstRun
604- z: 200 // put on top of everything else
605- }
606-
607- PageStack {
608- id: mainPageStack
609-
610- // Properties storing the current page info
611- property Page currentMusicPage: null // currentPage can be Tabs
612- property bool popping: false
613-
614- /* Helper functions */
615-
616- // Go back up the stack if possible
617- function goBack() {
618- if (depth > 1) {
619- pop()
620- }
621- }
622-
623- // Pop a specific page in the stack
624- function popPage(page) {
625- var tmpPages = []
626-
627- popping = true
628-
629- while (currentPage !== page && depth > 0) {
630- tmpPages.push(currentPage)
631- pop()
632- }
633-
634- if (depth > 0) {
635- pop()
636- }
637-
638- for (var i=tmpPages.length - 1; i > -1; i--) {
639- push(tmpPages[i])
640- }
641-
642- popping = false
643- }
644-
645- // Set the current page, and any parent/stacks
646- function setPage(childPage) {
647- if (!popping) {
648- currentMusicPage = childPage;
649- }
650- }
651-
652- Tabs {
653- id: tabs
654+ Layouts {
655+ objectName: "musicLayout"
656+ id: layouts
657+ anchors.fill: parent
658+ layouts: [
659+ ConditionalLayout {
660+ name: "wideAspect"
661+ when: wideAspect
662+ Rectangle {
663+ anchors.fill: parent
664+ color: "transparent"
665+ ItemLayout {
666+ item: "MusicPage"
667+ width: layouts.width*0.625
668+ anchors {
669+ top: parent.top
670+ bottom: parent.bottom
671+ }
672+ }
673+ Rectangle {
674+ width: layouts.width*0.375
675+ anchors {
676+ top: parent.top
677+ bottom:parent.bottom
678+ right: parent.right
679+ }
680+ color: "transparent"
681+
682+ NowPlaying {
683+ id: nowPlaying
684+ Layouts.item: "nowPlaying"
685+ }
686+ }
687+ }
688+ }
689+ ]
690+
691+ Loader {
692+ id: musicToolbar
693+ anchors {
694+ bottom: parent.bottom
695+ left: parent.left
696+ right: parent.right
697+ }
698+ asynchronous: true
699+ source: "components/MusicToolbar.qml"
700+ visible: mainPageStack.currentPage.title !== i18n.tr("Now playing") &&
701+ mainPageStack.currentPage.title !== i18n.tr("Queue") &&
702+ mainPageStack.currentPage.title !== i18n.tr("Select playlist") &&
703+ !firstRun &&
704+ !wideAspect
705+ z: 200 // put on top of everything else
706+ }
707+
708+ PageStack {
709+ id: mainPageStack
710+
711+ // Properties storing the current page info
712+ property Page currentMusicPage: null // currentPage can be Tabs
713+ property bool popping: false
714+
715+ /* Helper functions */
716+
717+ // Go back up the stack if possible
718+ function goBack() {
719+ if (depth > 1) {
720+ pop()
721+ }
722+ }
723+
724+ // Pop a specific page in the stack
725+ function popPage(page) {
726+ var tmpPages = []
727+
728+ popping = true
729+
730+ while (currentPage !== page && depth > 0) {
731+ tmpPages.push(currentPage)
732+ pop()
733+ }
734+
735+ if (depth > 0) {
736+ pop()
737+ }
738+
739+ for (var i=tmpPages.length - 1; i > -1; i--) {
740+ push(tmpPages[i])
741+ }
742+
743+ popping = false
744+ }
745+
746+ // Set the current page, and any parent/stacks
747+ function setPage(childPage) {
748+ if (!popping) {
749+ currentMusicPage = childPage;
750+ }
751+ }
752+
753+ Tabs {
754+ id: tabs
755+ anchors {
756+ fill: parent
757+ }
758+
759+ property Tab lastTab: selectedTab
760+
761+ onSelectedTabChanged: {
762+ // pause loading of the models in the old tab
763+ if (lastTab !== null && lastTab !== selectedTab) {
764+ allowLoading(lastTab, false);
765+ }
766+
767+ lastTab = selectedTab;
768+
769+ ensurePopulated(selectedTab);
770+ }
771+
772+ onSelectedTabIndexChanged: {
773+ if (loadedUI) { // store the tab index if changed by the user
774+ startupSettings.tabIndex = selectedTabIndex
775+ }
776+ }
777+
778+ // Use a repeater to 'hide' the recent tab when the model is empty
779+ // A repeater is used because the Tabs component respects adds and
780+ // removes. Whereas replacing the list tabChildren does not appear
781+ // to respect removes and setting the page as active: false causes
782+ // the page to be blank but the action to still in the overflow
783+ Repeater {
784+ id: recentTabRepeater
785+ // If the model has not loaded and at startup the db was not empty
786+ // then show recent or
787+ // If the workerlist has been set and it has values then show recent
788+ model: (!recentModel.preLoadComplete && !startupRecentEmpty) ||
789+ (recentModel.workerList !== undefined &&
790+ recentModel.workerList.length > 0) ? 1 : 0
791+ delegate: Component {
792+ // First tab is all music
793+ Tab {
794+ property bool populated: recentTabRepeater.populated
795+ property var loader: [recentModel.filterRecent]
796+ property bool loading: recentTabRepeater.loading
797+ property var model: [recentModel, albumTracksModel]
798+ id: recentTab
799+ objectName: "recentTab"
800+ anchors.fill: parent
801+ title: page.title
802+
803+ // Tab content begins here
804+ page: Recent {
805+ id: recentPage
806+ }
807+ }
808+ }
809+
810+ // Store the startup state of the db separately otherwise
811+ // it breaks the binding of model
812+ property bool startupRecentEmpty: Library.isRecentEmpty()
813+
814+ // cached values of the recent model that are copied when
815+ // the tab is created
816+ property bool loading: false
817+ property bool populated: false
818+
819+ onCountChanged: {
820+ if (count === 0 && loadedUI) {
821+ // Jump to the albums tab when recent is empty
822+ tabs.selectedTabIndex = albumsTab.index
823+ } else if (count > 0 && !loadedUI) {
824+ // UI is still loading and recent tab has been inserted
825+ // so move the selected index 'down' as the value is
826+ // not auto updated - this is for the case of loading
827+ // directly to the recent tab (otherwise the content
828+ // appears as the second tab but the tabs think they are
829+ // on the first tab)
830+ tabs.selectedTabIndex -= 1
831+ } else if (count > 0 && loadedUI) {
832+ // tab inserted while the app is running so move the
833+ // selected index 'up' to keep the same position
834+ tabs.selectedTabIndex += 1
835+ }
836+ }
837+ }
838+
839+ // Second tab is arists
840+ Tab {
841+ property bool populated: true
842+ property var loader: []
843+ property bool loading: false
844+ property var model: []
845+ id: artistsTab
846+ objectName: "artistsTab"
847+ anchors.fill: parent
848+ title: page.title
849+
850+ // tab content
851+ page: Artists {
852+ id: artistsPage
853+ }
854+ }
855+
856+ // third tab is albums
857+ Tab {
858+ property bool populated: true
859+ property var loader: []
860+ property bool loading: false
861+ property var model: []
862+ id: albumsTab
863+ objectName: "albumsTab"
864+ anchors.fill: parent
865+ title: page.title
866+
867+ // Tab content begins here
868+ page: Albums {
869+ id: albumsPage
870+ }
871+ }
872+
873+ // forth tab is genres
874+ Tab {
875+ property bool populated: true
876+ property var loader: []
877+ property bool loading: false
878+ property var model: []
879+ id: genresTab
880+ objectName: "genresTab"
881+ anchors.fill: parent
882+ title: page.title
883+
884+ // Tab content begins here
885+ page: Genres {
886+ id: genresPage
887+ }
888+ }
889+
890+ // fourth tab is all songs
891+ Tab {
892+ property bool populated: true
893+ property var loader: []
894+ property bool loading: false
895+ property var model: []
896+ id: songsTab
897+ objectName: "songsTab"
898+ anchors.fill: parent
899+ title: page.title
900+
901+ // Tab content begins here
902+ page: Songs {
903+ id: tracksPage
904+ }
905+ }
906+
907+
908+ // fifth tab is the playlists
909+ Tab {
910+ property bool populated: false
911+ property var loader: [playlistModel.filterPlaylists]
912+ property bool loading: false
913+ property var model: [playlistModel, albumTracksModel]
914+ id: playlistsTab
915+ objectName: "playlistsTab"
916+ anchors.fill: parent
917+ title: page.title
918+
919+ // Tab content begins here
920+ page: Playlists {
921+ id: playlistsPage
922+ }
923+ }
924+
925+ // Set the models in the tab to allow/disallow loading
926+ function allowLoading(tabToLoad, state)
927+ {
928+ if (tabToLoad !== undefined && tabToLoad.model !== undefined)
929+ {
930+ for (var i=0; i < tabToLoad.model.length; i++)
931+ {
932+ tabToLoad.model[i].canLoad = state;
933+ }
934+ }
935+ }
936+
937+ function ensurePopulated(selectedTab)
938+ {
939+ allowLoading(selectedTab, true); // allow loading of the models
940+
941+ if (!selectedTab.populated && !selectedTab.loading && loadedUI) {
942+ loading.visible = true
943+ selectedTab.loading = true
944+
945+ if (selectedTab.loader !== undefined)
946+ {
947+ for (var i=0; i < selectedTab.loader.length; i++)
948+ {
949+ selectedTab.loader[i]();
950+ }
951+ }
952+ }
953+ loading.visible = selectedTab.loading || !selectedTab.populated
954+ }
955+
956+ function pushNowPlaying()
957+ {
958+ // only push if on a different page
959+ if (mainPageStack.currentPage.title !== i18n.tr("Now playing")
960+ && mainPageStack.currentPage.title !== i18n.tr("Queue")) {
961+ mainPageStack.push(Qt.resolvedUrl("components/NowPlaying.qml"), {})
962+ }
963+
964+ if (mainPageStack.currentPage.title === i18n.tr("Queue")) {
965+ mainPageStack.currentPage.isListView = false; // ensure full view
966+ }
967+ }
968+ } // end of tabs
969+ }
970+
971+ Loader {
972+ id: emptyPageLoader
973+ // Do not be active if content-hub is importing due to the models resetting
974+ // this then causes the empty page loader to partially run then showing a blank header
975+ active: noMusic && !firstRun && contentHubWaitForFile.processId === -1
976 anchors {
977 fill: parent
978 }
979-
980- property Tab lastTab: selectedTab
981-
982- onSelectedTabChanged: {
983- // pause loading of the models in the old tab
984- if (lastTab !== null && lastTab !== selectedTab) {
985- allowLoading(lastTab, false);
986- }
987-
988- lastTab = selectedTab;
989-
990- ensurePopulated(selectedTab);
991- }
992-
993- onSelectedTabIndexChanged: {
994- if (loadedUI) { // store the tab index if changed by the user
995- startupSettings.tabIndex = selectedTabIndex
996- }
997- }
998-
999- // Use a repeater to 'hide' the recent tab when the model is empty
1000- // A repeater is used because the Tabs component respects adds and
1001- // removes. Whereas replacing the list tabChildren does not appear
1002- // to respect removes and setting the page as active: false causes
1003- // the page to be blank but the action to still in the overflow
1004- Repeater {
1005- id: recentTabRepeater
1006- // If the model has not loaded and at startup the db was not empty
1007- // then show recent or
1008- // If the workerlist has been set and it has values then show recent
1009- model: (!recentModel.preLoadComplete && !startupRecentEmpty) ||
1010- (recentModel.workerList !== undefined &&
1011- recentModel.workerList.length > 0) ? 1 : 0
1012- delegate: Component {
1013- // First tab is all music
1014- Tab {
1015- property bool populated: recentTabRepeater.populated
1016- property var loader: [recentModel.filterRecent]
1017- property bool loading: recentTabRepeater.loading
1018- property var model: [recentModel, albumTracksModel]
1019- id: recentTab
1020- objectName: "recentTab"
1021- anchors.fill: parent
1022- title: page.title
1023-
1024- // Tab content begins here
1025- page: Recent {
1026- id: recentPage
1027- }
1028- }
1029- }
1030-
1031- // Store the startup state of the db separately otherwise
1032- // it breaks the binding of model
1033- property bool startupRecentEmpty: Library.isRecentEmpty()
1034-
1035- // cached values of the recent model that are copied when
1036- // the tab is created
1037- property bool loading: false
1038- property bool populated: false
1039-
1040- onCountChanged: {
1041- if (count === 0 && loadedUI) {
1042- // Jump to the albums tab when recent is empty
1043- tabs.selectedTabIndex = albumsTab.index
1044- } else if (count > 0 && !loadedUI) {
1045- // UI is still loading and recent tab has been inserted
1046- // so move the selected index 'down' as the value is
1047- // not auto updated - this is for the case of loading
1048- // directly to the recent tab (otherwise the content
1049- // appears as the second tab but the tabs think they are
1050- // on the first tab)
1051- tabs.selectedTabIndex -= 1
1052- } else if (count > 0 && loadedUI) {
1053- // tab inserted while the app is running so move the
1054- // selected index 'up' to keep the same position
1055- tabs.selectedTabIndex += 1
1056- }
1057- }
1058- }
1059-
1060- // Second tab is arists
1061- Tab {
1062- property bool populated: true
1063- property var loader: []
1064- property bool loading: false
1065- property var model: []
1066- id: artistsTab
1067- objectName: "artistsTab"
1068- anchors.fill: parent
1069- title: page.title
1070-
1071- // tab content
1072- page: Artists {
1073- id: artistsPage
1074- }
1075- }
1076-
1077- // third tab is albums
1078- Tab {
1079- property bool populated: true
1080- property var loader: []
1081- property bool loading: false
1082- property var model: []
1083- id: albumsTab
1084- objectName: "albumsTab"
1085- anchors.fill: parent
1086- title: page.title
1087-
1088- // Tab content begins here
1089- page: Albums {
1090- id: albumsPage
1091- }
1092- }
1093-
1094- // forth tab is genres
1095- Tab {
1096- property bool populated: true
1097- property var loader: []
1098- property bool loading: false
1099- property var model: []
1100- id: genresTab
1101- objectName: "genresTab"
1102- anchors.fill: parent
1103- title: page.title
1104-
1105- // Tab content begins here
1106- page: Genres {
1107- id: genresPage
1108- }
1109- }
1110-
1111- // fourth tab is all songs
1112- Tab {
1113- property bool populated: true
1114- property var loader: []
1115- property bool loading: false
1116- property var model: []
1117- id: songsTab
1118- objectName: "songsTab"
1119- anchors.fill: parent
1120- title: page.title
1121-
1122- // Tab content begins here
1123- page: Songs {
1124- id: tracksPage
1125- }
1126- }
1127-
1128-
1129- // fifth tab is the playlists
1130- Tab {
1131- property bool populated: false
1132- property var loader: [playlistModel.filterPlaylists]
1133- property bool loading: false
1134- property var model: [playlistModel, albumTracksModel]
1135- id: playlistsTab
1136- objectName: "playlistsTab"
1137- anchors.fill: parent
1138- title: page.title
1139-
1140- // Tab content begins here
1141- page: Playlists {
1142- id: playlistsPage
1143- }
1144- }
1145-
1146- // Set the models in the tab to allow/disallow loading
1147- function allowLoading(tabToLoad, state)
1148- {
1149- if (tabToLoad !== undefined && tabToLoad.model !== undefined)
1150- {
1151- for (var i=0; i < tabToLoad.model.length; i++)
1152- {
1153- tabToLoad.model[i].canLoad = state;
1154- }
1155- }
1156- }
1157-
1158- function ensurePopulated(selectedTab)
1159- {
1160- allowLoading(selectedTab, true); // allow loading of the models
1161-
1162- if (!selectedTab.populated && !selectedTab.loading && loadedUI) {
1163- loading.visible = true
1164- selectedTab.loading = true
1165-
1166- if (selectedTab.loader !== undefined)
1167- {
1168- for (var i=0; i < selectedTab.loader.length; i++)
1169- {
1170- selectedTab.loader[i]();
1171- }
1172- }
1173- }
1174- loading.visible = selectedTab.loading || !selectedTab.populated
1175- }
1176-
1177- function pushNowPlaying()
1178- {
1179- // only push if on a different page
1180- if (mainPageStack.currentPage.title !== i18n.tr("Now playing")
1181- && mainPageStack.currentPage.title !== i18n.tr("Queue")) {
1182- mainPageStack.push(Qt.resolvedUrl("ui/NowPlaying.qml"), {})
1183- }
1184-
1185- if (mainPageStack.currentPage.title === i18n.tr("Queue")) {
1186- mainPageStack.currentPage.isListView = false; // ensure full view
1187- }
1188- }
1189- } // end of tabs
1190- }
1191-
1192- Loader {
1193- id: emptyPageLoader
1194- // Do not be active if content-hub is importing due to the models resetting
1195- // this then causes the empty page loader to partially run then showing a blank header
1196- active: noMusic && !firstRun && contentHubWaitForFile.processId === -1
1197- anchors {
1198- fill: parent
1199- }
1200- source: "ui/LibraryEmptyState.qml"
1201- visible: active
1202-
1203- property bool noMusic: allSongsModel.rowCount === 0 && allSongsModelModel.status === SongsModel.Ready && loadedUI
1204- }
1205-
1206- LoadingSpinnerComponent {
1207- id: loading
1208+ source: "ui/LibraryEmptyState.qml"
1209+ visible: active
1210+
1211+ property bool noMusic: allSongsModel.rowCount === 0 && allSongsModelModel.status === SongsModel.Ready && loadedUI
1212+ }
1213+
1214+ LoadingSpinnerComponent {
1215+ id: loading
1216+ }
1217 }
1218 } // end of main view
1219
1220=== renamed file 'app/ui/NowPlaying.qml' => 'app/ui/NowPlayingView.qml'
1221--- app/ui/NowPlaying.qml 2015-02-08 04:06:28 +0000
1222+++ app/ui/NowPlayingView.qml 2015-02-11 03:14:13 +0000
1223@@ -37,7 +37,7 @@
1224 title: isListView ? queueTitle : nowPlayingTitle
1225 visible: false
1226
1227- property bool isListView: false
1228+ property alias isListView: nowPlaying.isListView
1229 // TRANSLATORS: this appears in the header with limited space (around 20 characters)
1230 property string nowPlayingTitle: i18n.tr("Now playing")
1231 // TRANSLATORS: this appears in the header with limited space (around 20 characters)
1232@@ -137,511 +137,7 @@
1233 }
1234 ]
1235
1236- Item {
1237- id: fullview
1238- anchors {
1239- top: parent.top
1240- topMargin: mainView.header.height
1241- }
1242- height: parent.height - mainView.header.height - units.gu(9.5)
1243- visible: !isListView
1244- width: parent.width
1245-
1246- BlurredBackground {
1247- id: blurredBackground
1248- anchors {
1249- left: parent.left
1250- right: parent.right
1251- top: parent.top
1252- }
1253- art: albumImage.firstSource
1254- height: parent.height - units.gu(7)
1255-
1256- Item {
1257- id: albumImageContainer
1258- anchors {
1259- horizontalCenter: parent.horizontalCenter
1260- top: parent.top
1261- }
1262- height: parent.height
1263- width: parent.width
1264-
1265- CoverGrid {
1266- id: albumImage
1267- anchors.centerIn: parent
1268- covers: [{art: player.currentMetaArt, author: player.currentMetaArtist, album: player.currentMetaAlbum}]
1269- size: parent.width > parent.height ? parent.height : parent.width
1270- }
1271- }
1272-
1273- Rectangle {
1274- id: nowPlayingWideAspectLabelsBackground
1275- anchors.bottom: parent.bottom
1276- color: styleMusic.common.black
1277- height: nowPlayingWideAspectTitle.lineCount === 1 ? units.gu(10) : units.gu(13)
1278- opacity: 0.8
1279- width: parent.width
1280- }
1281-
1282- /* Column for labels in wideAspect */
1283- Column {
1284- id: nowPlayingWideAspectLabels
1285- spacing: units.gu(1)
1286- anchors {
1287- left: parent.left
1288- leftMargin: units.gu(2)
1289- right: parent.right
1290- rightMargin: units.gu(2)
1291- top: nowPlayingWideAspectLabelsBackground.top
1292- topMargin: nowPlayingWideAspectTitle.lineCount === 1 ? units.gu(2) : units.gu(1.5)
1293- }
1294-
1295- /* Title of track */
1296- Label {
1297- id: nowPlayingWideAspectTitle
1298- anchors {
1299- left: parent.left
1300- leftMargin: units.gu(1)
1301- right: parent.right
1302- rightMargin: units.gu(1)
1303- }
1304- color: styleMusic.playerControls.labelColor
1305- elide: Text.ElideRight
1306- fontSize: "x-large"
1307- maximumLineCount: 2
1308- objectName: "playercontroltitle"
1309- text: trackQueue.model.count === 0 ? "" : player.currentMetaTitle === "" ? player.currentMetaFile : player.currentMetaTitle
1310- wrapMode: Text.WordWrap
1311- }
1312-
1313- /* Artist of track */
1314- Label {
1315- id: nowPlayingWideAspectArtist
1316- anchors {
1317- left: parent.left
1318- leftMargin: units.gu(1)
1319- right: parent.right
1320- rightMargin: units.gu(1)
1321- }
1322- color: styleMusic.nowPlaying.labelSecondaryColor
1323- elide: Text.ElideRight
1324- fontSize: "small"
1325- text: trackQueue.model.count === 0 ? "" : player.currentMetaArtist
1326- }
1327- }
1328-
1329- /* Detect cover art swipe */
1330- MouseArea {
1331- anchors.fill: parent
1332- property string direction: "None"
1333- property real lastX: -1
1334-
1335- onPressed: lastX = mouse.x
1336-
1337- onReleased: {
1338- var diff = mouse.x - lastX
1339- if (Math.abs(diff) < units.gu(4)) {
1340- return;
1341- } else if (diff < 0) {
1342- player.nextSong()
1343- } else if (diff > 0) {
1344- player.previousSong()
1345- }
1346- }
1347- }
1348- }
1349-
1350- /* Background for progress bar component */
1351- Rectangle {
1352- id: musicToolbarFullProgressBackground
1353- anchors {
1354- bottom: parent.bottom
1355- left: parent.left
1356- right: parent.right
1357- top: blurredBackground.bottom
1358- }
1359- color: styleMusic.common.black
1360- }
1361-
1362- /* Progress bar component */
1363- Item {
1364- id: musicToolbarFullProgressContainer
1365- anchors.left: parent.left
1366- anchors.leftMargin: units.gu(3)
1367- anchors.right: parent.right
1368- anchors.rightMargin: units.gu(3)
1369- anchors.top: blurredBackground.bottom
1370- anchors.topMargin: units.gu(1)
1371- height: units.gu(3)
1372- width: parent.width
1373-
1374- /* Position label */
1375- Label {
1376- id: musicToolbarFullPositionLabel
1377- anchors.top: progressSliderMusic.bottom
1378- anchors.topMargin: units.gu(-2)
1379- anchors.left: parent.left
1380- color: styleMusic.nowPlaying.labelSecondaryColor
1381- fontSize: "small"
1382- height: parent.height
1383- horizontalAlignment: Text.AlignHCenter
1384- text: durationToString(player.position)
1385- verticalAlignment: Text.AlignVCenter
1386- width: units.gu(3)
1387- }
1388-
1389- Slider {
1390- id: progressSliderMusic
1391- anchors.left: parent.left
1392- anchors.right: parent.right
1393- maximumValue: player.duration // load value at startup
1394- objectName: "progressSliderShape"
1395- style: UbuntuBlueSliderStyle {}
1396- value: player.position // load value at startup
1397-
1398- function formatValue(v) {
1399- if (seeking) { // update position label while dragging
1400- musicToolbarFullPositionLabel.text = durationToString(v)
1401- }
1402-
1403- return durationToString(v)
1404- }
1405-
1406- property bool seeking: false
1407- property bool seeked: false
1408-
1409- onSeekingChanged: {
1410- if (seeking === false) {
1411- musicToolbarFullPositionLabel.text = durationToString(player.position)
1412- }
1413- }
1414-
1415- onPressedChanged: {
1416- seeking = pressed
1417-
1418- if (!pressed) {
1419- seeked = true
1420- player.seek(value)
1421-
1422- musicToolbarFullPositionLabel.text = durationToString(value)
1423- }
1424- }
1425-
1426- Connections {
1427- target: player
1428- onPositionChanged: {
1429- // seeked is a workaround for bug 1310706 as the first position after a seek is sometimes invalid (0)
1430- if (progressSliderMusic.seeking === false && !progressSliderMusic.seeked) {
1431- musicToolbarFullPositionLabel.text = durationToString(player.position)
1432- musicToolbarFullDurationLabel.text = durationToString(player.duration)
1433-
1434- progressSliderMusic.value = player.position
1435- progressSliderMusic.maximumValue = player.duration
1436- }
1437-
1438- progressSliderMusic.seeked = false;
1439- }
1440- onStopped: {
1441- musicToolbarFullPositionLabel.text = durationToString(0);
1442- musicToolbarFullDurationLabel.text = durationToString(0);
1443- }
1444- }
1445- }
1446-
1447- /* Duration label */
1448- Label {
1449- id: musicToolbarFullDurationLabel
1450- anchors.top: progressSliderMusic.bottom
1451- anchors.topMargin: units.gu(-2)
1452- anchors.right: parent.right
1453- color: styleMusic.nowPlaying.labelSecondaryColor
1454- fontSize: "small"
1455- height: parent.height
1456- horizontalAlignment: Text.AlignHCenter
1457- text: durationToString(player.duration)
1458- verticalAlignment: Text.AlignVCenter
1459- width: units.gu(3)
1460- }
1461- }
1462- }
1463-
1464- Loader {
1465- id: queueListLoader
1466- anchors {
1467- fill: parent
1468- }
1469- asynchronous: true
1470- sourceComponent: MultiSelectListView {
1471- id: queueList
1472- anchors {
1473- bottomMargin: musicToolbarFullContainer.height + units.gu(2)
1474- fill: parent
1475- topMargin: units.gu(2)
1476- }
1477- delegate: queueDelegate
1478- footer: Item {
1479- height: mainView.height - (styleMusic.common.expandHeight + queueList.currentHeight) + units.gu(8)
1480- }
1481- model: trackQueue.model
1482- objectName: "nowPlayingqueueList"
1483-
1484- property int normalHeight: units.gu(6)
1485- property int transitionDuration: 250 // transition length of animations
1486-
1487- onCountChanged: customdebug("Queue: Now has: " + queueList.count + " tracks")
1488-
1489- Component {
1490- id: queueDelegate
1491- ListItemWithActions {
1492- id: queueListItem
1493- color: player.currentIndex === index ? "#2c2c34" : styleMusic.mainView.backgroundColor
1494- height: queueList.normalHeight
1495- objectName: "nowPlayingListItem" + index
1496- state: ""
1497-
1498- leftSideAction: Remove {
1499- onTriggered: trackQueue.removeQueueList([index])
1500- }
1501- multiselectable: true
1502- reorderable: true
1503- rightSideActions: [
1504- AddToPlaylist{
1505-
1506- }
1507- ]
1508-
1509- onItemClicked: {
1510- customdebug("File: " + model.filename) // debugger
1511- trackQueueClick(index); // toggle track state
1512- }
1513- onReorder: {
1514- console.debug("Move: ", from, to);
1515-
1516- trackQueue.model.move(from, to, 1);
1517- Library.moveQueueItem(from, to);
1518-
1519- // Maintain currentIndex with current song
1520- if (from === player.currentIndex) {
1521- player.currentIndex = to;
1522- }
1523- else if (from < player.currentIndex && to >= player.currentIndex) {
1524- player.currentIndex -= 1;
1525- }
1526- else if (from > player.currentIndex && to <= player.currentIndex) {
1527- player.currentIndex += 1;
1528- }
1529-
1530- queueIndex = player.currentIndex
1531- }
1532-
1533- Item {
1534- id: trackContainer;
1535- anchors {
1536- fill: parent
1537- }
1538-
1539- NumberAnimation {
1540- id: trackContainerReorderAnimation
1541- target: trackContainer;
1542- property: "anchors.leftMargin";
1543- duration: queueList.transitionDuration;
1544- to: units.gu(2)
1545- }
1546-
1547- NumberAnimation {
1548- id: trackContainerResetAnimation
1549- target: trackContainer;
1550- property: "anchors.leftMargin";
1551- duration: queueList.transitionDuration;
1552- to: units.gu(0.5)
1553- }
1554-
1555- MusicRow {
1556- id: musicRow
1557- height: parent.height
1558- column: Column {
1559- Label {
1560- id: trackTitle
1561- color: player.currentIndex === index ? UbuntuColors.blue
1562- : styleMusic.common.music
1563- fontSize: "small"
1564- objectName: "titleLabel"
1565- text: model.title
1566- }
1567-
1568- Label {
1569- id: trackArtist
1570- color: styleMusic.common.subtitle
1571- fontSize: "x-small"
1572- objectName: "artistLabel"
1573- text: model.author
1574- }
1575- }
1576- }
1577- }
1578- }
1579- }
1580- }
1581- visible: isListView
1582- }
1583-
1584- /* Full toolbar */
1585- Rectangle {
1586- id: musicToolbarFullContainer
1587- anchors.bottom: parent.bottom
1588- color: styleMusic.common.black
1589- height: units.gu(10)
1590- width: parent.width
1591-
1592- /* Repeat button */
1593- MouseArea {
1594- id: nowPlayingRepeatButton
1595- anchors.right: nowPlayingPreviousButton.left
1596- anchors.rightMargin: units.gu(1)
1597- anchors.verticalCenter: nowPlayingPlayButton.verticalCenter
1598- height: units.gu(6)
1599- opacity: player.repeat && !emptyPageLoader.noMusic ? 1 : .4
1600- width: height
1601- onClicked: player.repeat = !player.repeat
1602-
1603- Icon {
1604- id: repeatIcon
1605- height: units.gu(3)
1606- width: height
1607- anchors.verticalCenter: parent.verticalCenter
1608- anchors.horizontalCenter: parent.horizontalCenter
1609- color: "white"
1610- name: "media-playlist-repeat"
1611- objectName: "repeatShape"
1612- opacity: player.repeat && !emptyPageLoader.noMusic ? 1 : .4
1613- }
1614- }
1615-
1616- /* Previous button */
1617- MouseArea {
1618- id: nowPlayingPreviousButton
1619- anchors.right: nowPlayingPlayButton.left
1620- anchors.rightMargin: units.gu(1)
1621- anchors.verticalCenter: nowPlayingPlayButton.verticalCenter
1622- height: units.gu(6)
1623- opacity: trackQueue.model.count === 0 ? .4 : 1
1624- width: height
1625- onClicked: player.previousSong()
1626-
1627- Icon {
1628- id: nowPlayingPreviousIndicator
1629- height: units.gu(3)
1630- width: height
1631- anchors.verticalCenter: parent.verticalCenter
1632- anchors.horizontalCenter: parent.horizontalCenter
1633- color: "white"
1634- name: "media-skip-backward"
1635- objectName: "previousShape"
1636- opacity: 1
1637- }
1638- }
1639-
1640- /* Play/Pause button */
1641- MouseArea {
1642- id: nowPlayingPlayButton
1643- anchors.centerIn: parent
1644- height: units.gu(10)
1645- width: height
1646- onClicked: player.toggle()
1647-
1648- Icon {
1649- id: nowPlayingPlayIndicator
1650- height: units.gu(6)
1651- width: height
1652- anchors.verticalCenter: parent.verticalCenter
1653- anchors.horizontalCenter: parent.horizontalCenter
1654- opacity: emptyPageLoader.noMusic ? .4 : 1
1655- color: "white"
1656- name: player.playbackState === MediaPlayer.PlayingState ? "media-playback-pause" : "media-playback-start"
1657- objectName: "playShape"
1658- }
1659- }
1660-
1661- /* Next button */
1662- MouseArea {
1663- id: nowPlayingNextButton
1664- anchors.left: nowPlayingPlayButton.right
1665- anchors.leftMargin: units.gu(1)
1666- anchors.verticalCenter: nowPlayingPlayButton.verticalCenter
1667- height: units.gu(6)
1668- opacity: trackQueue.model.count === 0 ? .4 : 1
1669- width: height
1670- onClicked: player.nextSong()
1671-
1672- Icon {
1673- id: nowPlayingNextIndicator
1674- height: units.gu(3)
1675- width: height
1676- anchors.verticalCenter: parent.verticalCenter
1677- anchors.horizontalCenter: parent.horizontalCenter
1678- color: "white"
1679- name: "media-skip-forward"
1680- objectName: "forwardShape"
1681- opacity: 1
1682- }
1683- }
1684-
1685- /* Shuffle button */
1686- MouseArea {
1687- id: nowPlayingShuffleButton
1688- anchors.left: nowPlayingNextButton.right
1689- anchors.leftMargin: units.gu(1)
1690- anchors.verticalCenter: nowPlayingPlayButton.verticalCenter
1691- height: units.gu(6)
1692- opacity: player.shuffle && !emptyPageLoader.noMusic ? 1 : .4
1693- width: height
1694- onClicked: player.shuffle = !player.shuffle
1695-
1696- Icon {
1697- id: shuffleIcon
1698- height: units.gu(3)
1699- width: height
1700- anchors.verticalCenter: parent.verticalCenter
1701- anchors.horizontalCenter: parent.horizontalCenter
1702- color: "white"
1703- name: "media-playlist-shuffle"
1704- objectName: "shuffleShape"
1705- opacity: player.shuffle && !emptyPageLoader.noMusic ? 1 : .4
1706- }
1707- }
1708-
1709- /* Object which provides the progress bar when in the queue */
1710- Rectangle {
1711- id: playerControlsProgressBar
1712- anchors {
1713- bottom: parent.bottom
1714- left: parent.left
1715- right: parent.right
1716- }
1717- color: styleMusic.common.black
1718- height: units.gu(0.25)
1719- visible: isListView
1720-
1721- Rectangle {
1722- id: playerControlsProgressBarHint
1723- anchors {
1724- left: parent.left
1725- bottom: parent.bottom
1726- }
1727- color: UbuntuColors.blue
1728- height: parent.height
1729- width: player.duration > 0 ? (player.position / player.duration) * playerControlsProgressBar.width : 0
1730-
1731- Connections {
1732- target: player
1733- onPositionChanged: {
1734- playerControlsProgressBarHint.width = (player.position / player.duration) * playerControlsProgressBar.width
1735- }
1736- onStopped: {
1737- playerControlsProgressBarHint.width = 0;
1738- }
1739- }
1740- }
1741- }
1742+ NowPlaying {
1743+ id: view
1744 }
1745 }

Subscribers

People subscribed via source and target branches

to all changes: