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