Merge lp:~ahayzen/music-app/now-playing-split-take-2 into lp:music-app/trusty

Proposed by Andrew Hayzen
Status: Rejected
Rejected by: Andrew Hayzen
Proposed branch: lp:~ahayzen/music-app/now-playing-split-take-2
Merge into: lp:music-app/trusty
Diff against target: 1345 lines (+647/-631)
4 files modified
MusicNowPlaying.qml (+179/-622)
MusicToolbar.qml (+2/-1)
common/Queue.qml (+455/-0)
music-app.qml (+11/-8)
To merge this branch: bzr merge lp:~ahayzen/music-app/now-playing-split-take-2
Reviewer Review Type Date Requested Status
Ubuntu Phone Apps Jenkins Bot continuous-integration Needs Fixing
Andrew Hayzen Needs Information
Review via email: mp+226720@code.launchpad.net

Commit message

* Split now playing and queue into separate subtabs
* Make a common/Queue.qml component which is reusable (for convergence later)

Description of the change

* Split now playing and queue into separate subtabs
* Make a common/Queue.qml component which is reusable (for convergence later)

This mp is to see the diff, and for any design/behaviour discussions NOT for code merging...yet ;)

To post a comment you must log in.
Revision history for this message
Andrew Hayzen (ahayzen) wrote :

Blocking as this needs design/behaviour input first.

review: Needs Information
Revision history for this message
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Andrew Hayzen (ahayzen) wrote :

Marking as WIP as I am going to defer this for now.

Unmerged revisions

521. By Andrew Hayzen

* Add support for MusicRow in Queue.qml

520. By Andrew Hayzen

* Merge of trunk

519. By Andrew Hayzen

* Fix to only jump to current if toolbar was raised on queue subtab

518. By Andrew Hayzen

* Performance tweaks

517. By Andrew Hayzen

* Switch to now playing when track selected from queue
* Toggle playback if current track is selected
* Fix for header not being shown correctly on now playing subtab
* Force toolbar to show when navigating to now playing

516. By Andrew Hayzen

* Don't ensureVisible when clicking on queue
* Animate queue highlight to fade in/out
* Switch to now playing if current track play

515. By Andrew Hayzen

* Tweaks to now playing page
* Fix to removing current track

514. By Andrew Hayzen

* Add common/Queue.qml to bzr

513. By Andrew Hayzen

