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

Proposed by Andrew Hayzen
Status: Rejected
Rejected by: Andrew Hayzen
Proposed branch: lp:~ahayzen/music-app/now-playing-queue-split
Merge into: lp:music-app/trusty
Diff against target: 1411 lines (+670/-643)
5 files modified
MusicNowPlaying.qml (+75/-630)
MusicQueue.qml (+60/-0)
MusicToolbar.qml (+10/-7)
common/Queue.qml (+510/-0)
music-app.qml (+15/-6)
To merge this branch: bzr merge lp:~ahayzen/music-app/now-playing-queue-split
Reviewer Review Type Date Requested Status
Ubuntu Phone Apps Jenkins Bot continuous-integration Needs Fixing
Victor Thompson Needs Fixing
Review via email: mp+225417@code.launchpad.net

Commit message

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

Description of the change

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

To post a comment you must log in.
Revision history for this message
Victor Thompson (vthompson) wrote :

I think we need to think about this from a design perspective a bit more. IMO if we split the Queue and the Now Playing pages They should still be in one page, but the displayed layout should be selectable similar to how the Contacts app has "All" and "Favourites" (sic :P). In addition to that, I think we need to discuss if we even want to make this change. It simplifies things A LOT from an interaction point of view, but it also removes the dynamic feel of the queue that is sort of iconic about the app currently.

One bug I've noticed is that reorder in the queue does not cause the white playing indicator on the left to follow the currently playing item.

review: Needs Fixing
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
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Nicholas Skaggs (nskaggs) wrote :

merge conflict again

1 conflicts encountered.
bzr: ERROR: Conflicts from merge

Revision history for this message
Andrew Hayzen (ahayzen) wrote :

This mp [0] is likely to be preferred, marking this as WIP for now.

0 - https://code.launchpad.net/~andrew-hayzen/music-app/now-playing-split-take-2/+merge/226720

Unmerged revisions

497. By Andrew Hayzen

* Merge of trunk

496. By Andrew Hayzen

* Merge of trunk

495. By Andrew Hayzen

* Add margin top to now playing

494. By Andrew Hayzen

* Only jump to current when toolbar is made visible, hide toolbar on scroll
* Tweaks to mouse controls on now playing page

493. By Andrew Hayzen

* Tweaks of now playing for mobile

492. By Andrew Hayzen

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

Subscribers

People subscribed via source and target branches

to status/vote changes: