Merge lp:~music-app-dev/music-app/prototype-now-playing-side-panel into lp:music-app
- prototype-now-playing-side-panel
- Merge into trunk
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 |
Related bugs: |
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 | } |