* Split now playing and queue into separate subtabs
* Make a common/Queue.qml component which is reusable (for convergence later)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'MusicNowPlaying.qml'
2--- MusicNowPlaying.qml 2014-06-24 19:35:57 +0000
3+++ MusicNowPlaying.qml 2014-07-14 17:57:04 +0000
4@@ -30,12 +30,21 @@
5
6 MusicPage {
7 id: nowPlaying
8+ flickable: queueSelected ? queueList : nowPlayingFlick
9 objectName: "nowplayingpage"
10 title: i18n.tr("Now Playing")
11 visible: false
12
13- property int ensureVisibleIndex: 0 // ensure first index is visible at startup
14-
15+ onVisibleChanged: {
16+ if (visible && !nowPlayingHeader.queueSelected) {
17+ musicToolbar.showToolbar();
18+ }
19+ }
20+
21+ property alias ensureVisibleIndex: queueList.ensureVisibleIndex
22+ property alias queueSelected: nowPlayingHeader.queueSelected
23+
24+ /* Main background */
25 Rectangle {
26 anchors.fill: parent
27 color: styleMusic.nowPlaying.backgroundColor
28@@ -45,629 +54,177 @@
29 }
30 }
31
32- Component.onCompleted: {
33- onToolbarShownChanged.connect(jumpToCurrent)
34+ /* Queue section */
35+ Queue {
36+ id: queueList
37+ anchors {
38+ bottomMargin: musicToolbar.currentHeight
39+ fill: parent
40+ topMargin: nowPlayingHeader.height
41+ }
42+ clip: false
43+ onMovementStarted: musicToolbar.hideToolbar()
44+ visible: nowPlayingHeader.queueSelected
45 }
46
47 Connections {
48- target: player
49- onCurrentIndexChanged: {
50- if (player.source === "") {
51- return;
52- }
53-
54- collapseExpand(); // Collapse expanded tracks
55- queuelist.currentIndex = player.currentIndex;
56-
57- customdebug("MusicQueue update currentIndex: " + player.source);
58-
59- // Always jump to current track
60- nowPlaying.jumpToCurrent(musicToolbar.opened, nowPlaying, musicToolbar.currentTab)
61-
62- }
63- }
64-
65- function jumpToCurrent(shown, currentPage, currentTab)
66- {
67- // If the toolbar is shown, the page is now playing and snaptrack is enabled
68- if (shown && currentPage === nowPlaying && Settings.getSetting("snaptrack") === "1")
69- {
70- // Then position the view at the current index
71- queuelist.positionViewAtIndex(queuelist.currentIndex, ListView.Beginning);
72- }
73- }
74-
75- function positionAt(index) {
76- queuelist.positionViewAtIndex(index, ListView.Beginning);
77- queuelist.contentY -= header.height;
78- }
79-
80- ListView {
81- id: queuelist
82- objectName: "queuelist"
83- anchors.fill: parent
84- anchors.bottomMargin: musicToolbar.mouseAreaOffset + musicToolbar.minimizedHeight
85- spacing: units.gu(1)
86- delegate: queueDelegate
87- model: trackQueue.model
88- highlightFollowsCurrentItem: false
89- state: "normal"
90- states: [
91- State {
92- name: "normal"
93- PropertyChanges {
94- target: queuelist
95- interactive: true
96- }
97- },
98- State {
99- name: "reorder"
100- PropertyChanges {
101- target: queuelist
102- interactive: false
103- }
104- }
105- ]
106- footer: Item {
107- height: mainView.height - (styleMusic.common.expandHeight + queuelist.currentHeight) + units.gu(8)
108- }
109-
110- property int normalHeight: units.gu(12)
111- property int currentHeight: units.gu(48)
112- property int transitionDuration: 250 // transition length of animations
113-
114- onCountChanged: {
115- customdebug("Queue: Now has: " + queuelist.count + " tracks")
116- }
117-
118- onMovementStarted: {
119- musicToolbar.hideToolbar();
120- }
121-
122- Component {
123- id: queueDelegate
124- ListItem.Standard {
125- id: queueListItem
126- height: queuelist.normalHeight
127- state: queuelist.currentIndex == index ? "current" : ""
128-
129- // cached height used to restore the height after expansion
130- property int cachedHeight: -1
131-
132- SwipeDelete {
133- id: swipeBackground
134- duration: queuelist.transitionDuration
135-
136- onDeleteStateChanged: {
137- if (deleteState === true)
138- {
139- queueListItemRemoveAnimation.start();
140- }
141- }
142- }
143-
144- function onCollapseSwipeDelete(indexCol)
145- {
146- if ((indexCol !== index || indexCol === -1) && swipeBackground !== undefined && swipeBackground.direction !== "")
147- {
148- customdebug("auto collapse swipeDelete")
149- queueListItemResetStartAnimation.start();
150- }
151- }
152-
153- Component.onCompleted: {
154- collapseSwipeDelete.connect(onCollapseSwipeDelete);
155- }
156-
157- MouseArea {
158- id: queueArea
159- anchors.fill: parent
160-
161- property int startX: queueListItem.x
162- property int startY: queueListItem.y
163- property int startMouseY: -1
164-
165- // Allow dragging on the X axis for swipeDelete if not reordering
166- drag.target: queueListItem
167- drag.axis: Drag.XAxis
168- drag.minimumX: queuelist.state == "reorder" ? 0 : -queueListItem.width
169- drag.maximumX: queuelist.state == "reorder" ? 0 : queueListItem.width
170-
171- /* Get the mouse and item difference from the starting positions */
172- function getDiff(mouseY)
173- {
174- return (mouseY - startMouseY) + (queueListItem.y - startY);
175- }
176-
177- /*
178- * Has the mouse crossed the current item
179- * True - it has crossed
180- * NULL - it is on the current
181- * False - it has not crossed
182- */
183- function hasCrossedCurrent(diff, currentOffset)
184- {
185- // Only crossed if in same direction
186- if ((diff > 0 || currentOffset > 0) && (diff <= 0 || currentOffset <= 0))
187- {
188- return false;
189- }
190-
191- if (Math.abs(diff) > (Math.abs(currentOffset) * queuelist.normalHeight) + queuelist.currentHeight)
192- {
193- return true;
194- }
195- else if (Math.abs(diff) > (Math.abs(currentOffset) * queuelist.normalHeight))
196- {
197- return null;
198- }
199- else
200- {
201- return false;
202- }
203- }
204-
205- function getNewIndex(mouseY, index)
206- {
207- var diff = getDiff(mouseY);
208- var negPos = diff < 0 ? -1 : 1;
209- var currentOffset = queuelist.currentIndex - index; // get the current offset
210-
211- if (currentOffset < 0) // when current is less the offset is actually +1
212- {
213- currentOffset += 1;
214- }
215-
216- var hasCrossed = hasCrossedCurrent(diff, currentOffset);
217-
218- if (hasCrossed === true)
219- {
220- /* Take off difference so it just appears like a normalheight
221- * minus when after and add when before */
222- diff -= negPos * (queuelist.currentHeight - queuelist.normalHeight);
223- }
224- else if (hasCrossed === null)
225- {
226- // Work out how far into the current item it is
227- var tmpDiff = Math.abs(diff) - (Math.abs(currentOffset) * queuelist.normalHeight);
228-
229- // Scale difference so is the same as a normalHeight
230- tmpDiff *= (queuelist.normalHeight / queuelist.currentHeight);
231-
232- // rebuild Diff with new values
233- diff = (currentOffset * queuelist.normalHeight) + (negPos * tmpDiff);
234- }
235-
236- return index + (Math.round(diff / queuelist.normalHeight));
237- }
238-
239- onClicked: {
240- collapseSwipeDelete(-1); // collapse all expands
241- customdebug("File: " + model.filename) // debugger
242- trackQueueClick(index); // toggle track state
243- }
244-
245- onMouseXChanged: {
246- // Only allow XChange if not in reorder state
247- if (queuelist.state == "reorder")
248- {
249- return;
250- }
251-
252- // New X is less than start so swiping left
253- if (queueListItem.x < startX)
254- {
255- collapseExpand();
256- swipeBackground.state = "swipingLeft";
257- startY = queueListItem.y;
258- }
259- // New X is greater sow swiping right
260- else if (queueListItem.x > startX)
261- {
262- collapseExpand();
263- swipeBackground.state = "swipingRight";
264- startY = queueListItem.y;
265- }
266- // Same so reset state back to normal
267- else
268- {
269- swipeBackground.state = "normal";
270- queuelist.state = "normal";
271- }
272- }
273-
274- onMouseYChanged: {
275- // Y change only affects when in reorder mode
276- if (queuelist.state == "reorder")
277- {
278- /* update the listitem y position so that the
279- * listitem horizontalCenter is under the mouse.y */
280- queueListItem.y += mouse.y - (queueListItem.height / 2);
281- }
282- }
283-
284- onPressed: {
285- startX = queueListItem.x;
286- startY = queueListItem.y;
287- startMouseY = mouse.y;
288- }
289-
290- onPressAndHold: {
291- // Must be in a normal state to change to reorder state
292- if (queuelist.state == "normal" && swipeBackground.state == "normal" && queuelist.currentIndex != index)
293- {
294- collapseSwipeDelete(-1); // collapse all swipedeletes
295- collapseExpand(); // collapse all
296- customdebug("Pressed and held queued track "+model.filename)
297- queuelist.state = "reorder"; // enable reordering state
298- trackContainerReorderAnimation.start();
299- }
300- }
301-
302- onReleased: {
303- // Get current state to determine what to do
304- if (queuelist.state == "reorder")
305- {
306- var newIndex = getNewIndex(mouse.y + (queueListItem.height / 2), index); // get new index
307-
308- // Indexes larger than current need -1 because when it is moved the current is removed
309- if (newIndex > index)
310- {
311- newIndex -= 1;
312- }
313-
314- if (newIndex === index)
315- {
316- queueListItemResetAnimation.start(); // reset item position
317- trackContainerResetAnimation.start(); // reset the trackContainer
318- }
319- else
320- {
321- queueListItem.x = startX; // ensure X position is correct
322- trackContainerResetAnimation.start(); // reset the trackContainer
323-
324- // Check that the newIndex is within the range
325- if (newIndex < 0)
326- {
327- newIndex = 0;
328- }
329- else if (newIndex > queuelist.count - 1)
330- {
331- newIndex = queuelist.count - 1;
332- }
333-
334- console.debug("Move: " + index + " To: " + newIndex);
335- queuelist.model.move(index, newIndex, 1); // update the model
336- }
337- }
338- else if (swipeBackground.state == "swipingLeft" || swipeBackground.state == "swipingRight")
339- {
340- var moved = Math.abs(queueListItem.x - startX);
341-
342- // Make sure that item has been dragged far enough
343- if (moved > queueListItem.width / 2 || (swipeBackground.primed === true && moved > units.gu(5)))
344- {
345- if (swipeBackground.primed === false)
346- {
347- collapseSwipeDelete(index); // collapse other swipeDeletes
348-
349- // Move the listitem half way across to reveal the delete button
350- queueListItemPrepareRemoveAnimation.start();
351- }
352- else
353- {
354- // Check that actually swiping to cancel
355- if (swipeBackground.direction !== "" &&
356- swipeBackground.direction !== swipeBackground.state)
357- {
358- // Reset the listitem to the centre
359- queueListItemResetStartAnimation.start();
360- }
361- else
362- {
363- // Reset the listitem to the centre
364- queueListItemResetAnimation.start();
365- }
366- }
367- }
368- else
369- {
370- // Reset the listitem to the centre
371- queueListItemResetAnimation.start();
372- }
373- }
374-
375- // ensure states are normal
376- swipeBackground.state = "normal";
377- queuelist.state = "normal";
378- }
379-
380- // Animation to reset the x, y of the queueitem
381- ParallelAnimation {
382- id: queueListItemResetAnimation
383- running: false
384- NumberAnimation { // reset X
385- target: queueListItem
386- property: "x"
387- to: queueArea.startX
388- duration: queuelist.transitionDuration
389- }
390- NumberAnimation { // reset Y
391- target: queueListItem
392- property: "y"
393- to: queueArea.startY
394- duration: queuelist.transitionDuration
395- }
396- }
397-
398- // Animation to reset the x, y of the item
399- ParallelAnimation {
400- id: queueListItemResetStartAnimation
401- running: false
402- NumberAnimation { // reset X
403- target: queueListItem
404- property: "x"
405- to: 0
406- duration: queuelist.transitionDuration
407- }
408- NumberAnimation { // reset Y
409- target: queueListItem
410- property: "y"
411- to: queueArea.startY
412- duration: queuelist.transitionDuration
413- }
414- onRunningChanged: {
415- if (running === true)
416- {
417- swipeBackground.direction = "";
418- swipeBackground.primed = false;
419- }
420- }
421- }
422-
423- // Move the listitem half way across to reveal the delete button
424- NumberAnimation {
425- id: queueListItemPrepareRemoveAnimation
426- target: queueListItem
427- property: "x"
428- to: swipeBackground.state == "swipingRight" ? queueListItem.width / 2 : 0 - (queueListItem.width / 2)
429- duration: queuelist.transitionDuration
430- onRunningChanged: {
431- if (running === true)
432- {
433- swipeBackground.direction = swipeBackground.state;
434- swipeBackground.primed = true;
435- }
436- }
437- }
438-
439- ParallelAnimation {
440- id: queueListItemRemoveAnimation
441- running: false
442- NumberAnimation { // 'slide' up
443- target: queueListItem
444- property: "height"
445- to: 0
446- duration: queuelist.transitionDuration
447- }
448- NumberAnimation { // 'slide' in direction of removal
449- target: queueListItem
450- property: "x"
451- to: swipeBackground.direction === "swipingLeft" ? 0 - queueListItem.width : queueListItem.width
452- duration: queuelist.transitionDuration
453- }
454- onRunningChanged: {
455- if (running === false)
456- {
457- // Remove the item
458- if (index == queuelist.currentIndex)
459- {
460- if (queuelist.count > 1)
461- {
462- // Next song and only play if currently playing
463- player.nextSong(player.isPlaying);
464- }
465- else
466- {
467- player.stop();
468- }
469- }
470-
471- if (index < player.currentIndex) {
472- player.currentIndex -= 1;
473- }
474-
475- // Remove item from queue and clear caches
476- trackQueue.model.remove(index);
477- }
478- }
479- }
480- }
481-
482- onFocusChanged: {
483- if (focus == false) {
484- selected = false
485- } else {
486- selected = false
487- }
488- }
489-
490- Rectangle {
491- id: trackContainer;
492- anchors {
493- fill: parent
494- margins: units.gu(0.5)
495- rightMargin: expandable.expanderButtonWidth
496- }
497- color: "transparent"
498-
499- NumberAnimation {
500- id: trackContainerReorderAnimation
501- target: trackContainer;
502- property: "anchors.leftMargin";
503- duration: queuelist.transitionDuration;
504- to: units.gu(2)
505- }
506-
507- NumberAnimation {
508- id: trackContainerResetAnimation
509- target: trackContainer;
510- property: "anchors.leftMargin";
511- duration: queuelist.transitionDuration;
512- to: units.gu(0.5)
513- }
514-
515- UbuntuShape {
516- id: trackImage
517- anchors.left: parent.left
518- anchors.leftMargin: units.gu(1.5)
519- anchors.top: parent.top
520- height: (queueListItem.state === "current" ? queuelist.currentHeight - units.gu(8) : queuelist.normalHeight) - units.gu(2)
521- width: height
522- image: Image {
523- source: "image://albumart/artist=" + model.author + "&album=" + model.album
524- onStatusChanged: {
525- if (status === Image.Error) {
526- source = Qt.resolvedUrl("images/music-app-cover@30.png")
527- }
528- }
529- }
530-
531- function calcAnchors() {
532- if (trackImage.height > queuelist.normalHeight && mainView.wideAspect) {
533- trackImage.anchors.left = undefined
534- trackImage.anchors.horizontalCenter = trackImage.parent.horizontalCenter
535- } else {
536- trackImage.anchors.left = trackImage.parent.left
537- trackImage.anchors.horizontalCenter = undefined
538- }
539-
540- trackImage.width = trackImage.height; // force width to match height
541- }
542-
543- Connections {
544- target: mainView
545- onWideAspectChanged: trackImage.calcAnchors()
546- }
547-
548- onHeightChanged: {
549- calcAnchors()
550- }
551- Behavior on height {
552- NumberAnimation {
553- target: trackImage;
554- property: "height";
555- duration: queuelist.transitionDuration;
556- }
557- }
558- }
559- Label {
560- id: nowPlayingArtist
561- objectName: "nowplayingartist"
562- color: styleMusic.nowPlaying.labelSecondaryColor
563- elide: Text.ElideRight
564- height: units.gu(1)
565- text: model.author
566- fontSize: 'small'
567- width: parent.width - trackImage.width - units.gu(3.5)
568- x: trackImage.x + trackImage.width + units.gu(1)
569- y: trackImage.y + units.gu(1)
570- }
571- Label {
572- id: nowPlayingTitle
573- objectName: "nowplayingtitle"
574- color: styleMusic.common.white
575- elide: Text.ElideRight
576- height: units.gu(1)
577- text: model.title
578- fontSize: 'medium'
579- width: parent.width - trackImage.width - units.gu(3.5)
580- x: trackImage.x + trackImage.width + units.gu(1)
581- y: nowPlayingArtist.y + nowPlayingArtist.height + units.gu(1.25)
582- }
583- Label {
584- id: nowPlayingAlbum
585- objectName: "nowplayingalbum"
586- color: styleMusic.nowPlaying.labelSecondaryColor
587- elide: Text.ElideRight
588- height: units.gu(1)
589- text: model.album
590- fontSize: 'x-small'
591- width: parent.width - trackImage.width - units.gu(3.5)
592- x: trackImage.x + trackImage.width + units.gu(1)
593- y: nowPlayingTitle.y + nowPlayingTitle.height + units.gu(1.25)
594- }
595- }
596-
597- Expander {
598- id: expandable
599- anchors {
600- fill: parent
601- }
602- actualListItemHeight: queueListItem.state === "current" ?
603- queuelist.currentHeight :
604- queuelist.normalHeight
605- buttonEnabled: !swipeBackground.primed
606- expanderButtonCentreFromBottom: queuelist.normalHeight - (trackContainer.anchors.margins * 2) - nowPlayingTitle.y - (nowPlayingTitle.height / 2)
607- listItem: queueListItem
608- model: trackQueue.model.get(index)
609- row: Row {
610- AddToPlaylist {
611- }
612- }
613- Behavior on actualListItemHeight {
614- NumberAnimation {
615- target: expandable;
616- property: "actualListItemHeight";
617- duration: queuelist.transitionDuration;
618- }
619- }
620- }
621-
622- states: State {
623- name: "current"
624- PropertyChanges {
625- target: queueListItem
626- height: queuelist.currentHeight
627- }
628- PropertyChanges {
629- target: nowPlayingArtist
630- width: trackImage.width
631- x: trackImage.x
632- y: trackImage.y + trackImage.height + units.gu(0.5)
633- }
634- PropertyChanges {
635- target: nowPlayingTitle
636- width: trackImage.width
637- x: trackImage.x
638- y: nowPlayingArtist.y + nowPlayingArtist.height + units.gu(1.25)
639- }
640- PropertyChanges {
641- target: nowPlayingAlbum
642- width: trackImage.width
643- x: trackImage.x
644- y: nowPlayingTitle.y + nowPlayingTitle.height + units.gu(1.25)
645- }
646- PropertyChanges {
647- target: expandable
648- expanderButtonCentreFromBottom: queuelist.currentHeight - (trackContainer.anchors.margins * 2) - nowPlayingTitle.y - (nowPlayingTitle.height / 2)
649- }
650- }
651- transitions: Transition {
652- from: ",current"
653- to: "current,"
654- NumberAnimation {
655- duration: queuelist.transitionDuration
656- properties: "height,opacity,width,x,y"
657- }
658-
659- onRunningChanged: {
660- if (running === false && ensureVisibleIndex != -1)
661- {
662- queuelist.positionViewAtIndex(ensureVisibleIndex, ListView.Beginning);
663- ensureVisibleIndex = -1;
664- }
665- }
666+ target: musicToolbar
667+ onOpenedChanged: {
668+ if (musicToolbar.opened && musicToolbar.currentPage === nowPlaying
669+ && queueSelected) {
670+ queueList.ensureVisibleIndex = -1;
671+ queueList.ensureVisibleIndex = player.currentIndex;
672+ }
673+ }
674+ }
675+
676+ /* Now Playing section */
677+ Flickable {
678+ id: nowPlayingFlick
679+ anchors {
680+ bottomMargin: musicToolbar.currentHeight
681+ fill: parent
682+ topMargin: nowPlayingHeader.height
683+ }
684+ contentHeight: trackContainer.height
685+ visible: !nowPlayingHeader.queueSelected
686+
687+ Column {
688+ id: trackContainer;
689+ anchors {
690+ horizontalCenter: parent.horizontalCenter
691+ }
692+ spacing: units.gu(0.5)
693+
694+ // Top margin
695+ Rectangle {
696+ color: "transparent"
697+ height: units.gu(1)
698+ width: parent.width
699+ }
700+
701+ UbuntuShape {
702+ id: trackImage
703+ height: units.gu(32)
704+ width: height
705+ image: Image {
706+ source: "image://albumart/artist=" + player.currentMetaArtist + "&album=" + player.currentMetaAlbum
707+ onStatusChanged: {
708+ if (status === Image.Error) {
709+ source = Qt.resolvedUrl("images/music-app-cover@30.png")
710+ }
711+ }
712+ }
713+ }
714+ Label {
715+ id: nowPlayingArtist
716+ objectName: "nowplayingartist"
717+ color: styleMusic.nowPlaying.labelSecondaryColor
718+ elide: Text.ElideRight
719+ text: player.currentMetaArtist
720+ fontSize: 'small'
721+ width: parent.width
722+ }
723+ Label {
724+ id: nowPlayingTitle
725+ objectName: "nowplayingtitle"
726+ color: styleMusic.common.white
727+ elide: Text.ElideRight
728+ text: player.currentMetaTitle
729+ fontSize: 'medium'
730+ width: parent.width
731+ }
732+ Label {
733+ id: nowPlayingAlbum
734+ objectName: "nowplayingalbum"
735+ color: styleMusic.nowPlaying.labelSecondaryColor
736+ elide: Text.ElideRight
737+ text: player.currentMetaAlbum
738+ fontSize: 'x-small'
739+ width: parent.width
740+ }
741+ }
742+
743+ MouseArea {
744+ anchors {
745+ fill: trackContainer
746+ }
747+ onClicked: player.toggle()
748+ }
749+ }
750+
751+ /* Header section */
752+ Rectangle {
753+ id: nowPlayingHeader
754+ anchors {
755+ left: parent.left
756+ right: parent.right
757+ top: parent.top
758+ topMargin: header.height + header.y // FIXME: minor hack
759+ }
760+ color: "transparent"
761+ height: units.gu(5)
762+
763+ property bool queueSelected: true
764+
765+ onQueueSelectedChanged: {
766+ if (!queueSelected) {
767+ musicToolbar.showToolbar();
768+ }
769+ }
770+
771+ Rectangle {
772+ anchors {
773+ fill: parent
774+ }
775+ color: styleMusic.nowPlaying.backgroundColor
776+ opacity: 0.75
777+ }
778+
779+ Row {
780+ anchors {
781+ fill: parent
782+ }
783+ Label {
784+ anchors {
785+ top: parent.top
786+ bottom: parent.bottom
787+ }
788+ color: nowPlayingHeader.queueSelected ? UbuntuColors.orange : UbuntuColors.warmGrey
789+ horizontalAlignment: Text.AlignHCenter
790+ text: i18n.tr("Queue")
791+ verticalAlignment: Text.AlignVCenter
792+ width: parent.width / 2
793+
794+ MouseArea {
795+ anchors.fill: parent
796+ onClicked: nowPlayingHeader.queueSelected = true
797+ }
798+ }
799+
800+ Rectangle {
801+ anchors {
802+ top: parent.top
803+ bottom: parent.bottom
804+ margins: units.gu(1)
805+ }
806+ width: 1
807+ }
808+
809+ Label {
810+ anchors {
811+ top: parent.top
812+ bottom: parent.bottom
813+ }
814+ color: !nowPlayingHeader.queueSelected ? UbuntuColors.orange : UbuntuColors.warmGrey
815+ horizontalAlignment: Text.AlignHCenter
816+ text: i18n.tr("Now Playing")
817+ verticalAlignment: Text.AlignVCenter
818+ width: parent.width / 2
819+
820+ MouseArea {
821+ anchors.fill: parent
822+ onClicked: nowPlayingHeader.queueSelected = false
823 }
824 }
825 }
826
827=== modified file 'MusicToolbar.qml'
828--- MusicToolbar.qml 2014-07-02 22:28:59 +0000
829+++ MusicToolbar.qml 2014-07-14 17:57:04 +0000
830@@ -42,12 +42,13 @@
831 property bool shown: false
832 property int transitionDuration: 100
833
834- property alias currentHeight: musicToolbarPanel.height
835 property alias minimizedHeight: musicToolbarPanel.minimizedHeight
836 property alias expandedHeight: musicToolbarPanel.expandedHeight
837 property alias fullHeight: musicToolbarPanel.fullHeight
838 property alias mouseAreaOffset: musicToolbarPanel.hintSize
839
840+ property int currentHeight: opened ? musicToolbarPanel.height : minimizedHeight
841+
842 property alias animating: musicToolbarPanel.animating
843 property alias opened: musicToolbarPanel.opened
844
845
846=== added file 'common/Queue.qml'
847--- common/Queue.qml 1970-01-01 00:00:00 +0000
848+++ common/Queue.qml 2014-07-14 17:57:04 +0000
849@@ -0,0 +1,455 @@
850+/*
851+ * Copyright (C) 2014 Andrew Hayzen <ahayzen@gmail.com>
852+ * Daniel Holm <d.holmen@gmail.com>
853+ * Victor Thompson <victor.thompson@gmail.com>
854+ *
855+ * This program is free software; you can redistribute it and/or modify
856+ * it under the terms of the GNU General Public License as published by
857+ * the Free Software Foundation; version 3.
858+ *
859+ * This program is distributed in the hope that it will be useful,
860+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
861+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
862+ * GNU General Public License for more details.
863+ *
864+ * You should have received a copy of the GNU General Public License
865+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
866+ */
867+
868+import QtQuick 2.0
869+import Ubuntu.Components 0.1
870+import Ubuntu.Components.ListItems 0.1 as ListItem
871+import "ExpanderItems"
872+
873+ListView {
874+ id: queueListView
875+ anchors {
876+ fill: parent
877+ }
878+ clip: true
879+ model: trackQueue.model
880+ state: "normal"
881+ states: [
882+ State {
883+ name: "normal"
884+ PropertyChanges {
885+ target: queueListView
886+ interactive: true
887+ }
888+ },
889+ State {
890+ name: "reorder"
891+ PropertyChanges {
892+ target: queueListView
893+ interactive: false
894+ }
895+ }
896+ ]
897+
898+ property int ensureVisibleIndex: 0 // ensure first index is visible at startup
899+ property int normalHeight: styleMusic.common.itemHeight
900+ property int transitionDuration: 250 // transition length of animations
901+
902+ onEnsureVisibleIndexChanged: {
903+ if (ensureVisibleIndex === -1) {
904+ return
905+ }
906+
907+ queueListView.positionViewAtIndex(ensureVisibleIndex, ListView.Center);
908+ }
909+
910+ delegate: ListItem.Standard {
911+ id: queueListItem
912+ height: styleMusic.common.itemHeight
913+
914+ // cached height used to restore the height after expansion
915+ property int cachedHeight: -1
916+
917+ SwipeDelete {
918+ id: swipeBackground
919+ duration: queueListView.transitionDuration
920+
921+ onDeleteStateChanged: {
922+ if (deleteState === true) {
923+ queueListItemRemoveAnimation.start();
924+ }
925+ }
926+ }
927+
928+ function onCollapseSwipeDelete(indexCol)
929+ {
930+ if ((indexCol !== index || indexCol === -1) && swipeBackground !== undefined && swipeBackground.direction !== "") {
931+ customdebug("auto collapse swipeDelete")
932+ queueListItemResetStartAnimation.start();
933+ }
934+ }
935+
936+ Component.onCompleted: {
937+ collapseSwipeDelete.connect(onCollapseSwipeDelete);
938+ }
939+
940+ MouseArea {
941+ id: queueArea
942+ anchors.fill: parent
943+
944+ property int startX: queueListItem.x
945+ property int startY: queueListItem.y
946+ property int startMouseY: -1
947+
948+ // Allow dragging on the X axis for swipeDelete if not reordering
949+ drag.target: queueListItem
950+ drag.axis: Drag.XAxis
951+ drag.minimumX: queueListView.state === "reorder" ? 0 : -queueListItem.width
952+ drag.maximumX: queueListView.state === "reorder" ? 0 : queueListItem.width
953+
954+ /* Get the mouse and item difference from the starting positions */
955+ function getDiff(mouseY)
956+ {
957+ return (mouseY - startMouseY) + (queueListItem.y - startY);
958+ }
959+
960+ function getNewIndex(mouseY, index)
961+ {
962+ var diff = getDiff(mouseY);
963+ var negPos = diff < 0 ? -1 : 1;
964+ var currentOffset = queueListView.currentIndex - index; // get the current offset
965+
966+ if (currentOffset < 0) // when current is less the offset is actually +1
967+ {
968+ currentOffset += 1;
969+ }
970+
971+ return index + (Math.round(diff / queueListView.normalHeight));
972+ }
973+
974+ onClicked: {
975+ collapseSwipeDelete(-1); // collapse all expands
976+ customdebug("File: " + model.filename) // debugger
977+ trackQueueClick(index)
978+ }
979+
980+ onMouseXChanged: {
981+ // Only allow XChange if not in reorder state
982+ if (queueListView.state === "reorder")
983+ {
984+ return;
985+ }
986+
987+ // New X is less than start so swiping left
988+ if (queueListItem.x < startX)
989+ {
990+ collapseExpand();
991+ swipeBackground.state = "swipingLeft";
992+ startY = queueListItem.y;
993+ }
994+ // New X is greater sow swiping right
995+ else if (queueListItem.x > startX)
996+ {
997+ collapseExpand();
998+ swipeBackground.state = "swipingRight";
999+ startY = queueListItem.y;
1000+ }
1001+ // Same so reset state back to normal
1002+ else
1003+ {
1004+ swipeBackground.state = "normal";
1005+ queueListView.state = "normal";
1006+ }
1007+ }
1008+
1009+ onMouseYChanged: {
1010+ // Y change only affects when in reorder mode
1011+ if (queueListView.state === "reorder")
1012+ {
1013+ /* update the listitem y position so that the
1014+ * listitem horizontalCenter is under the mouse.y */
1015+ queueListItem.y += mouse.y - (queueListItem.height / 2);
1016+ }
1017+ }
1018+
1019+ onPressed: {
1020+ startX = queueListItem.x;
1021+ startY = queueListItem.y;
1022+ startMouseY = mouse.y;
1023+ }
1024+
1025+ onPressAndHold: {
1026+ // Must be in a normal state to change to reorder state
1027+ if (queueListView.state === "normal" && swipeBackground.state == "normal" && queueListView.currentIndex !== index)
1028+ {
1029+ collapseSwipeDelete(-1); // collapse all swipedeletes
1030+ collapseExpand(); // collapse all
1031+ customdebug("Pressed and held queued track "+model.filename)
1032+ queueListView.state = "reorder"; // enable reordering state
1033+ trackContainerReorderAnimation.start();
1034+ }
1035+ }
1036+
1037+ onReleased: {
1038+ // Get current state to determine what to do
1039+ if (queueListView.state === "reorder")
1040+ {
1041+ var newIndex = getNewIndex(mouse.y + (queueListItem.height / 2), index); // get new index
1042+
1043+ // Indexes larger than current need -1 because when it is moved the current is removed
1044+ if (newIndex > index)
1045+ {
1046+ newIndex -= 1;
1047+ }
1048+
1049+ if (newIndex === index)
1050+ {
1051+ queueListItemResetAnimation.start(); // reset item position
1052+ trackContainerResetAnimation.start(); // reset the trackContainer
1053+ }
1054+ else
1055+ {
1056+ queueListItem.x = startX; // ensure X position is correct
1057+ trackContainerResetAnimation.start(); // reset the trackContainer
1058+
1059+ // Check that the newIndex is within the range
1060+ if (newIndex < 0)
1061+ {
1062+ newIndex = 0;
1063+ }
1064+ else if (newIndex > queueListView.count - 1)
1065+ {
1066+ newIndex = queueListView.count - 1;
1067+ }
1068+
1069+ console.debug("Move: " + index + " To: " + newIndex);
1070+ queueListView.model.move(index, newIndex, 1); // update the model
1071+ }
1072+ }
1073+ else if (swipeBackground.state == "swipingLeft" || swipeBackground.state == "swipingRight")
1074+ {
1075+ var moved = Math.abs(queueListItem.x - startX);
1076+
1077+ // Make sure that item has been dragged far enough
1078+ if (moved > queueListItem.width / 2 || (swipeBackground.primed === true && moved > units.gu(5)))
1079+ {
1080+ if (swipeBackground.primed === false)
1081+ {
1082+ collapseSwipeDelete(index); // collapse other swipeDeletes
1083+
1084+ // Move the listitem half way across to reveal the delete button
1085+ queueListItemPrepareRemoveAnimation.start();
1086+ }
1087+ else
1088+ {
1089+ // Check that actually swiping to cancel
1090+ if (swipeBackground.direction !== "" &&
1091+ swipeBackground.direction !== swipeBackground.state)
1092+ {
1093+ // Reset the listitem to the centre
1094+ queueListItemResetStartAnimation.start();
1095+ }
1096+ else
1097+ {
1098+ // Reset the listitem to the centre
1099+ queueListItemResetAnimation.start();
1100+ }
1101+ }
1102+ }
1103+ else
1104+ {
1105+ // Reset the listitem to the centre
1106+ queueListItemResetAnimation.start();
1107+ }
1108+ }
1109+
1110+ // ensure states are normal
1111+ swipeBackground.state = "normal";
1112+ queueListView.state = "normal";
1113+ }
1114+
1115+ // Animation to reset the x, y of the queueitem
1116+ ParallelAnimation {
1117+ id: queueListItemResetAnimation
1118+ running: false
1119+ NumberAnimation { // reset X
1120+ target: queueListItem
1121+ property: "x"
1122+ to: queueArea.startX
1123+ duration: queueListView.transitionDuration
1124+ }
1125+ NumberAnimation { // reset Y
1126+ target: queueListItem
1127+ property: "y"
1128+ to: queueArea.startY
1129+ duration: queueListView.transitionDuration
1130+ }
1131+ }
1132+
1133+ // Animation to reset the x, y of the item
1134+ ParallelAnimation {
1135+ id: queueListItemResetStartAnimation
1136+ running: false
1137+ NumberAnimation { // reset X
1138+ target: queueListItem
1139+ property: "x"
1140+ to: 0
1141+ duration: queueListView.transitionDuration
1142+ }
1143+ NumberAnimation { // reset Y
1144+ target: queueListItem
1145+ property: "y"
1146+ to: queueArea.startY
1147+ duration: queueListView.transitionDuration
1148+ }
1149+ onRunningChanged: {
1150+ if (running === true)
1151+ {
1152+ swipeBackground.direction = "";
1153+ swipeBackground.primed = false;
1154+ }
1155+ }
1156+ }
1157+
1158+ // Move the listitem half way across to reveal the delete button
1159+ NumberAnimation {
1160+ id: queueListItemPrepareRemoveAnimation
1161+ target: queueListItem
1162+ property: "x"
1163+ to: swipeBackground.state == "swipingRight" ? queueListItem.width / 2 : 0 - (queueListItem.width / 2)
1164+ duration: queueListView.transitionDuration
1165+ onRunningChanged: {
1166+ if (running === true)
1167+ {
1168+ swipeBackground.direction = swipeBackground.state;
1169+ swipeBackground.primed = true;
1170+ }
1171+ }
1172+ }
1173+
1174+ ParallelAnimation {
1175+ id: queueListItemRemoveAnimation
1176+ running: false
1177+ NumberAnimation { // 'slide' up
1178+ target: queueListItem
1179+ property: "height"
1180+ to: 0
1181+ duration: queueListView.transitionDuration
1182+ }
1183+ NumberAnimation { // 'slide' in direction of removal
1184+ target: queueListItem
1185+ property: "x"
1186+ to: swipeBackground.direction === "swipingLeft" ? 0 - queueListItem.width : queueListItem.width
1187+ duration: queueListView.transitionDuration
1188+ }
1189+ onRunningChanged: {
1190+ if (running === false)
1191+ {
1192+ // Remove the item
1193+ if (index === player.currentIndex)
1194+ {
1195+ if (queueListView.count > 1)
1196+ {
1197+ // Next song and only play if currently playing
1198+ player.nextSong(player.isPlaying);
1199+ }
1200+ else
1201+ {
1202+ player.stop();
1203+ }
1204+ }
1205+
1206+ if (index < player.currentIndex) {
1207+ player.currentIndex -= 1;
1208+ }
1209+
1210+ // Remove item from queue and clear caches
1211+ trackQueue.model.remove(index);
1212+ }
1213+ }
1214+ }
1215+ }
1216+
1217+ Rectangle {
1218+ anchors {
1219+ left: parent.left
1220+ top: parent.top
1221+ }
1222+ height: parent.height
1223+ color: styleMusic.common.white
1224+ opacity: index === player.currentIndex ? 1 : 0
1225+ width: units.gu(1)
1226+ visible: opacity !== 0
1227+
1228+ Behavior on opacity {
1229+ UbuntuNumberAnimation {
1230+ duration: UbuntuAnimation.SlowDuration
1231+ }
1232+ }
1233+ }
1234+
1235+ MusicRow {
1236+ id: musicRow
1237+ anchors.leftMargin: units.gu(2)
1238+ covers: [{author: model.author, album: model.album}]
1239+ column: Column {
1240+ spacing: units.gu(1)
1241+ Label {
1242+ id: nowPlayingArtist
1243+ color: styleMusic.nowPlaying.labelSecondaryColor
1244+ fontSize: 'small'
1245+ objectName: "nowplayingartist"
1246+ text: model.author
1247+ }
1248+
1249+ Label {
1250+ id: nowPlayingTitle
1251+ color: styleMusic.common.white
1252+ fontSize: 'medium'
1253+ objectName: "nowplayingtitle"
1254+ text: model.title
1255+ }
1256+
1257+ Label {
1258+ id: nowPlayingAlbum
1259+ color: styleMusic.nowPlaying.labelSecondaryColor
1260+ fontSize: 'x-small'
1261+ objectName: "nowplayingalbum"
1262+ text: model.album
1263+ }
1264+ }
1265+
1266+ NumberAnimation {
1267+ id: trackContainerReorderAnimation
1268+ target: musicRow
1269+ property: "anchors.leftMargin"
1270+ duration: queueListView.transitionDuration
1271+ to: units.gu(4)
1272+ }
1273+
1274+ NumberAnimation {
1275+ id: trackContainerResetAnimation
1276+ target: musicRow
1277+ property: "anchors.leftMargin"
1278+ duration: queueListView.transitionDuration
1279+ to: units.gu(0.5)
1280+ }
1281+ }
1282+
1283+ Expander {
1284+ id: expandable
1285+ anchors {
1286+ fill: parent
1287+ }
1288+ buttonEnabled: !swipeBackground.primed
1289+ listItem: queueListItem
1290+ model: trackQueue.model.get(index)
1291+ row: Row {
1292+ AddToPlaylist {
1293+ }
1294+ }
1295+ Behavior on actualListItemHeight {
1296+ NumberAnimation {
1297+ target: expandable;
1298+ property: "actualListItemHeight";
1299+ duration: queueListView.transitionDuration;
1300+ }
1301+ }
1302+ }
1303+ }
1304+}
1305
1306=== modified file 'music-app.qml'
1307--- music-app.qml 2014-07-13 21:11:11 +0000
1308+++ music-app.qml 2014-07-14 17:57:04 +0000
1309@@ -466,6 +466,8 @@
1310
1311 // Show the Now Playing page and make sure the track is visible
1312 tabs.pushNowPlaying();
1313+
1314+ nowPlaying.ensureVisibleIndex = -1;
1315 nowPlaying.ensureVisibleIndex = index;
1316
1317 musicToolbar.showToolbar();
1318@@ -478,18 +480,19 @@
1319 }
1320
1321 function trackQueueClick(index) {
1322- if (player.currentIndex === index) {
1323- player.toggle();
1324- }
1325- else {
1326- player.playSong(trackQueue.model.get(index).filename, index);
1327- }
1328-
1329 // Show the Now Playing page and make sure the track is visible
1330 tabs.pushNowPlaying();
1331- nowPlaying.ensureVisibleIndex = index;
1332+
1333+ nowPlaying.queueSelected = false; // swtich to now playing
1334
1335 musicToolbar.showToolbar();
1336+
1337+ if (player.currentIndex !== index) {
1338+ player.playSong(trackQueue.model.get(index).filename, index);
1339+ }
1340+ else {
1341+ player.toggle();
1342+ }
1343 }
1344
1345 function playRandomSong(shuffle)

Subscribers

People subscribed via source and target branches

to status/vote changes: