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
=== modified file 'app/components/MusicPage.qml'
--- app/components/MusicPage.qml 2015-01-21 19:04:07 +0000
+++ app/components/MusicPage.qml 2015-02-11 03:14:13 +0000
@@ -19,6 +19,7 @@
1919
20import QtQuick 2.320import QtQuick 2.3
21import Ubuntu.Components 1.121import Ubuntu.Components 1.1
22import Ubuntu.Layouts 1.0
2223
2324
24// generic page for music, could be useful for bottomedge implementation25// generic page for music, could be useful for bottomedge implementation
@@ -28,6 +29,7 @@
28 bottomMargin: musicToolbar.visible ? musicToolbar.height : 029 bottomMargin: musicToolbar.visible ? musicToolbar.height : 0
29 fill: parent30 fill: parent
30 }31 }
32 Layouts.item: "MusicPage"
3133
32 property bool searchable: false34 property bool searchable: false
33 property int searchResultsCount35 property int searchResultsCount
3436
=== added file 'app/components/NowPlaying.qml'
--- app/components/NowPlaying.qml 1970-01-01 00:00:00 +0000
+++ app/components/NowPlaying.qml 2015-02-11 03:14:13 +0000
@@ -0,0 +1,550 @@
1/*
2 * Copyright (C) 2013, 2014, 2015
3 * Andrew Hayzen <ahayzen@gmail.com>
4 * Daniel Holm <d.holmen@gmail.com>
5 * Victor Thompson <victor.thompson@gmail.com>
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; version 3.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20import QtMultimedia 5.0
21import QtQuick 2.3
22import QtQuick.LocalStorage 2.0
23import Ubuntu.Components 1.1
24import Ubuntu.Thumbnailer 0.1
25import "../components"
26import "../components/Flickables"
27import "../components/HeadState"
28import "../components/ListItemActions"
29import "../components/Themes/Ambiance"
30import "../logic/meta-database.js" as Library
31import "../logic/playlists.js" as Playlists
32
33Item {
34 id: nowPlayingView
35 objectName: "nowPlayingView"
36
37 anchors {
38 fill: parent
39 }
40
41 property bool isListView: false
42
43 Item {
44 id: fullview
45 anchors {
46 top: parent.top
47 topMargin: mainView.header.height
48 }
49 height: parent.height - mainView.header.height - units.gu(9.5)
50 visible: !isListView || wideAspect
51 width: parent.width
52
53 BlurredBackground {
54 id: blurredBackground
55 anchors {
56 left: parent.left
57 right: parent.right
58 top: parent.top
59 }
60 art: albumImage.firstSource
61 height: parent.height - units.gu(7)
62
63 Item {
64 id: albumImageContainer
65 anchors {
66 horizontalCenter: parent.horizontalCenter
67 top: parent.top
68 }
69 height: parent.height
70 width: parent.width
71
72 CoverGrid {
73 id: albumImage
74 anchors.centerIn: parent
75 covers: [{art: player.currentMetaArt, author: player.currentMetaArtist, album: player.currentMetaAlbum}]
76 size: parent.width > parent.height ? parent.height : parent.width
77 }
78 }
79
80 Rectangle {
81 id: nowPlayingWideAspectLabelsBackground
82 anchors.bottom: parent.bottom
83 color: styleMusic.common.black
84 height: nowPlayingWideAspectTitle.lineCount === 1 ? units.gu(10) : units.gu(13)
85 opacity: 0.8
86 width: parent.width
87 }
88
89 /* Column for labels in wideAspect */
90 Column {
91 id: nowPlayingWideAspectLabels
92 spacing: units.gu(1)
93 anchors {
94 left: parent.left
95 leftMargin: units.gu(2)
96 right: parent.right
97 rightMargin: units.gu(2)
98 top: nowPlayingWideAspectLabelsBackground.top
99 topMargin: nowPlayingWideAspectTitle.lineCount === 1 ? units.gu(2) : units.gu(1.5)
100 }
101
102 /* Title of track */
103 Label {
104 id: nowPlayingWideAspectTitle
105 anchors {
106 left: parent.left
107 leftMargin: units.gu(1)
108 right: parent.right
109 rightMargin: units.gu(1)
110 }
111 color: styleMusic.playerControls.labelColor
112 elide: Text.ElideRight
113 fontSize: "x-large"
114 maximumLineCount: 2
115 objectName: "playercontroltitle"
116 text: trackQueue.model.count === 0 ? "" : player.currentMetaTitle === "" ? player.currentMetaFile : player.currentMetaTitle
117 wrapMode: Text.WordWrap
118 }
119
120 /* Artist of track */
121 Label {
122 id: nowPlayingWideAspectArtist
123 anchors {
124 left: parent.left
125 leftMargin: units.gu(1)
126 right: parent.right
127 rightMargin: units.gu(1)
128 }
129 color: styleMusic.nowPlaying.labelSecondaryColor
130 elide: Text.ElideRight
131 fontSize: "small"
132 text: trackQueue.model.count === 0 ? "" : player.currentMetaArtist
133 }
134 }
135
136 /* Detect cover art swipe */
137 MouseArea {
138 anchors.fill: parent
139 property string direction: "None"
140 property real lastX: -1
141
142 onPressed: lastX = mouse.x
143
144 onReleased: {
145 var diff = mouse.x - lastX
146 if (Math.abs(diff) < units.gu(4)) {
147 return;
148 } else if (diff < 0) {
149 player.nextSong()
150 } else if (diff > 0) {
151 player.previousSong()
152 }
153 }
154 }
155 }
156
157 /* Background for progress bar component */
158 Rectangle {
159 id: musicToolbarFullProgressBackground
160 anchors {
161 bottom: parent.bottom
162 left: parent.left
163 right: parent.right
164 top: blurredBackground.bottom
165 }
166 color: styleMusic.common.black
167 }
168
169 /* Progress bar component */
170 Item {
171 id: musicToolbarFullProgressContainer
172 anchors.left: parent.left
173 anchors.leftMargin: units.gu(3)
174 anchors.right: parent.right
175 anchors.rightMargin: units.gu(3)
176 anchors.top: blurredBackground.bottom
177 anchors.topMargin: units.gu(1)
178 height: units.gu(3)
179 width: parent.width
180
181 /* Position label */
182 Label {
183 id: musicToolbarFullPositionLabel
184 anchors.top: progressSliderMusic.bottom
185 anchors.topMargin: units.gu(-2)
186 anchors.left: parent.left
187 color: styleMusic.nowPlaying.labelSecondaryColor
188 fontSize: "small"
189 height: parent.height
190 horizontalAlignment: Text.AlignHCenter
191 text: durationToString(player.position)
192 verticalAlignment: Text.AlignVCenter
193 width: units.gu(3)
194 }
195
196 Slider {
197 id: progressSliderMusic
198 anchors.left: parent.left
199 anchors.right: parent.right
200 maximumValue: player.duration // load value at startup
201 objectName: "progressSliderShape"
202 style: UbuntuBlueSliderStyle {}
203 value: player.position // load value at startup
204
205 function formatValue(v) {
206 if (seeking) { // update position label while dragging
207 musicToolbarFullPositionLabel.text = durationToString(v)
208 }
209
210 return durationToString(v)
211 }
212
213 property bool seeking: false
214 property bool seeked: false
215
216 onSeekingChanged: {
217 if (seeking === false) {
218 musicToolbarFullPositionLabel.text = durationToString(player.position)
219 }
220 }
221
222 onPressedChanged: {
223 seeking = pressed
224
225 if (!pressed) {
226 seeked = true
227 player.seek(value)
228
229 musicToolbarFullPositionLabel.text = durationToString(value)
230 }
231 }
232
233 Connections {
234 target: player
235 onPositionChanged: {
236 // seeked is a workaround for bug 1310706 as the first position after a seek is sometimes invalid (0)
237 if (progressSliderMusic.seeking === false && !progressSliderMusic.seeked) {
238 musicToolbarFullPositionLabel.text = durationToString(player.position)
239 musicToolbarFullDurationLabel.text = durationToString(player.duration)
240
241 progressSliderMusic.value = player.position
242 progressSliderMusic.maximumValue = player.duration
243 }
244
245 progressSliderMusic.seeked = false;
246 }
247 onStopped: {
248 musicToolbarFullPositionLabel.text = durationToString(0);
249 musicToolbarFullDurationLabel.text = durationToString(0);
250 }
251 }
252 }
253
254 /* Duration label */
255 Label {
256 id: musicToolbarFullDurationLabel
257 anchors.top: progressSliderMusic.bottom
258 anchors.topMargin: units.gu(-2)
259 anchors.right: parent.right
260 color: styleMusic.nowPlaying.labelSecondaryColor
261 fontSize: "small"
262 height: parent.height
263 horizontalAlignment: Text.AlignHCenter
264 text: durationToString(player.duration)
265 verticalAlignment: Text.AlignVCenter
266 width: units.gu(3)
267 }
268 }
269 }
270
271 Loader {
272 id: queueListLoader
273 anchors {
274 fill: parent
275 }
276 asynchronous: true
277 sourceComponent: MultiSelectListView {
278 id: queueList
279 anchors {
280 bottomMargin: musicToolbarFullContainer.height + units.gu(2)
281 fill: parent
282 topMargin: units.gu(2)
283 }
284 delegate: queueDelegate
285 footer: Item {
286 height: mainView.height - (styleMusic.common.expandHeight + queueList.currentHeight) + units.gu(8)
287 }
288 model: trackQueue.model
289 objectName: "nowPlayingqueueList"
290
291 property int normalHeight: units.gu(6)
292 property int transitionDuration: 250 // transition length of animations
293
294 onCountChanged: customdebug("Queue: Now has: " + queueList.count + " tracks")
295
296 Component {
297 id: queueDelegate
298 ListItemWithActions {
299 id: queueListItem
300 color: player.currentIndex === index ? "#2c2c34" : styleMusic.mainView.backgroundColor
301 height: queueList.normalHeight
302 objectName: "nowPlayingListItem" + index
303 state: ""
304
305 leftSideAction: Remove {
306 onTriggered: trackQueue.removeQueueList([index])
307 }
308 multiselectable: true
309 reorderable: true
310 rightSideActions: [
311 AddToPlaylist{
312
313 }
314 ]
315
316 onItemClicked: {
317 customdebug("File: " + model.filename) // debugger
318 trackQueueClick(index); // toggle track state
319 }
320 onReorder: {
321 console.debug("Move: ", from, to);
322
323 trackQueue.model.move(from, to, 1);
324 Library.moveQueueItem(from, to);
325
326 // Maintain currentIndex with current song
327 if (from === player.currentIndex) {
328 player.currentIndex = to;
329 }
330 else if (from < player.currentIndex && to >= player.currentIndex) {
331 player.currentIndex -= 1;
332 }
333 else if (from > player.currentIndex && to <= player.currentIndex) {
334 player.currentIndex += 1;
335 }
336
337 queueIndex = player.currentIndex
338 }
339
340 Item {
341 id: trackContainer;
342 anchors {
343 fill: parent
344 }
345
346 NumberAnimation {
347 id: trackContainerReorderAnimation
348 target: trackContainer;
349 property: "anchors.leftMargin";
350 duration: queueList.transitionDuration;
351 to: units.gu(2)
352 }
353
354 NumberAnimation {
355 id: trackContainerResetAnimation
356 target: trackContainer;
357 property: "anchors.leftMargin";
358 duration: queueList.transitionDuration;
359 to: units.gu(0.5)
360 }
361
362 MusicRow {
363 id: musicRow
364 height: parent.height
365 column: Column {
366 Label {
367 id: trackTitle
368 color: player.currentIndex === index ? UbuntuColors.blue
369 : styleMusic.common.music
370 fontSize: "small"
371 objectName: "titleLabel"
372 text: model.title
373 }
374
375 Label {
376 id: trackArtist
377 color: styleMusic.common.subtitle
378 fontSize: "x-small"
379 objectName: "artistLabel"
380 text: model.author
381 }
382 }
383 }
384 }
385 }
386 }
387 }
388 visible: isListView || wideAspect
389 }
390
391 /* Full toolbar */
392 Rectangle {
393 id: musicToolbarFullContainer
394 anchors.bottom: parent.bottom
395 color: styleMusic.common.black
396 height: units.gu(10)
397 width: parent.width
398
399 /* Repeat button */
400 MouseArea {
401 id: nowPlayingRepeatButton
402 anchors.right: nowPlayingPreviousButton.left
403 anchors.rightMargin: units.gu(1)
404 anchors.verticalCenter: nowPlayingPlayButton.verticalCenter
405 height: units.gu(6)
406 opacity: player.repeat && !emptyPageLoader.noMusic ? 1 : .4
407 width: height
408 onClicked: player.repeat = !player.repeat
409
410 Icon {
411 id: repeatIcon
412 height: units.gu(3)
413 width: height
414 anchors.verticalCenter: parent.verticalCenter
415 anchors.horizontalCenter: parent.horizontalCenter
416 color: "white"
417 name: "media-playlist-repeat"
418 objectName: "repeatShape"
419 opacity: player.repeat && !emptyPageLoader.noMusic ? 1 : .4
420 }
421 }
422
423 /* Previous button */
424 MouseArea {
425 id: nowPlayingPreviousButton
426 anchors.right: nowPlayingPlayButton.left
427 anchors.rightMargin: units.gu(1)
428 anchors.verticalCenter: nowPlayingPlayButton.verticalCenter
429 height: units.gu(6)
430 opacity: trackQueue.model.count === 0 ? .4 : 1
431 width: height
432 onClicked: player.previousSong()
433
434 Icon {
435 id: nowPlayingPreviousIndicator
436 height: units.gu(3)
437 width: height
438 anchors.verticalCenter: parent.verticalCenter
439 anchors.horizontalCenter: parent.horizontalCenter
440 color: "white"
441 name: "media-skip-backward"
442 objectName: "previousShape"
443 opacity: 1
444 }
445 }
446
447 /* Play/Pause button */
448 MouseArea {
449 id: nowPlayingPlayButton
450 anchors.centerIn: parent
451 height: units.gu(10)
452 width: height
453 onClicked: player.toggle()
454
455 Icon {
456 id: nowPlayingPlayIndicator
457 height: units.gu(6)
458 width: height
459 anchors.verticalCenter: parent.verticalCenter
460 anchors.horizontalCenter: parent.horizontalCenter
461 opacity: emptyPageLoader.noMusic ? .4 : 1
462 color: "white"
463 name: player.playbackState === MediaPlayer.PlayingState ? "media-playback-pause" : "media-playback-start"
464 objectName: "playShape"
465 }
466 }
467
468 /* Next button */
469 MouseArea {
470 id: nowPlayingNextButton
471 anchors.left: nowPlayingPlayButton.right
472 anchors.leftMargin: units.gu(1)
473 anchors.verticalCenter: nowPlayingPlayButton.verticalCenter
474 height: units.gu(6)
475 opacity: trackQueue.model.count === 0 ? .4 : 1
476 width: height
477 onClicked: player.nextSong()
478
479 Icon {
480 id: nowPlayingNextIndicator
481 height: units.gu(3)
482 width: height
483 anchors.verticalCenter: parent.verticalCenter
484 anchors.horizontalCenter: parent.horizontalCenter
485 color: "white"
486 name: "media-skip-forward"
487 objectName: "forwardShape"
488 opacity: 1
489 }
490 }
491
492 /* Shuffle button */
493 MouseArea {
494 id: nowPlayingShuffleButton
495 anchors.left: nowPlayingNextButton.right
496 anchors.leftMargin: units.gu(1)
497 anchors.verticalCenter: nowPlayingPlayButton.verticalCenter
498 height: units.gu(6)
499 opacity: player.shuffle && !emptyPageLoader.noMusic ? 1 : .4
500 width: height
501 onClicked: player.shuffle = !player.shuffle
502
503 Icon {
504 id: shuffleIcon
505 height: units.gu(3)
506 width: height
507 anchors.verticalCenter: parent.verticalCenter
508 anchors.horizontalCenter: parent.horizontalCenter
509 color: "white"
510 name: "media-playlist-shuffle"
511 objectName: "shuffleShape"
512 opacity: player.shuffle && !emptyPageLoader.noMusic ? 1 : .4
513 }
514 }
515
516 /* Object which provides the progress bar when in the queue */
517 Rectangle {
518 id: playerControlsProgressBar
519 anchors {
520 bottom: parent.bottom
521 left: parent.left
522 right: parent.right
523 }
524 color: styleMusic.common.black
525 height: units.gu(0.25)
526 visible: isListView || wideAspect
527
528 Rectangle {
529 id: playerControlsProgressBarHint
530 anchors {
531 left: parent.left
532 bottom: parent.bottom
533 }
534 color: UbuntuColors.blue
535 height: parent.height
536 width: player.duration > 0 ? (player.position / player.duration) * playerControlsProgressBar.width : 0
537
538 Connections {
539 target: player
540 onPositionChanged: {
541 playerControlsProgressBarHint.width = (player.position / player.duration) * playerControlsProgressBar.width
542 }
543 onStopped: {
544 playerControlsProgressBarHint.width = 0;
545 }
546 }
547 }
548 }
549 }
550}
0551
=== modified file 'app/music-app.qml'
--- app/music-app.qml 2015-02-08 04:40:52 +0000
+++ app/music-app.qml 2015-02-11 03:14:13 +0000
@@ -22,6 +22,7 @@
22import Ubuntu.Components.Popups 1.022import Ubuntu.Components.Popups 1.0
23import Ubuntu.Components.ListItems 1.0 as ListItem23import Ubuntu.Components.ListItems 1.0 as ListItem
24import Ubuntu.Content 0.124import Ubuntu.Content 0.1
25import Ubuntu.Layouts 1.0
25import Ubuntu.MediaScanner 0.126import Ubuntu.MediaScanner 0.1
26import Qt.labs.settings 1.027import Qt.labs.settings 1.0
27import QtMultimedia 5.028import QtMultimedia 5.0
@@ -966,300 +967,339 @@
966 }967 }
967 }968 }
968969
969 Loader {970 Layouts {
970 id: musicToolbar971 objectName: "musicLayout"
971 anchors {972 id: layouts
972 bottom: parent.bottom973 anchors.fill: parent
973 left: parent.left974 layouts: [
974 right: parent.right975 ConditionalLayout {
975 }976 name: "wideAspect"
976 asynchronous: true977 when: wideAspect
977 source: "components/MusicToolbar.qml"978 Rectangle {
978 visible: mainPageStack.currentPage.title !== i18n.tr("Now playing") &&979 anchors.fill: parent
979 mainPageStack.currentPage.title !== i18n.tr("Queue") &&980 color: "transparent"
980 mainPageStack.currentPage.title !== i18n.tr("Select playlist") &&981 ItemLayout {
981 !firstRun982 item: "MusicPage"
982 z: 200 // put on top of everything else983 width: layouts.width*0.625
983 }984 anchors {
984985 top: parent.top
985 PageStack {986 bottom: parent.bottom
986 id: mainPageStack987 }
987988 }
988 // Properties storing the current page info989 Rectangle {
989 property Page currentMusicPage: null // currentPage can be Tabs990 width: layouts.width*0.375
990 property bool popping: false991 anchors {
991992 top: parent.top
992 /* Helper functions */993 bottom:parent.bottom
993994 right: parent.right
994 // Go back up the stack if possible995 }
995 function goBack() {996 color: "transparent"
996 if (depth > 1) {997
997 pop()998 NowPlaying {
998 }999 id: nowPlaying
999 }1000 Layouts.item: "nowPlaying"
10001001 }
1001 // Pop a specific page in the stack1002 }
1002 function popPage(page) {1003 }
1003 var tmpPages = []1004 }
10041005 ]
1005 popping = true1006
10061007 Loader {
1007 while (currentPage !== page && depth > 0) {1008 id: musicToolbar
1008 tmpPages.push(currentPage)1009 anchors {
1009 pop()1010 bottom: parent.bottom
1010 }1011 left: parent.left
10111012 right: parent.right
1012 if (depth > 0) {1013 }
1013 pop()1014 asynchronous: true
1014 }1015 source: "components/MusicToolbar.qml"
10151016 visible: mainPageStack.currentPage.title !== i18n.tr("Now playing") &&
1016 for (var i=tmpPages.length - 1; i > -1; i--) {1017 mainPageStack.currentPage.title !== i18n.tr("Queue") &&
1017 push(tmpPages[i])1018 mainPageStack.currentPage.title !== i18n.tr("Select playlist") &&
1018 }1019 !firstRun &&
10191020 !wideAspect
1020 popping = false1021 z: 200 // put on top of everything else
1021 }1022 }
10221023
1023 // Set the current page, and any parent/stacks1024 PageStack {
1024 function setPage(childPage) {1025 id: mainPageStack
1025 if (!popping) {1026
1026 currentMusicPage = childPage;1027 // Properties storing the current page info
1027 }1028 property Page currentMusicPage: null // currentPage can be Tabs
1028 }1029 property bool popping: false
10291030
1030 Tabs {1031 /* Helper functions */
1031 id: tabs1032
1033 // Go back up the stack if possible
1034 function goBack() {
1035 if (depth > 1) {
1036 pop()
1037 }
1038 }
1039
1040 // Pop a specific page in the stack
1041 function popPage(page) {
1042 var tmpPages = []
1043
1044 popping = true
1045
1046 while (currentPage !== page && depth > 0) {
1047 tmpPages.push(currentPage)
1048 pop()
1049 }
1050
1051 if (depth > 0) {
1052 pop()
1053 }
1054
1055 for (var i=tmpPages.length - 1; i > -1; i--) {
1056 push(tmpPages[i])
1057 }
1058
1059 popping = false
1060 }
1061
1062 // Set the current page, and any parent/stacks
1063 function setPage(childPage) {
1064 if (!popping) {
1065 currentMusicPage = childPage;
1066 }
1067 }
1068
1069 Tabs {
1070 id: tabs
1071 anchors {
1072 fill: parent
1073 }
1074
1075 property Tab lastTab: selectedTab
1076
1077 onSelectedTabChanged: {
1078 // pause loading of the models in the old tab
1079 if (lastTab !== null && lastTab !== selectedTab) {
1080 allowLoading(lastTab, false);
1081 }
1082
1083 lastTab = selectedTab;
1084
1085 ensurePopulated(selectedTab);
1086 }
1087
1088 onSelectedTabIndexChanged: {
1089 if (loadedUI) { // store the tab index if changed by the user
1090 startupSettings.tabIndex = selectedTabIndex
1091 }
1092 }
1093
1094 // Use a repeater to 'hide' the recent tab when the model is empty
1095 // A repeater is used because the Tabs component respects adds and
1096 // removes. Whereas replacing the list tabChildren does not appear
1097 // to respect removes and setting the page as active: false causes
1098 // the page to be blank but the action to still in the overflow
1099 Repeater {
1100 id: recentTabRepeater
1101 // If the model has not loaded and at startup the db was not empty
1102 // then show recent or
1103 // If the workerlist has been set and it has values then show recent
1104 model: (!recentModel.preLoadComplete && !startupRecentEmpty) ||
1105 (recentModel.workerList !== undefined &&
1106 recentModel.workerList.length > 0) ? 1 : 0
1107 delegate: Component {
1108 // First tab is all music
1109 Tab {
1110 property bool populated: recentTabRepeater.populated
1111 property var loader: [recentModel.filterRecent]
1112 property bool loading: recentTabRepeater.loading
1113 property var model: [recentModel, albumTracksModel]
1114 id: recentTab
1115 objectName: "recentTab"
1116 anchors.fill: parent
1117 title: page.title
1118
1119 // Tab content begins here
1120 page: Recent {
1121 id: recentPage
1122 }
1123 }
1124 }
1125
1126 // Store the startup state of the db separately otherwise
1127 // it breaks the binding of model
1128 property bool startupRecentEmpty: Library.isRecentEmpty()
1129
1130 // cached values of the recent model that are copied when
1131 // the tab is created
1132 property bool loading: false
1133 property bool populated: false
1134
1135 onCountChanged: {
1136 if (count === 0 && loadedUI) {
1137 // Jump to the albums tab when recent is empty
1138 tabs.selectedTabIndex = albumsTab.index
1139 } else if (count > 0 && !loadedUI) {
1140 // UI is still loading and recent tab has been inserted
1141 // so move the selected index 'down' as the value is
1142 // not auto updated - this is for the case of loading
1143 // directly to the recent tab (otherwise the content
1144 // appears as the second tab but the tabs think they are
1145 // on the first tab)
1146 tabs.selectedTabIndex -= 1
1147 } else if (count > 0 && loadedUI) {
1148 // tab inserted while the app is running so move the
1149 // selected index 'up' to keep the same position
1150 tabs.selectedTabIndex += 1
1151 }
1152 }
1153 }
1154
1155 // Second tab is arists
1156 Tab {
1157 property bool populated: true
1158 property var loader: []
1159 property bool loading: false
1160 property var model: []
1161 id: artistsTab
1162 objectName: "artistsTab"
1163 anchors.fill: parent
1164 title: page.title
1165
1166 // tab content
1167 page: Artists {
1168 id: artistsPage
1169 }
1170 }
1171
1172 // third tab is albums
1173 Tab {
1174 property bool populated: true
1175 property var loader: []
1176 property bool loading: false
1177 property var model: []
1178 id: albumsTab
1179 objectName: "albumsTab"
1180 anchors.fill: parent
1181 title: page.title
1182
1183 // Tab content begins here
1184 page: Albums {
1185 id: albumsPage
1186 }
1187 }
1188
1189 // forth tab is genres
1190 Tab {
1191 property bool populated: true
1192 property var loader: []
1193 property bool loading: false
1194 property var model: []
1195 id: genresTab
1196 objectName: "genresTab"
1197 anchors.fill: parent
1198 title: page.title
1199
1200 // Tab content begins here
1201 page: Genres {
1202 id: genresPage
1203 }
1204 }
1205
1206 // fourth tab is all songs
1207 Tab {
1208 property bool populated: true
1209 property var loader: []
1210 property bool loading: false
1211 property var model: []
1212 id: songsTab
1213 objectName: "songsTab"
1214 anchors.fill: parent
1215 title: page.title
1216
1217 // Tab content begins here
1218 page: Songs {
1219 id: tracksPage
1220 }
1221 }
1222
1223
1224 // fifth tab is the playlists
1225 Tab {
1226 property bool populated: false
1227 property var loader: [playlistModel.filterPlaylists]
1228 property bool loading: false
1229 property var model: [playlistModel, albumTracksModel]
1230 id: playlistsTab
1231 objectName: "playlistsTab"
1232 anchors.fill: parent
1233 title: page.title
1234
1235 // Tab content begins here
1236 page: Playlists {
1237 id: playlistsPage
1238 }
1239 }
1240
1241 // Set the models in the tab to allow/disallow loading
1242 function allowLoading(tabToLoad, state)
1243 {
1244 if (tabToLoad !== undefined && tabToLoad.model !== undefined)
1245 {
1246 for (var i=0; i < tabToLoad.model.length; i++)
1247 {
1248 tabToLoad.model[i].canLoad = state;
1249 }
1250 }
1251 }
1252
1253 function ensurePopulated(selectedTab)
1254 {
1255 allowLoading(selectedTab, true); // allow loading of the models
1256
1257 if (!selectedTab.populated && !selectedTab.loading && loadedUI) {
1258 loading.visible = true
1259 selectedTab.loading = true
1260
1261 if (selectedTab.loader !== undefined)
1262 {
1263 for (var i=0; i < selectedTab.loader.length; i++)
1264 {
1265 selectedTab.loader[i]();
1266 }
1267 }
1268 }
1269 loading.visible = selectedTab.loading || !selectedTab.populated
1270 }
1271
1272 function pushNowPlaying()
1273 {
1274 // only push if on a different page
1275 if (mainPageStack.currentPage.title !== i18n.tr("Now playing")
1276 && mainPageStack.currentPage.title !== i18n.tr("Queue")) {
1277 mainPageStack.push(Qt.resolvedUrl("components/NowPlaying.qml"), {})
1278 }
1279
1280 if (mainPageStack.currentPage.title === i18n.tr("Queue")) {
1281 mainPageStack.currentPage.isListView = false; // ensure full view
1282 }
1283 }
1284 } // end of tabs
1285 }
1286
1287 Loader {
1288 id: emptyPageLoader
1289 // Do not be active if content-hub is importing due to the models resetting
1290 // this then causes the empty page loader to partially run then showing a blank header
1291 active: noMusic && !firstRun && contentHubWaitForFile.processId === -1
1032 anchors {1292 anchors {
1033 fill: parent1293 fill: parent
1034 }1294 }
10351295 source: "ui/LibraryEmptyState.qml"
1036 property Tab lastTab: selectedTab1296 visible: active
10371297
1038 onSelectedTabChanged: {1298 property bool noMusic: allSongsModel.rowCount === 0 && allSongsModelModel.status === SongsModel.Ready && loadedUI
1039 // pause loading of the models in the old tab1299 }
1040 if (lastTab !== null && lastTab !== selectedTab) {1300
1041 allowLoading(lastTab, false);1301 LoadingSpinnerComponent {
1042 }1302 id: loading
10431303 }
1044 lastTab = selectedTab;
1045
1046 ensurePopulated(selectedTab);
1047 }
1048
1049 onSelectedTabIndexChanged: {
1050 if (loadedUI) { // store the tab index if changed by the user
1051 startupSettings.tabIndex = selectedTabIndex
1052 }
1053 }
1054
1055 // Use a repeater to 'hide' the recent tab when the model is empty
1056 // A repeater is used because the Tabs component respects adds and
1057 // removes. Whereas replacing the list tabChildren does not appear
1058 // to respect removes and setting the page as active: false causes
1059 // the page to be blank but the action to still in the overflow
1060 Repeater {
1061 id: recentTabRepeater
1062 // If the model has not loaded and at startup the db was not empty
1063 // then show recent or
1064 // If the workerlist has been set and it has values then show recent
1065 model: (!recentModel.preLoadComplete && !startupRecentEmpty) ||
1066 (recentModel.workerList !== undefined &&
1067 recentModel.workerList.length > 0) ? 1 : 0
1068 delegate: Component {
1069 // First tab is all music
1070 Tab {
1071 property bool populated: recentTabRepeater.populated
1072 property var loader: [recentModel.filterRecent]
1073 property bool loading: recentTabRepeater.loading
1074 property var model: [recentModel, albumTracksModel]
1075 id: recentTab
1076 objectName: "recentTab"
1077 anchors.fill: parent
1078 title: page.title
1079
1080 // Tab content begins here
1081 page: Recent {
1082 id: recentPage
1083 }
1084 }
1085 }
1086
1087 // Store the startup state of the db separately otherwise
1088 // it breaks the binding of model
1089 property bool startupRecentEmpty: Library.isRecentEmpty()
1090
1091 // cached values of the recent model that are copied when
1092 // the tab is created
1093 property bool loading: false
1094 property bool populated: false
1095
1096 onCountChanged: {
1097 if (count === 0 && loadedUI) {
1098 // Jump to the albums tab when recent is empty
1099 tabs.selectedTabIndex = albumsTab.index
1100 } else if (count > 0 && !loadedUI) {
1101 // UI is still loading and recent tab has been inserted
1102 // so move the selected index 'down' as the value is
1103 // not auto updated - this is for the case of loading
1104 // directly to the recent tab (otherwise the content
1105 // appears as the second tab but the tabs think they are
1106 // on the first tab)
1107 tabs.selectedTabIndex -= 1
1108 } else if (count > 0 && loadedUI) {
1109 // tab inserted while the app is running so move the
1110 // selected index 'up' to keep the same position
1111 tabs.selectedTabIndex += 1
1112 }
1113 }
1114 }
1115
1116 // Second tab is arists
1117 Tab {
1118 property bool populated: true
1119 property var loader: []
1120 property bool loading: false
1121 property var model: []
1122 id: artistsTab
1123 objectName: "artistsTab"
1124 anchors.fill: parent
1125 title: page.title
1126
1127 // tab content
1128 page: Artists {
1129 id: artistsPage
1130 }
1131 }
1132
1133 // third tab is albums
1134 Tab {
1135 property bool populated: true
1136 property var loader: []
1137 property bool loading: false
1138 property var model: []
1139 id: albumsTab
1140 objectName: "albumsTab"
1141 anchors.fill: parent
1142 title: page.title
1143
1144 // Tab content begins here
1145 page: Albums {
1146 id: albumsPage
1147 }
1148 }
1149
1150 // forth tab is genres
1151 Tab {
1152 property bool populated: true
1153 property var loader: []
1154 property bool loading: false
1155 property var model: []
1156 id: genresTab
1157 objectName: "genresTab"
1158 anchors.fill: parent
1159 title: page.title
1160
1161 // Tab content begins here
1162 page: Genres {
1163 id: genresPage
1164 }
1165 }
1166
1167 // fourth tab is all songs
1168 Tab {
1169 property bool populated: true
1170 property var loader: []
1171 property bool loading: false
1172 property var model: []
1173 id: songsTab
1174 objectName: "songsTab"
1175 anchors.fill: parent
1176 title: page.title
1177
1178 // Tab content begins here
1179 page: Songs {
1180 id: tracksPage
1181 }
1182 }
1183
1184
1185 // fifth tab is the playlists
1186 Tab {
1187 property bool populated: false
1188 property var loader: [playlistModel.filterPlaylists]
1189 property bool loading: false
1190 property var model: [playlistModel, albumTracksModel]
1191 id: playlistsTab
1192 objectName: "playlistsTab"
1193 anchors.fill: parent
1194 title: page.title
1195
1196 // Tab content begins here
1197 page: Playlists {
1198 id: playlistsPage
1199 }
1200 }
1201
1202 // Set the models in the tab to allow/disallow loading
1203 function allowLoading(tabToLoad, state)
1204 {
1205 if (tabToLoad !== undefined && tabToLoad.model !== undefined)
1206 {
1207 for (var i=0; i < tabToLoad.model.length; i++)
1208 {
1209 tabToLoad.model[i].canLoad = state;
1210 }
1211 }
1212 }
1213
1214 function ensurePopulated(selectedTab)
1215 {
1216 allowLoading(selectedTab, true); // allow loading of the models
1217
1218 if (!selectedTab.populated && !selectedTab.loading && loadedUI) {
1219 loading.visible = true
1220 selectedTab.loading = true
1221
1222 if (selectedTab.loader !== undefined)
1223 {
1224 for (var i=0; i < selectedTab.loader.length; i++)
1225 {
1226 selectedTab.loader[i]();
1227 }
1228 }
1229 }
1230 loading.visible = selectedTab.loading || !selectedTab.populated
1231 }
1232
1233 function pushNowPlaying()
1234 {
1235 // only push if on a different page
1236 if (mainPageStack.currentPage.title !== i18n.tr("Now playing")
1237 && mainPageStack.currentPage.title !== i18n.tr("Queue")) {
1238 mainPageStack.push(Qt.resolvedUrl("ui/NowPlaying.qml"), {})
1239 }
1240
1241 if (mainPageStack.currentPage.title === i18n.tr("Queue")) {
1242 mainPageStack.currentPage.isListView = false; // ensure full view
1243 }
1244 }
1245 } // end of tabs
1246 }
1247
1248 Loader {
1249 id: emptyPageLoader
1250 // Do not be active if content-hub is importing due to the models resetting
1251 // this then causes the empty page loader to partially run then showing a blank header
1252 active: noMusic && !firstRun && contentHubWaitForFile.processId === -1
1253 anchors {
1254 fill: parent
1255 }
1256 source: "ui/LibraryEmptyState.qml"
1257 visible: active
1258
1259 property bool noMusic: allSongsModel.rowCount === 0 && allSongsModelModel.status === SongsModel.Ready && loadedUI
1260 }
1261
1262 LoadingSpinnerComponent {
1263 id: loading
1264 }1304 }
1265} // end of main view1305} // end of main view
12661306
=== renamed file 'app/ui/NowPlaying.qml' => 'app/ui/NowPlayingView.qml'
--- app/ui/NowPlaying.qml 2015-02-08 04:06:28 +0000
+++ app/ui/NowPlayingView.qml 2015-02-11 03:14:13 +0000
@@ -37,7 +37,7 @@
37 title: isListView ? queueTitle : nowPlayingTitle37 title: isListView ? queueTitle : nowPlayingTitle
38 visible: false38 visible: false
3939
40 property bool isListView: false40 property alias isListView: nowPlaying.isListView
41 // TRANSLATORS: this appears in the header with limited space (around 20 characters)41 // TRANSLATORS: this appears in the header with limited space (around 20 characters)
42 property string nowPlayingTitle: i18n.tr("Now playing") 42 property string nowPlayingTitle: i18n.tr("Now playing")
43 // TRANSLATORS: this appears in the header with limited space (around 20 characters)43 // TRANSLATORS: this appears in the header with limited space (around 20 characters)
@@ -137,511 +137,7 @@
137 }137 }
138 ]138 ]
139139
140 Item {140 NowPlaying {
141 id: fullview141 id: view
142 anchors {
143 top: parent.top
144 topMargin: mainView.header.height
145 }
146 height: parent.height - mainView.header.height - units.gu(9.5)
147 visible: !isListView
148 width: parent.width
149
150 BlurredBackground {
151 id: blurredBackground
152 anchors {
153 left: parent.left
154 right: parent.right
155 top: parent.top
156 }
157 art: albumImage.firstSource
158 height: parent.height - units.gu(7)
159
160 Item {
161 id: albumImageContainer
162 anchors {
163 horizontalCenter: parent.horizontalCenter
164 top: parent.top
165 }
166 height: parent.height
167 width: parent.width
168
169 CoverGrid {
170 id: albumImage
171 anchors.centerIn: parent
172 covers: [{art: player.currentMetaArt, author: player.currentMetaArtist, album: player.currentMetaAlbum}]
173 size: parent.width > parent.height ? parent.height : parent.width
174 }
175 }
176
177 Rectangle {
178 id: nowPlayingWideAspectLabelsBackground
179 anchors.bottom: parent.bottom
180 color: styleMusic.common.black
181 height: nowPlayingWideAspectTitle.lineCount === 1 ? units.gu(10) : units.gu(13)
182 opacity: 0.8
183 width: parent.width
184 }
185
186 /* Column for labels in wideAspect */
187 Column {
188 id: nowPlayingWideAspectLabels
189 spacing: units.gu(1)
190 anchors {
191 left: parent.left
192 leftMargin: units.gu(2)
193 right: parent.right
194 rightMargin: units.gu(2)
195 top: nowPlayingWideAspectLabelsBackground.top
196 topMargin: nowPlayingWideAspectTitle.lineCount === 1 ? units.gu(2) : units.gu(1.5)
197 }
198
199 /* Title of track */
200 Label {
201 id: nowPlayingWideAspectTitle
202 anchors {
203 left: parent.left
204 leftMargin: units.gu(1)
205 right: parent.right
206 rightMargin: units.gu(1)
207 }
208 color: styleMusic.playerControls.labelColor
209 elide: Text.ElideRight
210 fontSize: "x-large"
211 maximumLineCount: 2
212 objectName: "playercontroltitle"
213 text: trackQueue.model.count === 0 ? "" : player.currentMetaTitle === "" ? player.currentMetaFile : player.currentMetaTitle
214 wrapMode: Text.WordWrap
215 }
216
217 /* Artist of track */
218 Label {
219 id: nowPlayingWideAspectArtist
220 anchors {
221 left: parent.left
222 leftMargin: units.gu(1)
223 right: parent.right
224 rightMargin: units.gu(1)
225 }
226 color: styleMusic.nowPlaying.labelSecondaryColor
227 elide: Text.ElideRight
228 fontSize: "small"
229 text: trackQueue.model.count === 0 ? "" : player.currentMetaArtist
230 }
231 }
232
233 /* Detect cover art swipe */
234 MouseArea {
235 anchors.fill: parent
236 property string direction: "None"
237 property real lastX: -1
238
239 onPressed: lastX = mouse.x
240
241 onReleased: {
242 var diff = mouse.x - lastX
243 if (Math.abs(diff) < units.gu(4)) {
244 return;
245 } else if (diff < 0) {
246 player.nextSong()
247 } else if (diff > 0) {
248 player.previousSong()
249 }
250 }
251 }
252 }
253
254 /* Background for progress bar component */
255 Rectangle {
256 id: musicToolbarFullProgressBackground
257 anchors {
258 bottom: parent.bottom
259 left: parent.left
260 right: parent.right
261 top: blurredBackground.bottom
262 }
263 color: styleMusic.common.black
264 }
265
266 /* Progress bar component */
267 Item {
268 id: musicToolbarFullProgressContainer
269 anchors.left: parent.left
270 anchors.leftMargin: units.gu(3)
271 anchors.right: parent.right
272 anchors.rightMargin: units.gu(3)
273 anchors.top: blurredBackground.bottom
274 anchors.topMargin: units.gu(1)
275 height: units.gu(3)
276 width: parent.width
277
278 /* Position label */
279 Label {
280 id: musicToolbarFullPositionLabel
281 anchors.top: progressSliderMusic.bottom
282 anchors.topMargin: units.gu(-2)
283 anchors.left: parent.left
284 color: styleMusic.nowPlaying.labelSecondaryColor
285 fontSize: "small"
286 height: parent.height
287 horizontalAlignment: Text.AlignHCenter
288 text: durationToString(player.position)
289 verticalAlignment: Text.AlignVCenter
290 width: units.gu(3)
291 }
292
293 Slider {
294 id: progressSliderMusic
295 anchors.left: parent.left
296 anchors.right: parent.right
297 maximumValue: player.duration // load value at startup
298 objectName: "progressSliderShape"
299 style: UbuntuBlueSliderStyle {}
300 value: player.position // load value at startup
301
302 function formatValue(v) {
303 if (seeking) { // update position label while dragging
304 musicToolbarFullPositionLabel.text = durationToString(v)
305 }
306
307 return durationToString(v)
308 }
309
310 property bool seeking: false
311 property bool seeked: false
312
313 onSeekingChanged: {
314 if (seeking === false) {
315 musicToolbarFullPositionLabel.text = durationToString(player.position)
316 }
317 }
318
319 onPressedChanged: {
320 seeking = pressed
321
322 if (!pressed) {
323 seeked = true
324 player.seek(value)
325
326 musicToolbarFullPositionLabel.text = durationToString(value)
327 }
328 }
329
330 Connections {
331 target: player
332 onPositionChanged: {
333 // seeked is a workaround for bug 1310706 as the first position after a seek is sometimes invalid (0)
334 if (progressSliderMusic.seeking === false && !progressSliderMusic.seeked) {
335 musicToolbarFullPositionLabel.text = durationToString(player.position)
336 musicToolbarFullDurationLabel.text = durationToString(player.duration)
337
338 progressSliderMusic.value = player.position
339 progressSliderMusic.maximumValue = player.duration
340 }
341
342 progressSliderMusic.seeked = false;
343 }
344 onStopped: {
345 musicToolbarFullPositionLabel.text = durationToString(0);
346 musicToolbarFullDurationLabel.text = durationToString(0);
347 }
348 }
349 }
350
351 /* Duration label */
352 Label {
353 id: musicToolbarFullDurationLabel
354 anchors.top: progressSliderMusic.bottom
355 anchors.topMargin: units.gu(-2)
356 anchors.right: parent.right
357 color: styleMusic.nowPlaying.labelSecondaryColor
358 fontSize: "small"
359 height: parent.height
360 horizontalAlignment: Text.AlignHCenter
361 text: durationToString(player.duration)
362 verticalAlignment: Text.AlignVCenter
363 width: units.gu(3)
364 }
365 }
366 }
367
368 Loader {
369 id: queueListLoader
370 anchors {
371 fill: parent
372 }
373 asynchronous: true
374 sourceComponent: MultiSelectListView {
375 id: queueList
376 anchors {
377 bottomMargin: musicToolbarFullContainer.height + units.gu(2)
378 fill: parent
379 topMargin: units.gu(2)
380 }
381 delegate: queueDelegate
382 footer: Item {
383 height: mainView.height - (styleMusic.common.expandHeight + queueList.currentHeight) + units.gu(8)
384 }
385 model: trackQueue.model
386 objectName: "nowPlayingqueueList"
387
388 property int normalHeight: units.gu(6)
389 property int transitionDuration: 250 // transition length of animations
390
391 onCountChanged: customdebug("Queue: Now has: " + queueList.count + " tracks")
392
393 Component {
394 id: queueDelegate
395 ListItemWithActions {
396 id: queueListItem
397 color: player.currentIndex === index ? "#2c2c34" : styleMusic.mainView.backgroundColor
398 height: queueList.normalHeight
399 objectName: "nowPlayingListItem" + index
400 state: ""
401
402 leftSideAction: Remove {
403 onTriggered: trackQueue.removeQueueList([index])
404 }
405 multiselectable: true
406 reorderable: true
407 rightSideActions: [
408 AddToPlaylist{
409
410 }
411 ]
412
413 onItemClicked: {
414 customdebug("File: " + model.filename) // debugger
415 trackQueueClick(index); // toggle track state
416 }
417 onReorder: {
418 console.debug("Move: ", from, to);
419
420 trackQueue.model.move(from, to, 1);
421 Library.moveQueueItem(from, to);
422
423 // Maintain currentIndex with current song
424 if (from === player.currentIndex) {
425 player.currentIndex = to;
426 }
427 else if (from < player.currentIndex && to >= player.currentIndex) {
428 player.currentIndex -= 1;
429 }
430 else if (from > player.currentIndex && to <= player.currentIndex) {
431 player.currentIndex += 1;
432 }
433
434 queueIndex = player.currentIndex
435 }
436
437 Item {
438 id: trackContainer;
439 anchors {
440 fill: parent
441 }
442
443 NumberAnimation {
444 id: trackContainerReorderAnimation
445 target: trackContainer;
446 property: "anchors.leftMargin";
447 duration: queueList.transitionDuration;
448 to: units.gu(2)
449 }
450
451 NumberAnimation {
452 id: trackContainerResetAnimation
453 target: trackContainer;
454 property: "anchors.leftMargin";
455 duration: queueList.transitionDuration;
456 to: units.gu(0.5)
457 }
458
459 MusicRow {
460 id: musicRow
461 height: parent.height
462 column: Column {
463 Label {
464 id: trackTitle
465 color: player.currentIndex === index ? UbuntuColors.blue
466 : styleMusic.common.music
467 fontSize: "small"
468 objectName: "titleLabel"
469 text: model.title
470 }
471
472 Label {
473 id: trackArtist
474 color: styleMusic.common.subtitle
475 fontSize: "x-small"
476 objectName: "artistLabel"
477 text: model.author
478 }
479 }
480 }
481 }
482 }
483 }
484 }
485 visible: isListView
486 }
487
488 /* Full toolbar */
489 Rectangle {
490 id: musicToolbarFullContainer
491 anchors.bottom: parent.bottom
492 color: styleMusic.common.black
493 height: units.gu(10)
494 width: parent.width
495
496 /* Repeat button */
497 MouseArea {
498 id: nowPlayingRepeatButton
499 anchors.right: nowPlayingPreviousButton.left
500 anchors.rightMargin: units.gu(1)
501 anchors.verticalCenter: nowPlayingPlayButton.verticalCenter
502 height: units.gu(6)
503 opacity: player.repeat && !emptyPageLoader.noMusic ? 1 : .4
504 width: height
505 onClicked: player.repeat = !player.repeat
506
507 Icon {
508 id: repeatIcon
509 height: units.gu(3)
510 width: height
511 anchors.verticalCenter: parent.verticalCenter
512 anchors.horizontalCenter: parent.horizontalCenter
513 color: "white"
514 name: "media-playlist-repeat"
515 objectName: "repeatShape"
516 opacity: player.repeat && !emptyPageLoader.noMusic ? 1 : .4
517 }
518 }
519
520 /* Previous button */
521 MouseArea {
522 id: nowPlayingPreviousButton
523 anchors.right: nowPlayingPlayButton.left
524 anchors.rightMargin: units.gu(1)
525 anchors.verticalCenter: nowPlayingPlayButton.verticalCenter
526 height: units.gu(6)
527 opacity: trackQueue.model.count === 0 ? .4 : 1
528 width: height
529 onClicked: player.previousSong()
530
531 Icon {
532 id: nowPlayingPreviousIndicator
533 height: units.gu(3)
534 width: height
535 anchors.verticalCenter: parent.verticalCenter
536 anchors.horizontalCenter: parent.horizontalCenter
537 color: "white"
538 name: "media-skip-backward"
539 objectName: "previousShape"
540 opacity: 1
541 }
542 }
543
544 /* Play/Pause button */
545 MouseArea {
546 id: nowPlayingPlayButton
547 anchors.centerIn: parent
548 height: units.gu(10)
549 width: height
550 onClicked: player.toggle()
551
552 Icon {
553 id: nowPlayingPlayIndicator
554 height: units.gu(6)
555 width: height
556 anchors.verticalCenter: parent.verticalCenter
557 anchors.horizontalCenter: parent.horizontalCenter
558 opacity: emptyPageLoader.noMusic ? .4 : 1
559 color: "white"
560 name: player.playbackState === MediaPlayer.PlayingState ? "media-playback-pause" : "media-playback-start"
561 objectName: "playShape"
562 }
563 }
564
565 /* Next button */
566 MouseArea {
567 id: nowPlayingNextButton
568 anchors.left: nowPlayingPlayButton.right
569 anchors.leftMargin: units.gu(1)
570 anchors.verticalCenter: nowPlayingPlayButton.verticalCenter
571 height: units.gu(6)
572 opacity: trackQueue.model.count === 0 ? .4 : 1
573 width: height
574 onClicked: player.nextSong()
575
576 Icon {
577 id: nowPlayingNextIndicator
578 height: units.gu(3)
579 width: height
580 anchors.verticalCenter: parent.verticalCenter
581 anchors.horizontalCenter: parent.horizontalCenter
582 color: "white"
583 name: "media-skip-forward"
584 objectName: "forwardShape"
585 opacity: 1
586 }
587 }
588
589 /* Shuffle button */
590 MouseArea {
591 id: nowPlayingShuffleButton
592 anchors.left: nowPlayingNextButton.right
593 anchors.leftMargin: units.gu(1)
594 anchors.verticalCenter: nowPlayingPlayButton.verticalCenter
595 height: units.gu(6)
596 opacity: player.shuffle && !emptyPageLoader.noMusic ? 1 : .4
597 width: height
598 onClicked: player.shuffle = !player.shuffle
599
600 Icon {
601 id: shuffleIcon
602 height: units.gu(3)
603 width: height
604 anchors.verticalCenter: parent.verticalCenter
605 anchors.horizontalCenter: parent.horizontalCenter
606 color: "white"
607 name: "media-playlist-shuffle"
608 objectName: "shuffleShape"
609 opacity: player.shuffle && !emptyPageLoader.noMusic ? 1 : .4
610 }
611 }
612
613 /* Object which provides the progress bar when in the queue */
614 Rectangle {
615 id: playerControlsProgressBar
616 anchors {
617 bottom: parent.bottom
618 left: parent.left
619 right: parent.right
620 }
621 color: styleMusic.common.black
622 height: units.gu(0.25)
623 visible: isListView
624
625 Rectangle {
626 id: playerControlsProgressBarHint
627 anchors {
628 left: parent.left
629 bottom: parent.bottom
630 }
631 color: UbuntuColors.blue
632 height: parent.height
633 width: player.duration > 0 ? (player.position / player.duration) * playerControlsProgressBar.width : 0
634
635 Connections {
636 target: player
637 onPositionChanged: {
638 playerControlsProgressBarHint.width = (player.position / player.duration) * playerControlsProgressBar.width
639 }
640 onStopped: {
641 playerControlsProgressBarHint.width = 0;
642 }
643 }
644 }
645 }
646 }142 }
647}143}

Subscribers

People subscribed via source and target branches

to all changes: