Merge lp:~nik90/podbird/devel-branch-sync-2 into lp:podbird
- devel-branch-sync-2
- Merge into trunk
Proposed by
Nekhelesh Ramananthan
Status: | Merged | ||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Approved by: | Michael Sheldon | ||||||||||||||||
Approved revision: | 192 | ||||||||||||||||
Merged at revision: | 152 | ||||||||||||||||
Proposed branch: | lp:~nik90/podbird/devel-branch-sync-2 | ||||||||||||||||
Merge into: | lp:podbird | ||||||||||||||||
Prerequisite: | lp:~nik90/podbird/devel-branch-sync-1 | ||||||||||||||||
Diff against target: |
2860 lines (+1178/-1166) 15 files modified
app/components/Card.qml (+62/-117) app/components/CardView.qml (+15/-20) app/components/ColumnFlow.qml (+0/-494) app/components/CustomSectionHeader.qml (+47/-0) app/components/TabsList.qml (+0/-52) app/podbird.qml (+148/-77) app/podcasts.js (+102/-2) app/settings/About.qml (+0/-1) app/ui/EpisodesPage.qml (+21/-19) app/ui/EpisodesTab.qml (+275/-100) app/ui/NowPlayingPage.qml (+329/-224) app/ui/PlayerControls.qml (+6/-6) app/ui/PodcastsTab.qml (+4/-3) app/ui/Queue.qml (+88/-0) po/com.mikeasoft.podbird.pot (+81/-51) |
||||||||||||||||
To merge this branch: | bzr merge lp:~nik90/podbird/devel-branch-sync-2 | ||||||||||||||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Michael Sheldon | Approve | ||
Review via email: mp+289564@code.launchpad.net |
Commit message
Description of the change
This is a pretty heavy MP. It adds the following,
- Queue Support
- Migration to DownloadManager
- New GridView
- Multi-select view for the EpisodesTab alone (others will follow in the next MP)
To post a comment you must log in.
- 192. By Nekhelesh Ramananthan
-
merged prerequisite
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'app/components/Card.qml' |
2 | --- app/components/Card.qml 2016-02-25 11:09:44 +0000 |
3 | +++ app/components/Card.qml 2016-03-28 22:39:33 +0000 |
4 | @@ -1,12 +1,13 @@ |
5 | /* |
6 | - * Copyright (C) 2014-2016 |
7 | - * Andrew Hayzen <ahayzen@gmail.com> |
8 | - * |
9 | - * This program is free software; you can redistribute it and/or modify |
10 | + * Copyright 2016 Podbird Team |
11 | + * |
12 | + * This file is part of Podbird. |
13 | + * |
14 | + * Podbird is free software; you can redistribute it and/or modify |
15 | * it under the terms of the GNU General Public License as published by |
16 | * the Free Software Foundation; version 3. |
17 | * |
18 | - * This program is distributed in the hope that it will be useful, |
19 | + * Podbird is distributed in the hope that it will be useful, |
20 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
21 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
22 | * GNU General Public License for more details. |
23 | @@ -18,122 +19,66 @@ |
24 | import QtQuick 2.4 |
25 | import Ubuntu.Components 1.3 |
26 | |
27 | -Item { |
28 | +AbstractButton { |
29 | id: card |
30 | |
31 | - /* Required by ColumnFlow */ |
32 | - property int index |
33 | - property var model |
34 | + height: parent.parent.cellHeight |
35 | + width: parent.parent.cellWidth |
36 | |
37 | property alias coverArt: imgFrame.source |
38 | property alias primaryText: primaryLabel.text |
39 | - property alias secondaryText: secondaryLabel.text |
40 | - property alias secondaryTextVisible: secondaryLabel.visible |
41 | - |
42 | - signal clicked(var mouse) |
43 | - signal pressAndHold(var mouse) |
44 | - |
45 | - height: cardColumn.childrenRect.height + 2 * bg.anchors.margins |
46 | - |
47 | - /* Background for card */ |
48 | - Rectangle { |
49 | - id: bg |
50 | - anchors.fill: parent |
51 | - anchors.margins: units.gu(1) |
52 | - color: podbird.appTheme.hightlightListView |
53 | - } |
54 | - |
55 | - /* Column containing image and labels */ |
56 | - Column { |
57 | - id: cardColumn |
58 | - |
59 | - anchors.fill: bg |
60 | - spacing: units.gu(0.5) |
61 | - |
62 | - Image { |
63 | - id: imgFrame |
64 | - width: parent.width |
65 | - height: width |
66 | - sourceSize.height: width |
67 | - sourceSize.width: width |
68 | - } |
69 | - |
70 | - Item { |
71 | - height: units.gu(1) |
72 | - width: units.gu(1) |
73 | - } |
74 | - |
75 | - Label { |
76 | - id: primaryLabel |
77 | - anchors { |
78 | - left: parent.left |
79 | - right: parent.right |
80 | - margins: units.gu(1) |
81 | - } |
82 | - color: podbird.appTheme.baseText |
83 | - elide: Text.ElideRight |
84 | - textSize: Label.Small |
85 | - opacity: 1.0 |
86 | - wrapMode: Text.WordWrap |
87 | - horizontalAlignment: Text.AlignHCenter |
88 | - } |
89 | - |
90 | - Label { |
91 | - id: secondaryLabel |
92 | - anchors { |
93 | - left: parent.left |
94 | - leftMargin: units.gu(1) |
95 | - right: parent.right |
96 | - rightMargin: units.gu(1) |
97 | - } |
98 | - color: podbird.appTheme.baseSubText |
99 | - elide: Text.ElideRight |
100 | - textSize: Label.Small |
101 | - opacity: 1.0 |
102 | - wrapMode: Text.WordWrap |
103 | - horizontalAlignment: Text.AlignHCenter |
104 | - } |
105 | - |
106 | - Item { |
107 | - height: units.gu(1.5) |
108 | - width: units.gu(1) |
109 | - } |
110 | - } |
111 | - |
112 | - /* Overlay for when card is pressed */ |
113 | - Rectangle { |
114 | - id: overlay |
115 | - anchors.fill: bg |
116 | - color: "#000" |
117 | - opacity: 0 |
118 | - |
119 | - Behavior on opacity { |
120 | - UbuntuNumberAnimation {} |
121 | - } |
122 | - } |
123 | - |
124 | - /* Capture mouse events */ |
125 | - MouseArea { |
126 | - anchors.fill: parent |
127 | - onClicked: card.clicked(mouse) |
128 | - onPressAndHold: card.pressAndHold(mouse) |
129 | - onPressedChanged: overlay.opacity = pressed ? 0.3 : 0 |
130 | - } |
131 | - |
132 | - /* Animations */ |
133 | - Behavior on height { |
134 | - UbuntuNumberAnimation {} |
135 | - } |
136 | - |
137 | - Behavior on width { |
138 | - UbuntuNumberAnimation {} |
139 | - } |
140 | - |
141 | - Behavior on x { |
142 | - UbuntuNumberAnimation {} |
143 | - } |
144 | - |
145 | - Behavior on y { |
146 | - UbuntuNumberAnimation {} |
147 | + property string secondaryText: "" |
148 | + |
149 | + Image { |
150 | + id: imgFrame |
151 | + width: parent.width/1.2 |
152 | + height: width |
153 | + anchors.top: parent.top |
154 | + anchors.horizontalCenter: parent.horizontalCenter |
155 | + sourceSize.height: width |
156 | + sourceSize.width: width |
157 | + |
158 | + Loader { |
159 | + id: hintLoader |
160 | + anchors.verticalCenter: parent.top |
161 | + anchors.right: parent.right |
162 | + anchors.rightMargin: units.gu(-0.5) |
163 | + sourceComponent: secondaryText !== "" ? hintComponent : undefined |
164 | + } |
165 | + |
166 | + Component { |
167 | + id: hintComponent |
168 | + Rectangle { |
169 | + color: podbird.appTheme.focusText |
170 | + width: secondaryLabel.implicitWidth + units.gu(1) |
171 | + height: secondaryLabel.implicitHeight + units.gu(1) |
172 | + radius: units.gu(0.5) |
173 | + visible: secondaryLabel.text !== "" |
174 | + Label { |
175 | + id: secondaryLabel |
176 | + anchors.centerIn: parent |
177 | + text: secondaryText |
178 | + visible: text !== "" |
179 | + textSize: Label.Small |
180 | + color: "White" |
181 | + } |
182 | + } |
183 | + } |
184 | + } |
185 | + |
186 | + Label { |
187 | + id: primaryLabel |
188 | + anchors { |
189 | + top: imgFrame.bottom |
190 | + left: imgFrame.left |
191 | + right: imgFrame.right |
192 | + margins: units.gu(1) |
193 | + } |
194 | + color: podbird.appTheme.baseText |
195 | + elide: Text.ElideRight |
196 | + textSize: Label.Small |
197 | + wrapMode: Text.WordWrap |
198 | + maximumLineCount: 2 |
199 | + horizontalAlignment: Text.AlignHCenter |
200 | } |
201 | } |
202 | |
203 | === modified file 'app/components/CardView.qml' |
204 | --- app/components/CardView.qml 2016-02-25 11:09:44 +0000 |
205 | +++ app/components/CardView.qml 2016-03-28 22:39:33 +0000 |
206 | @@ -18,33 +18,28 @@ |
207 | import QtQuick 2.4 |
208 | import Ubuntu.Components 1.3 |
209 | |
210 | -Flickable { |
211 | - id: cardViewFlickable |
212 | +GridView { |
213 | + id: gridView |
214 | + |
215 | anchors { |
216 | fill: parent |
217 | margins: units.gu(1) |
218 | } |
219 | |
220 | - // dont use flow.contentHeight as it is inaccurate due to height of labels |
221 | - // changing as they load |
222 | - contentHeight: flow.contentHeight + flow.anchors.margins * 2 + units.gu(8) |
223 | - contentWidth: width |
224 | - |
225 | - property alias count: flow.count |
226 | - property alias delegate: flow.delegate |
227 | - property var getter |
228 | - property alias model: flow.model |
229 | - property real itemWidth: units.gu(15) |
230 | - |
231 | - onGetterChanged: flow.getter = getter // cannot use alias to set a function (must be var) |
232 | - |
233 | - ColumnFlow { |
234 | - id: flow |
235 | - anchors.fill: parent |
236 | - columns: parseInt(cardViewFlickable.width / itemWidth) || 1 // never drop to 0 |
237 | - flickable: cardViewFlickable |
238 | + cellHeight: cellSize + heightOffset |
239 | + cellWidth: cellSize + widthOffset |
240 | + |
241 | + header: Item { |
242 | + width: parent.width |
243 | + height: units.gu(2) |
244 | } |
245 | |
246 | + readonly property int columns: parseInt(width / itemWidth) || 1 // never drop to 0 |
247 | + readonly property int cellSize: width / columns |
248 | + property int itemWidth: units.gu(15) |
249 | + property int heightOffset: 0 |
250 | + property int widthOffset: 0 |
251 | + |
252 | Component.onCompleted: { |
253 | // FIXME: workaround for qtubuntu not returning values depending on the grid unit definition |
254 | // for Flickable.maximumFlickVelocity and Flickable.flickDeceleration |
255 | |
256 | === removed file 'app/components/ColumnFlow.qml' |
257 | --- app/components/ColumnFlow.qml 2016-02-25 11:09:44 +0000 |
258 | +++ app/components/ColumnFlow.qml 1970-01-01 00:00:00 +0000 |
259 | @@ -1,494 +0,0 @@ |
260 | -/* |
261 | - * Copyright (C) 2014-2016 |
262 | - * Andrew Hayzen <ahayzen@gmail.com> |
263 | - * |
264 | - * This program is free software; you can redistribute it and/or modify |
265 | - * it under the terms of the GNU General Public License as published by |
266 | - * the Free Software Foundation; version 3. |
267 | - * |
268 | - * This program is distributed in the hope that it will be useful, |
269 | - * but WITHOUT ANY WARRANTY; without even the implied warranty of |
270 | - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
271 | - * GNU General Public License for more details. |
272 | - * |
273 | - * You should have received a copy of the GNU General Public License |
274 | - * along with this program. If not, see <http://www.gnu.org/licenses/>. |
275 | - */ |
276 | - |
277 | -import QtQuick 2.4 |
278 | - |
279 | -Item { |
280 | - id: columnFlow |
281 | - property int columns: 1 |
282 | - property Flickable flickable |
283 | - property var model |
284 | - property Component delegate |
285 | - |
286 | - property var getter: function (i) { return model.get(i); } // optional getter override (useful for music-app ms2 models) |
287 | - |
288 | - property int buffer: units.gu(20) |
289 | - property var columnHeights: [] |
290 | - property var columnHeightsMax: [] |
291 | - property int columnWidth: parent.width / columns |
292 | - property int contentHeight: 0 |
293 | - property int count: model === undefined ? 0 : model.count |
294 | - property int delayRebuildIndex: -1 |
295 | - property var incubating: ({}) // incubating objects |
296 | - property var items: ({}) |
297 | - property var itemToColumn: ({}) // cache of the columns of indexes |
298 | - property int lastIndex: 0 // the furtherest index loaded |
299 | - property bool removing: false |
300 | - property bool restoring: false // is the view restoring? |
301 | - property var restoreItems: ({}) // when rebuilding items are stored here temporarily |
302 | - |
303 | - onColumnWidthChanged: { |
304 | - if (restoring) { |
305 | - return; |
306 | - } else if (columns != columnHeights.length && visible) { |
307 | - // number of columns has changed so rebuild the columns |
308 | - rebuildColumns() |
309 | - } else { // column width has changed update visible items properties linked to columnWidth |
310 | - for (var column=0; column < columnHeights.length; column++) { |
311 | - for (var i in columnHeights[column]) { |
312 | - if (columnHeights[column].hasOwnProperty(i) && items.hasOwnProperty(i)) { |
313 | - items[i].width = columnWidth; |
314 | - items[i].x = column * columnWidth; |
315 | - } |
316 | - } |
317 | - } |
318 | - |
319 | - ensureItemsVisible() |
320 | - } |
321 | - } |
322 | - |
323 | - onVisibleChanged: { |
324 | - if (visible && delayRebuildIndex !== -1) { // restore from count change |
325 | - if (delayRebuildIndex === 0) { |
326 | - reset() |
327 | - } else { |
328 | - removeIndex(delayRebuildIndex) |
329 | - } |
330 | - |
331 | - delayRebuildIndex = -1 |
332 | - append(true) |
333 | - } |
334 | - |
335 | - // number of columns has changed while invisible so reset if not already restoring |
336 | - if (visible && !restoring && columns != columnHeights.length) { |
337 | - rebuildColumns() |
338 | - } |
339 | - } |
340 | - |
341 | - ListModel { // fakemodel for connections to link to when there is no model |
342 | - id: fakeModel |
343 | - } |
344 | - |
345 | - Connections { |
346 | - target: model === undefined ? fakeModel : model |
347 | - onModelReset: { |
348 | - if (!visible && lastIndex > 0) { |
349 | - delayRebuildIndex = 0 |
350 | - } else { |
351 | - reset() |
352 | - append() |
353 | - } |
354 | - } |
355 | - onRowsInserted: { |
356 | - if (!visible && lastIndex > 0) { |
357 | - setDelayRebuildIndex(first) |
358 | - } else { |
359 | - if (first <= lastIndex) { |
360 | - if (first === 0) { |
361 | - reset() |
362 | - } else { |
363 | - removeIndex(first) // remove earliest index and all items after |
364 | - } |
365 | - } |
366 | - |
367 | - // Supply last index if larger as count is not updated until after insertion |
368 | - append(true, last > count ? last : count) |
369 | - } |
370 | - } |
371 | - onRowsRemoved: { |
372 | - if (!visible) { |
373 | - setDelayRebuildIndex(first) |
374 | - } else { |
375 | - if (first <= lastIndex) { |
376 | - if (first === 0) { |
377 | - reset() |
378 | - } else { |
379 | - removeIndex(first) // remove earliest index and all items after |
380 | - } |
381 | - |
382 | - // count is not updated until after removal, so send insertMax |
383 | - // insertMax is count - removal region inclusive - 1 (lastIndex is 1 infront) |
384 | - |
385 | - append(true, count - (1 + last - first) - 1) // rebuild any items on screen or before |
386 | - } |
387 | - } |
388 | - } |
389 | - } |
390 | - |
391 | - |
392 | - Connections { |
393 | - target: flickable |
394 | - onContentYChanged: { |
395 | - append() // Append any new items (scrolling down) |
396 | - |
397 | - ensureItemsVisible() |
398 | - } |
399 | - } |
400 | - |
401 | - // Append a new row of items if possible |
402 | - function append(loadBefore, insertMax) |
403 | - { |
404 | - // Do not allow append to run if incubating |
405 | - if (isIncubating() || restoring || removing) { |
406 | - return; |
407 | - } |
408 | - |
409 | - // get the columns in order |
410 | - var columnsByHeight = getColumnsByHeight(); |
411 | - var workDone = false; |
412 | - |
413 | - // check if a new item in each column is possible |
414 | - for (var i=0; i < columnsByHeight.length; i++) { |
415 | - var y = columnHeightsMax[columnsByHeight[i]]; |
416 | - |
417 | - // build new object in column if possible |
418 | - // if insertMax is undefined then allow if there is work todo (from the count in the model) |
419 | - // otherwise use the insertMax as the count to compare with the lastIndex added to the columnFlow |
420 | - // and |
421 | - // allow if the y position is within the viewport |
422 | - // or if loadBefore is true then allow if the y position is before the viewport |
423 | - if (((count > 0 && lastIndex < count && insertMax === undefined) || (insertMax !== undefined && lastIndex <= insertMax)) && (inViewport(y, 0) || (loadBefore === true && beforeViewport(y)))) { |
424 | - incubateObject(lastIndex++, columnsByHeight[i], getMaxInColumn(columnsByHeight[i]), append); |
425 | - workDone = true |
426 | - } else { |
427 | - break; |
428 | - } |
429 | - } |
430 | - |
431 | - if (!workDone) { // last iteration over append so visible ensure items are correct |
432 | - ensureItemsVisible(); |
433 | - } |
434 | - } |
435 | - |
436 | - // Detect if a loaded object is before the viewport with a buffer |
437 | - function beforeViewport(y) |
438 | - { |
439 | - return y <= flickable.contentY - buffer; |
440 | - } |
441 | - |
442 | - // Cache the size of the columns for use later |
443 | - function cacheColumnHeights() |
444 | - { |
445 | - columnHeightsMax = []; |
446 | - |
447 | - for (var i=0; i < columnHeights.length; i++) { |
448 | - var sum = 0; |
449 | - |
450 | - for (var j in columnHeights[i]) { |
451 | - sum += columnHeights[i][j]; |
452 | - } |
453 | - |
454 | - columnHeightsMax.push(sum); |
455 | - } |
456 | - |
457 | - if (!restoring) { // when not restoring otherwise user will be pushed to the top of the view |
458 | - // set the height of columnFlow to max column (for flickable contentHeight) |
459 | - contentHeight = Math.max.apply(null, columnHeightsMax); |
460 | - } |
461 | - } |
462 | - |
463 | - // Recache the visible items heights (due to a change in their height) |
464 | - function cacheVisibleItemsHeights() |
465 | - { |
466 | - for (var i in items) { |
467 | - if (items.hasOwnProperty(i)) { |
468 | - columnHeights[itemToColumn[i]][i] = items[i].height; |
469 | - } |
470 | - } |
471 | - |
472 | - cacheColumnHeights(); |
473 | - } |
474 | - |
475 | - // Ensures that the correct items are visible |
476 | - function ensureItemsVisible() |
477 | - { |
478 | - for (var i in items) { |
479 | - if (items.hasOwnProperty(i)) { |
480 | - items[i].visible = inViewport(items[i].y, items[i].height) |
481 | - } |
482 | - } |
483 | - } |
484 | - |
485 | - // Return if there are incubating objects |
486 | - function isIncubating() |
487 | - { |
488 | - for (var i in incubating) { |
489 | - if (incubating.hasOwnProperty(i)) { |
490 | - return true; |
491 | - } |
492 | - } |
493 | - |
494 | - return false; |
495 | - } |
496 | - |
497 | - // Run after incubation to store new column height and call any further append/restores |
498 | - function finishIncubation(index, callback) |
499 | - { |
500 | - var obj = incubating[index].object; |
501 | - delete incubating[index]; |
502 | - |
503 | - obj.heightChanged.connect(cacheVisibleItemsHeights) // if the height changes recache |
504 | - |
505 | - // Ensure properties linked to columnWidth are correct (as width may still be changing) |
506 | - obj.x = itemToColumn[index] * columnWidth; |
507 | - obj.width = columnWidth; |
508 | - |
509 | - items[index] = obj; |
510 | - |
511 | - columnHeights[itemToColumn[index]][index] = obj.height; // ensure height is the latest |
512 | - |
513 | - if (!isIncubating()) { |
514 | - cacheColumnHeights(); |
515 | - |
516 | - // Check if there is any more work to be done (append or restore) |
517 | - callback(); |
518 | - } |
519 | - } |
520 | - |
521 | - // Force any incubation to finish |
522 | - function forceIncubationCompletion() |
523 | - { |
524 | - for (var i in incubating) { |
525 | - if (incubating.hasOwnProperty(i)) { |
526 | - incubating[i].forceCompletion() |
527 | - } |
528 | - } |
529 | - } |
530 | - |
531 | - // Get the column index in order of height |
532 | - function getColumnsByHeight() |
533 | - { |
534 | - var columnsByHeight = []; |
535 | - |
536 | - for (var i=0; i < columnHeightsMax.length; i++) { |
537 | - var min = undefined; |
538 | - var index = -1; |
539 | - |
540 | - // Find the smallest column that has not been found yet |
541 | - for (var j=0; j < columnHeightsMax.length; j++) { |
542 | - if (columnsByHeight.indexOf(j) === -1 && (min === undefined || columnHeightsMax[j] < min)) { |
543 | - min = columnHeightsMax[j]; |
544 | - index = j; |
545 | - } |
546 | - } |
547 | - |
548 | - columnsByHeight.push(index); |
549 | - } |
550 | - |
551 | - return columnsByHeight; |
552 | - } |
553 | - |
554 | - // Get the highest index for a column |
555 | - function getMaxInColumn(column) |
556 | - { |
557 | - var max; |
558 | - |
559 | - for (var i in columnHeights[column]) { |
560 | - if (columnHeights[column].hasOwnProperty(i)) { |
561 | - i = parseInt(i); |
562 | - |
563 | - if (items.hasOwnProperty(i)) { |
564 | - if (i > max || max === undefined) { |
565 | - max = i; |
566 | - } |
567 | - } |
568 | - } |
569 | - } |
570 | - |
571 | - return max; |
572 | - } |
573 | - |
574 | - // Incubate an object for creation |
575 | - function incubateObject(index, column, anchorIndex, callback) |
576 | - { |
577 | - // Load parameters to send to the object on creation |
578 | - var params = { |
579 | - "anchors.top": anchorIndex === undefined ? parent.top : items[anchorIndex].bottom, |
580 | - index: index, |
581 | - model: getter(index), |
582 | - width: columnWidth, |
583 | - x: column * columnWidth |
584 | - }; |
585 | - |
586 | - // Start incubating and cache the column |
587 | - incubating[index] = delegate.incubateObject(parent, params); |
588 | - itemToColumn[index] = column; |
589 | - |
590 | - if (incubating[index].status != Component.Ready) { |
591 | - incubating[index].onStatusChanged = function(status) { |
592 | - if (status == Component.Ready) { |
593 | - finishIncubation(index, callback) |
594 | - } |
595 | - } |
596 | - } else { |
597 | - finishIncubation(index, callback) |
598 | - } |
599 | - } |
600 | - |
601 | - // Detect if a loaded object is in the viewport with a buffer |
602 | - function inViewport(y, height) |
603 | - { |
604 | - return flickable.contentY - buffer < y + height && y < flickable.contentY + flickable.height + buffer; |
605 | - } |
606 | - |
607 | - // Number of columns has changed rebuild with live items |
608 | - function rebuildColumns() |
609 | - { |
610 | - restoring = true; |
611 | - var i; |
612 | - |
613 | - forceIncubationCompletion() |
614 | - |
615 | - columnHeights = [] |
616 | - columnHeightsMax = [] |
617 | - |
618 | - for (i=0; i < columns; i++) { |
619 | - columnHeights.push({}); |
620 | - columnHeightsMax.push(0); |
621 | - } |
622 | - |
623 | - lastIndex = 0; |
624 | - |
625 | - restoreItems = items; |
626 | - items = {}; |
627 | - |
628 | - restoreExisting() |
629 | - |
630 | - restoring = false; |
631 | - |
632 | - cacheColumnHeights(); // rebuilds contentHeight |
633 | - |
634 | - // If the columns have changed while the view was locked rerun |
635 | - if (columns != columnHeights.length && visible) { |
636 | - rebuildColumns() |
637 | - } else { |
638 | - append() // check if any new items can be added |
639 | - } |
640 | - } |
641 | - |
642 | - // Remove an index from the model (invalidating anything after) |
643 | - function removeIndex(index) |
644 | - { |
645 | - removing = true |
646 | - |
647 | - forceIncubationCompletion() |
648 | - |
649 | - for (var i in items) { |
650 | - if (i >= index && items.hasOwnProperty(i)) { |
651 | - delete columnHeights[itemToColumn[i]][i] |
652 | - delete itemToColumn[i] |
653 | - |
654 | - items[i].destroy() |
655 | - delete items[i] |
656 | - } |
657 | - } |
658 | - |
659 | - lastIndex = index |
660 | - removing = false |
661 | - |
662 | - cacheColumnHeights() |
663 | - } |
664 | - |
665 | - // Restores existing items into potentially new positions |
666 | - function restoreExisting() |
667 | - { |
668 | - var i; |
669 | - |
670 | - // get the columns in order |
671 | - var columnsByHeight = getColumnsByHeight(); |
672 | - var workDone = false; |
673 | - |
674 | - // check if a new item in each column is possible |
675 | - for (i=0; i < columnsByHeight.length; i++) { |
676 | - var column = columnsByHeight[i]; |
677 | - |
678 | - // build new object in column if possible |
679 | - if (count > 0 && lastIndex < count) { |
680 | - if (restoreItems.hasOwnProperty(lastIndex)) { |
681 | - var item = restoreItems[lastIndex]; |
682 | - var maxInColumn = getMaxInColumn(column); // get lowest item in column |
683 | - |
684 | - itemToColumn[lastIndex] = column; |
685 | - columnHeights[column][lastIndex] = item.height; // ensure height is the latest |
686 | - |
687 | - // Rebuild item properties |
688 | - item.anchors.bottom = undefined |
689 | - item.anchors.top = maxInColumn === undefined ? parent.top : items[maxInColumn].bottom; |
690 | - item.x = column * columnWidth; |
691 | - item.visible = inViewport(item.y, item.height); |
692 | - |
693 | - // Migrate item from restoreItems to items |
694 | - items[lastIndex] = item; |
695 | - delete restoreItems[lastIndex]; |
696 | - |
697 | - // set after restore as height will likely change causing cacheVisibleItemsHeights to be run |
698 | - item.width = columnWidth; |
699 | - |
700 | - cacheColumnHeights(); // ensure column heights are up to date |
701 | - |
702 | - lastIndex++; |
703 | - workDone = true; |
704 | - } |
705 | - } else { |
706 | - break; |
707 | - } |
708 | - } |
709 | - |
710 | - if (workDone) { |
711 | - restoreExisting() // if work done then check if any more is needed |
712 | - } else { |
713 | - restoreItems = {}; // ensure restoreItems is empty |
714 | - } |
715 | - } |
716 | - |
717 | - // Reset the column flow |
718 | - function reset() |
719 | - { |
720 | - forceIncubationCompletion() |
721 | - |
722 | - // Destroy any old items |
723 | - for (var j in items) { |
724 | - if (items.hasOwnProperty(j)) { |
725 | - items[j].destroy() |
726 | - } |
727 | - } |
728 | - |
729 | - // Reset and rebuild the variables |
730 | - items = ({}) |
731 | - itemToColumn = ({}) |
732 | - lastIndex = 0 |
733 | - |
734 | - columnHeights = [] |
735 | - |
736 | - for (var k=0; k < columns; k++) { |
737 | - columnHeights.push({}) |
738 | - } |
739 | - |
740 | - cacheColumnHeights() |
741 | - |
742 | - contentHeight = 0 |
743 | - } |
744 | - |
745 | - function setDelayRebuildIndex(index) |
746 | - { |
747 | - if (delayRebuildIndex === -1 || index < lastIndex) { |
748 | - delayRebuildIndex = index |
749 | - } |
750 | - } |
751 | - |
752 | - Component.onCompleted: append(true) |
753 | -} |
754 | |
755 | === added file 'app/components/CustomSectionHeader.qml' |
756 | --- app/components/CustomSectionHeader.qml 1970-01-01 00:00:00 +0000 |
757 | +++ app/components/CustomSectionHeader.qml 2016-03-28 22:39:33 +0000 |
758 | @@ -0,0 +1,47 @@ |
759 | +/* |
760 | + * Copyright 2016 Podbird Team |
761 | + * |
762 | + * This file is part of Podbird. |
763 | + * |
764 | + * Podbird is free software; you can redistribute it and/or modify |
765 | + * it under the terms of the GNU General Public License as published by |
766 | + * the Free Software Foundation; version 3. |
767 | + * |
768 | + * Podbird is distributed in the hope that it will be useful, |
769 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
770 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
771 | + * GNU General Public License for more details. |
772 | + * |
773 | + * You should have received a copy of the GNU General Public License |
774 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
775 | + */ |
776 | + |
777 | +import QtQuick 2.4 |
778 | +import Ubuntu.Components 1.3 |
779 | + |
780 | +Item { |
781 | + id: customSectionHeader |
782 | + |
783 | + property alias title: headerText.text |
784 | + |
785 | + height: headerText.text !== "" ? headerText.implicitHeight + divider.height + headerText.anchors.topMargin + headerText.anchors.bottomMargin |
786 | + : units.gu(0) |
787 | + |
788 | + anchors { left: parent.left; right: parent.right; margins: units.gu(2) } |
789 | + |
790 | + Label { |
791 | + id: headerText |
792 | + color: podbird.appTheme.baseText |
793 | + font.weight: Font.DemiBold |
794 | + anchors { top: parent.top; topMargin: units.gu(2); bottom: parent.bottom; bottomMargin: units.gu(2) } |
795 | + width: parent.width |
796 | + } |
797 | + |
798 | + Rectangle { |
799 | + id: divider |
800 | + color: settings.themeName === "Dark.qml" ? "#888888" : "#cdcdcd" |
801 | + width: parent.width |
802 | + height: units.dp(1) |
803 | + anchors.bottom: parent.bottom |
804 | + } |
805 | +} |
806 | |
807 | === removed file 'app/components/TabsList.qml' |
808 | --- app/components/TabsList.qml 2016-03-28 22:39:32 +0000 |
809 | +++ app/components/TabsList.qml 1970-01-01 00:00:00 +0000 |
810 | @@ -1,52 +0,0 @@ |
811 | -/* |
812 | - * Copyright 2015-2016 Podbird Team |
813 | - * |
814 | - * This file is part of Podbird. |
815 | - * |
816 | - * Podbird is free software; you can redistribute it and/or modify |
817 | - * it under the terms of the GNU General Public License as published by |
818 | - * the Free Software Foundation; version 3. |
819 | - * |
820 | - * Podbird is distributed in the hope that it will be useful, |
821 | - * but WITHOUT ANY WARRANTY; without even the implied warranty of |
822 | - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
823 | - * GNU General Public License for more details. |
824 | - * |
825 | - * You should have received a copy of the GNU General Public License |
826 | - * along with this program. If not, see <http://www.gnu.org/licenses/>. |
827 | - */ |
828 | - |
829 | -import QtQuick 2.4 |
830 | -import Ubuntu.Components 1.3 |
831 | - |
832 | -ActionList { |
833 | - id: tabsList |
834 | - |
835 | - property int currentTab: tabs.selectedTabIndex |
836 | - |
837 | - children: [ |
838 | - Action { |
839 | - text: i18n.tr("Episodes") |
840 | - visible: currentTab !== 0 |
841 | - onTriggered: { |
842 | - tabs.selectedTabIndex = 0 |
843 | - } |
844 | - }, |
845 | - |
846 | - Action { |
847 | - text: i18n.tr("Podcasts") |
848 | - visible: currentTab !== 1 |
849 | - onTriggered: { |
850 | - tabs.selectedTabIndex = 1 |
851 | - } |
852 | - }, |
853 | - |
854 | - Action { |
855 | - text: i18n.tr("Settings") |
856 | - visible: currentTab !== 2 |
857 | - onTriggered: { |
858 | - tabs.selectedTabIndex = 2 |
859 | - } |
860 | - } |
861 | - ] |
862 | -} |
863 | |
864 | === modified file 'app/podbird.qml' |
865 | --- app/podbird.qml 2016-03-28 22:39:32 +0000 |
866 | +++ app/podbird.qml 2016-03-28 22:39:33 +0000 |
867 | @@ -18,13 +18,12 @@ |
868 | |
869 | import QtQuick 2.4 |
870 | import Podbird 1.0 |
871 | -import UserMetrics 0.1 |
872 | -import QtMultimedia 5.4 |
873 | +import QtMultimedia 5.6 |
874 | import Ubuntu.Connectivity 1.0 |
875 | import Qt.labs.settings 1.0 |
876 | import Ubuntu.Components 1.3 |
877 | import QtQuick.LocalStorage 2.0 |
878 | -import Ubuntu.DownloadManager 0.1 |
879 | +import Ubuntu.DownloadManager 1.2 |
880 | import "ui" |
881 | import "themes" as Themes |
882 | import "podcasts.js" as Podcasts |
883 | @@ -45,11 +44,11 @@ |
884 | |
885 | Component.onDestruction: { |
886 | console.log("[LOG]: Download cancelled"); |
887 | - downloader.cancel(); |
888 | var db = Podcasts.init() |
889 | db.transaction(function (tx) { |
890 | tx.executeSql('UPDATE Episode SET queued=0 WHERE queued=1'); |
891 | }) |
892 | + Podcasts.clearQueue() |
893 | } |
894 | |
895 | // RefreshModel function to call refreshModel() function of the tab currently |
896 | @@ -138,84 +137,156 @@ |
897 | } |
898 | } |
899 | |
900 | - SingleDownload { |
901 | + Component { |
902 | + id: singleDownloadComponent |
903 | + SingleDownload { |
904 | + id: singleDownloadObject |
905 | + property string image |
906 | + property string title |
907 | + property string guid |
908 | + metadata: Metadata { |
909 | + showInIndicator: true |
910 | + title: singleDownloadObject.title |
911 | + } |
912 | + } |
913 | + } |
914 | + |
915 | + function downloadEpisode(image, title, guid, url) { |
916 | + var singleDownload = singleDownloadComponent.createObject(podbird, {"image": image, "title": title, "guid": guid}) |
917 | + singleDownload.download(url) |
918 | + } |
919 | + |
920 | + DownloadManager { |
921 | id: downloader |
922 | - property var queue: [] |
923 | - property string downloadingGuid |
924 | - |
925 | - onFinished: { |
926 | + |
927 | + property string downloadingGuid: downloads.length > 0 ? downloads[0].guid : "NULL" |
928 | + property int progress: downloads.length > 0 ? downloads[0].progress : 0 |
929 | + |
930 | + cleanDownloads: true |
931 | + onDownloadFinished: { |
932 | var db = Podcasts.init(); |
933 | var finalLocation = fileManager.saveDownload(path); |
934 | db.transaction(function (tx) { |
935 | - tx.executeSql("UPDATE Episode SET downloadedfile=?, queued=0 WHERE guid=?", [finalLocation, downloadingGuid]); |
936 | - queue.shift(); |
937 | - if (queue.length > 0) { |
938 | - downloadingGuid = queue[0][0]; |
939 | - download(queue[0][1]); |
940 | - } else { |
941 | - downloadingGuid = ""; |
942 | - } |
943 | + tx.executeSql("UPDATE Episode SET downloadedfile=?, queued=0 WHERE guid=?", [finalLocation, download.guid]); |
944 | }); |
945 | } |
946 | |
947 | - function addDownload(guid, url) { |
948 | - queue.push([guid, url]); |
949 | - if (queue.length == 1) { |
950 | - downloadingGuid = guid; |
951 | - download(url); |
952 | - } |
953 | - } |
954 | - } |
955 | - |
956 | - // UserMetrics to show Podbird stats on welcome screen |
957 | - Metric { |
958 | - id: podcastsMetric |
959 | - name: "podcast-metrics" |
960 | - // TRANSLATORS: this refers to a number of songs greater than one. The actual number will be prepended to the string automatically (plural forms are not yet fully supported in usermetrics, the library that displays that string) |
961 | - format: i18n.tr("Podcasts listened to today: <b>%1</b>") |
962 | - emptyFormat: i18n.tr("No podcasts listened to today") |
963 | - domain: "com.mikeasoft.podbird" |
964 | - } |
965 | - |
966 | - // Load the media player only when the user starts to play some media. This |
967 | - // should improve app-startup slightly. |
968 | - Loader { |
969 | - id: playerLoader |
970 | - sourceComponent: currentUrl != "" ? playerComponent : undefined |
971 | - } |
972 | - |
973 | - Component { |
974 | - id: playerComponent |
975 | - MediaPlayer { |
976 | - id: player |
977 | - |
978 | - property bool podcastCounted: false |
979 | - |
980 | - source: currentUrl |
981 | - |
982 | - onSourceChanged: { |
983 | - podcastCounted = false |
984 | - } |
985 | - |
986 | - onPositionChanged: { |
987 | - if (currentGuid == "" || duration <= 0) { |
988 | - return; |
989 | - } |
990 | - |
991 | - if (position > 10000 && !podcastCounted) { |
992 | - podcastCounted = true |
993 | - podcastsMetric.increment() |
994 | - console.log("[LOG]: Podcast User metric incremented") |
995 | - } |
996 | - |
997 | - var db = Podcasts.init(); |
998 | - db.transaction(function (tx) { |
999 | - tx.executeSql("UPDATE Episode SET position=? WHERE guid=?", [position >= duration ? 120 : position, currentGuid]); |
1000 | - if (position >= duration - 120) { |
1001 | - tx.executeSql("UPDATE Episode SET listened = 1 WHERE guid=?", [currentGuid]); |
1002 | - } |
1003 | - }); |
1004 | - } |
1005 | + onErrorFound: { |
1006 | + console.log("[ERROR]: " + download.errorMessage) |
1007 | + } |
1008 | + } |
1009 | + |
1010 | + MediaPlayer { |
1011 | + id: player |
1012 | + |
1013 | + // Wrapper function around decodeURIComponent() to prevent exceptions |
1014 | + // from bubbling up to the app. |
1015 | + function decodeFileURI(filename) |
1016 | + { |
1017 | + var newFilename = ""; |
1018 | + try { |
1019 | + newFilename = decodeURIComponent(filename); |
1020 | + } catch (e) { |
1021 | + newFilename = filename; |
1022 | + console.log("Unicode decoding error:", filename, e.message) |
1023 | + } |
1024 | + |
1025 | + return newFilename; |
1026 | + } |
1027 | + |
1028 | + function metaForSource(source) { |
1029 | + var blankMeta = { |
1030 | + name: "", |
1031 | + artist: "", |
1032 | + image: "", |
1033 | + guid: "", |
1034 | + } |
1035 | + |
1036 | + source = source.toString() |
1037 | + |
1038 | + return Podcasts.lookup(decodeFileURI(source)) || blankMeta; |
1039 | + } |
1040 | + |
1041 | + function toggle() { |
1042 | + if (playbackState === MediaPlayer.PlayingState) { |
1043 | + pause() |
1044 | + } else { |
1045 | + play() |
1046 | + } |
1047 | + } |
1048 | + |
1049 | + function playEpisode(guid, image, name, artist, url) { |
1050 | + // Clear current queue |
1051 | + player.playlist.clear() |
1052 | + Podcasts.clearQueue() |
1053 | + |
1054 | + // Add episode to queue |
1055 | + Podcasts.addItemToQueue(guid, image, name, artist, url) |
1056 | + player.playlist.addItem(url) |
1057 | + |
1058 | + // Play episode |
1059 | + player.play() |
1060 | + } |
1061 | + |
1062 | + function addEpisodeToQueue(guid, image, name, artist, url) { |
1063 | + Podcasts.addItemToQueue(guid, image, name, artist, url) |
1064 | + player.playlist.addItem(url) |
1065 | + |
1066 | + // If added episode is the first one in the queue, then set the current metadata |
1067 | + // so that the bottom player controls will be shown, allowing the user to play |
1068 | + // the episode if he chooses to. |
1069 | + if (player.playlist.itemCount === 0) { |
1070 | + currentGuid = guid |
1071 | + currentName = name |
1072 | + currentArtist = artist |
1073 | + currentImage = image |
1074 | + currentUrl = url |
1075 | + } |
1076 | + } |
1077 | + |
1078 | + property bool endOfMedia: false |
1079 | + property double progress: 0 |
1080 | + |
1081 | + playlist: Playlist { |
1082 | + playbackMode: Playlist.Sequential |
1083 | + |
1084 | + readonly property bool canGoPrevious: currentIndex !== 0 |
1085 | + readonly property bool canGoNext: currentIndex !== itemCount - 1 |
1086 | + |
1087 | + onCurrentItemSourceChanged: { |
1088 | + var meta = player.metaForSource(currentItemSource) |
1089 | + currentGuid = ""; |
1090 | + currentName = meta.name |
1091 | + currentArtist = meta.artist |
1092 | + currentImage = meta.image |
1093 | + currentGuid = meta.guid |
1094 | + } |
1095 | + } |
1096 | + |
1097 | + onStatusChanged: { |
1098 | + if (status === MediaPlayer.EndOfMedia) { |
1099 | + console.log("[LOG]: End of Media. Stopping.") |
1100 | + endOfMedia = true |
1101 | + stop() |
1102 | + } |
1103 | + } |
1104 | + |
1105 | + onStopped: { |
1106 | + if (playlist.itemCount > 0) { |
1107 | + if (endOfMedia) { |
1108 | + // We just ended media, so jump to start of playlist |
1109 | + playlist.currentIndex = 0; |
1110 | + |
1111 | + // Play then pause otherwise when we come from EndOfMedia |
1112 | + // it calls next() until EndOfMedia again. |
1113 | + play() |
1114 | + } |
1115 | + |
1116 | + pause() |
1117 | + } |
1118 | + |
1119 | + // Always reset endOfMedia |
1120 | + endOfMedia = false |
1121 | } |
1122 | } |
1123 | |
1124 | @@ -290,13 +361,13 @@ |
1125 | states: [ |
1126 | State { |
1127 | name: "shown" |
1128 | - when: currentUrl != "" && !mainStack.currentPage.isNowPlayingPage |
1129 | + when: player.playlist.itemCount !== 0 && !mainStack.currentPage.isNowPlayingPage |
1130 | PropertyChanges { target: playerControlLoader; anchors.bottomMargin: 0 } |
1131 | }, |
1132 | |
1133 | State { |
1134 | name: "hidden" |
1135 | - when: currentUrl == "" || mainStack.currentPage.isNowPlayingPage || !playerControl.visible |
1136 | + when: player.playlist.itemCount === 0 || mainStack.currentPage.isNowPlayingPage || !playerControl.visible |
1137 | PropertyChanges { target: playerControlLoader; anchors.bottomMargin: -units.gu(7) } |
1138 | } |
1139 | ] |
1140 | |
1141 | === modified file 'app/podcasts.js' |
1142 | --- app/podcasts.js 2016-03-28 22:39:32 +0000 |
1143 | +++ app/podcasts.js 2016-03-28 22:39:33 +0000 |
1144 | @@ -22,6 +22,7 @@ |
1145 | db.transaction(function(tx) { |
1146 | tx.executeSql('CREATE TABLE IF NOT EXISTS Podcast(artist TEXT, name TEXT, description TEXT, feed TEXT, image TEXT, lastupdate TIMESTAMP)'); |
1147 | tx.executeSql('CREATE TABLE IF NOT EXISTS Episode(guid TEXT, podcast INTEGER, name TEXT, subtitle TEXT, description TEXT, duration INTEGER, audiourl TEXT, downloadedfile TEXT, published TIMESTAMP, queued BOOLEAN, listened BOOLEAN, favourited BOOLEAN, position INTEGER, FOREIGN KEY(podcast) REFERENCES Podcast(rowid))'); |
1148 | + tx.executeSql('CREATE TABLE IF NOT EXISTS Queue(ind INTEGER NOT NULL, guid TEXT, image TEXT, name TEXT, artist TEXT, url TEXT)'); |
1149 | }); |
1150 | |
1151 | try { |
1152 | @@ -55,6 +56,105 @@ |
1153 | return db; |
1154 | } |
1155 | |
1156 | +// Function to add item to queue |
1157 | +function addItemToQueue(guid, image, name, artist, url) { |
1158 | + var db = init() |
1159 | + |
1160 | + db.transaction(function(tx) { |
1161 | + var ind = getNextIndex(tx); |
1162 | + var rs = tx.executeSql("INSERT OR REPLACE INTO Queue (ind, guid, image, name, artist, url) VALUES (?, ?, ?, ?, ?, ?)", [ind, guid, image, name, artist, url]); |
1163 | + if (rs.rowsAffected > 0) { |
1164 | + console.log("[LOG]: QUEUE add OK") |
1165 | + console.log("[LOG]: URL Added to queue: " + url) |
1166 | + } else { |
1167 | + console.log("[LOG]: QUEUE add FAIL") |
1168 | + } |
1169 | + }); |
1170 | +} |
1171 | + |
1172 | +function removeItemFromQueue(source) { |
1173 | + var db = init() |
1174 | + |
1175 | + db.transaction(function(tx) { |
1176 | + // Remove selected source from the queue |
1177 | + tx.executeSql("DELETE FROM Queue WHERE url = ?", source) |
1178 | + |
1179 | + // Rebuild queue in order |
1180 | + var rs = tx.executeSql("SELECT ind FROM Queue ORDER BY ind ASC") |
1181 | + |
1182 | + for (var i=0; i<rs.rows.length; i++) { |
1183 | + tx.executeSql("UPDATE Queue SET ind = ? WHERE ind = ?", [i, rs.rows.item(i).ind]) |
1184 | + } |
1185 | + }) |
1186 | +} |
1187 | + |
1188 | +// Function to clear the queue |
1189 | +function clearQueue() { |
1190 | + var db = init(); |
1191 | + db.transaction(function(tx) { |
1192 | + tx.executeSql("DELETE FROM Queue"); |
1193 | + }); |
1194 | +} |
1195 | + |
1196 | +function lookup(source) { |
1197 | + var db = init(); |
1198 | + var meta = { |
1199 | + name: "", |
1200 | + artist: "", |
1201 | + image: "", |
1202 | + guid: "", |
1203 | + } |
1204 | + |
1205 | + db.transaction(function(tx) { |
1206 | + var rs = tx.executeSql("SELECT * FROM Queue ORDER BY ind ASC"); |
1207 | + for(var i = 0; i < rs.rows.length; i++) { |
1208 | + var episode = rs.rows.item(i); |
1209 | + if (source === episode.url) { |
1210 | + meta.name = episode.name |
1211 | + meta.artist = episode.artist |
1212 | + meta.image = episode.image |
1213 | + meta.guid = episode.guid |
1214 | + break |
1215 | + } |
1216 | + } |
1217 | + }); |
1218 | + |
1219 | + return meta |
1220 | +} |
1221 | + |
1222 | +// Function to get the next index for the queue |
1223 | +function getNextIndex(tx) { |
1224 | + var ind; |
1225 | + |
1226 | + if (tx === undefined) { |
1227 | + var db = init(); |
1228 | + db.transaction(function(tx) { |
1229 | + ind = getNextIndex(tx); |
1230 | + }); |
1231 | + } else { |
1232 | + var rs = tx.executeSql('SELECT MAX(ind) FROM Queue') |
1233 | + ind = isQueueEmpty(tx) ? 0 : rs.rows.item(0)["MAX(ind)"] + 1 |
1234 | + } |
1235 | + |
1236 | + return ind; |
1237 | +} |
1238 | + |
1239 | +function isQueueEmpty(tx) { |
1240 | + var empty = false; |
1241 | + |
1242 | + if (tx === undefined) { |
1243 | + var db = init(); |
1244 | + db.transaction( function(tx) { |
1245 | + empty = isQueueEmpty(tx) |
1246 | + }); |
1247 | + } else { |
1248 | + var rs = tx.executeSql("SELECT count(*) as value FROM Queue") |
1249 | + empty = rs.rows.item(0).value === 0 |
1250 | + } |
1251 | + |
1252 | + return empty |
1253 | +} |
1254 | + |
1255 | function subscribe(artist, name, feed, img) { |
1256 | var db = init(); |
1257 | db.transaction(function(tx) { |
1258 | @@ -259,8 +359,8 @@ |
1259 | var rs2 = tx.executeSql("SELECT rowid, * FROM Episode WHERE podcast=? ORDER BY published DESC", [rs.rows.item(i).rowid]); |
1260 | var loopCount = maxEpisodeDownload > rs2.rows.length ? rs2.rows.length : maxEpisodeDownload |
1261 | for (var j=0; j < loopCount; j++) { |
1262 | - if (!rs2.rows.item(j).downloadedfile && !rs2.rows.item(j).listened) { |
1263 | - downloader.addDownload(rs2.rows.item(j).guid, rs2.rows.item(j).audiourl) |
1264 | + if (!rs2.rows.item(j).downloadedfile && !rs2.rows.item(j).listened && rs2.rows.item(j).audiourl) { |
1265 | + podbird.downloadEpisode(rs.rows.item(i).image, rs2.rows.item(j).name, rs2.rows.item(j).guid, rs2.rows.item(j).audiourl) |
1266 | tx.executeSql("UPDATE Episode SET queued=1 WHERE guid = ?", [rs2.rows.item(j).guid]); |
1267 | } |
1268 | } |
1269 | |
1270 | === modified file 'app/settings/About.qml' |
1271 | --- app/settings/About.qml 2016-02-25 11:09:44 +0000 |
1272 | +++ app/settings/About.qml 2016-03-28 22:39:33 +0000 |
1273 | @@ -36,7 +36,6 @@ |
1274 | |
1275 | anchors { |
1276 | left: parent.left |
1277 | - leftMargin: units.gu(2) |
1278 | bottom: parent.bottom |
1279 | } |
1280 | |
1281 | |
1282 | === modified file 'app/ui/EpisodesPage.qml' |
1283 | --- app/ui/EpisodesPage.qml 2016-03-28 22:39:32 +0000 |
1284 | +++ app/ui/EpisodesPage.qml 2016-03-28 22:39:33 +0000 |
1285 | @@ -17,10 +17,10 @@ |
1286 | */ |
1287 | |
1288 | import QtQuick 2.4 |
1289 | -import QtMultimedia 5.4 |
1290 | +import QtMultimedia 5.6 |
1291 | import Ubuntu.Components 1.3 |
1292 | import QtQuick.LocalStorage 2.0 |
1293 | -import Ubuntu.DownloadManager 0.1 |
1294 | +import Ubuntu.DownloadManager 1.2 |
1295 | import Ubuntu.Components.Popups 1.3 |
1296 | import "../podcasts.js" as Podcasts |
1297 | import "../components" |
1298 | @@ -442,8 +442,8 @@ |
1299 | id: listItemLayout |
1300 | |
1301 | title.text: model.name !== undefined ? model.name.trim() : "Undefined" |
1302 | - title.color: currentGuid === model.guid || downloader.downloadingGuid === model.guid ? podbird.appTheme.focusText |
1303 | - : podbird.appTheme.baseText |
1304 | + title.color: downloader.downloadingGuid === model.guid ? podbird.appTheme.focusText |
1305 | + : podbird.appTheme.baseText |
1306 | // #FIXME: Change this 2 to prevent title eliding when UITK is updated to rev > 1800 |
1307 | title.maximumLineCount: 1 |
1308 | |
1309 | @@ -505,12 +505,24 @@ |
1310 | tx.executeSql("UPDATE Episode SET queued=1 WHERE guid = ?", [model.guid]); |
1311 | }); |
1312 | episodeModel.setProperty(model.index, "queued", 1) |
1313 | - downloader.addDownload(model.guid, model.audiourl); |
1314 | + if (model.audiourl) { |
1315 | + podbird.downloadEpisode(model.image, model.name, model.guid, model.audiourl) |
1316 | + } else { |
1317 | + console.log("[ERROR]: Invalid download url: " + model.audiourl) |
1318 | + } |
1319 | } |
1320 | } |
1321 | }, |
1322 | |
1323 | Action { |
1324 | + iconName: "add-to-playlist" |
1325 | + onTriggered: { |
1326 | + var url = model.downloadedfile ? "file://" + model.downloadedfile : model.audiourl |
1327 | + player.addEpisodeToQueue(model.guid, model.image, model.name, model.artist, url) |
1328 | + } |
1329 | + }, |
1330 | + |
1331 | + Action { |
1332 | iconName: model.favourited ? "unlike" : "like" |
1333 | onTriggered: { |
1334 | var db = Podcasts.init(); |
1335 | @@ -536,20 +548,10 @@ |
1336 | |
1337 | onClicked: { |
1338 | Haptics.play() |
1339 | - var db = Podcasts.init(); |
1340 | - db.transaction(function (tx) { |
1341 | - if (currentGuid !== model.guid) { |
1342 | - currentGuid = ""; |
1343 | - currentUrl = model.downloadedfile ? model.downloadedfile : model.audiourl; |
1344 | - var rs = tx.executeSql("SELECT position FROM Episode WHERE guid=?", [model.guid]); |
1345 | - playerLoader.item.play(); |
1346 | - playerLoader.item.seek(rs.rows.item(0).position); |
1347 | - currentName = model.name; |
1348 | - currentArtist = model.artist; |
1349 | - currentImage = model.image; |
1350 | - currentGuid = model.guid; |
1351 | - } |
1352 | - }); |
1353 | + if (currentGuid !== model.guid) { |
1354 | + currentUrl = model.downloadedfile ? "file://" + model.downloadedfile : model.audiourl; |
1355 | + player.playEpisode(model.guid, model.image, model.name, model.artist, currentUrl) |
1356 | + } |
1357 | } |
1358 | } |
1359 | |
1360 | |
1361 | === modified file 'app/ui/EpisodesTab.qml' |
1362 | --- app/ui/EpisodesTab.qml 2016-03-28 22:39:32 +0000 |
1363 | +++ app/ui/EpisodesTab.qml 2016-03-28 22:39:33 +0000 |
1364 | @@ -17,10 +17,10 @@ |
1365 | */ |
1366 | |
1367 | import QtQuick 2.4 |
1368 | -import QtMultimedia 5.4 |
1369 | +import QtMultimedia 5.6 |
1370 | import Ubuntu.Components 1.3 |
1371 | import QtQuick.LocalStorage 2.0 |
1372 | -import Ubuntu.DownloadManager 0.1 |
1373 | +import Ubuntu.DownloadManager 1.2 |
1374 | import Ubuntu.Components.Popups 1.3 |
1375 | import "../podcasts.js" as Podcasts |
1376 | import "../components" |
1377 | @@ -64,58 +64,6 @@ |
1378 | episodesPage.header = searchHeader |
1379 | searchField.item.forceActiveFocus() |
1380 | } |
1381 | - }, |
1382 | - |
1383 | - Action { |
1384 | - iconName: "select" |
1385 | - visible: episodesPageHeaderSections.selectedIndex === 0 |
1386 | - text: i18n.tr("Mark all listened") |
1387 | - onTriggered: { |
1388 | - var db = Podcasts.init(); |
1389 | - db.transaction(function (tx) { |
1390 | - for (var i=0; i<episodesModel.count; i++) { |
1391 | - tx.executeSql("UPDATE Episode SET listened=1 WHERE guid=?", [episodesModel.get(i).guid]); |
1392 | - } |
1393 | - episodesModel.clear() |
1394 | - }); |
1395 | - } |
1396 | - }, |
1397 | - |
1398 | - Action { |
1399 | - iconName: "save" |
1400 | - visible: episodesPageHeaderSections.selectedIndex === 0 |
1401 | - text: i18n.tr("Download all") |
1402 | - onTriggered: { |
1403 | - var db = Podcasts.init(); |
1404 | - db.transaction(function (tx) { |
1405 | - for (var i=0; i<episodesModel.count; i++) { |
1406 | - if (!episodesModel.get(i).downloadedfile) { |
1407 | - episodesModel.setProperty(i, "queued", 1) |
1408 | - tx.executeSql("UPDATE Episode SET queued=1 WHERE guid = ?", [episodesModel.get(i).guid]); |
1409 | - downloader.addDownload(episodesModel.get(i).guid, episodesModel.get(i).audiourl); |
1410 | - } |
1411 | - } |
1412 | - }); |
1413 | - } |
1414 | - }, |
1415 | - |
1416 | - Action { |
1417 | - iconName: "delete" |
1418 | - text: i18n.tr("Delete all") |
1419 | - visible: episodesPageHeaderSections.selectedIndex === 1 |
1420 | - onTriggered: { |
1421 | - var db = Podcasts.init(); |
1422 | - db.transaction(function (tx) { |
1423 | - for (var i=0; i<episodesModel.count; i++) { |
1424 | - if (episodesModel.get(i).downloadedfile) { |
1425 | - fileManager.deleteFile(episodesModel.get(i).downloadedfile); |
1426 | - tx.executeSql("UPDATE Episode SET downloadedfile = NULL WHERE guid = ?", [episodesModel.get(i).guid]); |
1427 | - episodesModel.setProperty(i, "downloadedfile", "") |
1428 | - } |
1429 | - } |
1430 | - }); |
1431 | - refreshModel(); |
1432 | - } |
1433 | } |
1434 | ] |
1435 | |
1436 | @@ -124,7 +72,6 @@ |
1437 | |
1438 | anchors { |
1439 | left: parent.left |
1440 | - leftMargin: units.gu(2) |
1441 | bottom: parent.bottom |
1442 | } |
1443 | |
1444 | @@ -132,7 +79,7 @@ |
1445 | selectedSectionColor: podbird.appTheme.focusText |
1446 | } |
1447 | |
1448 | - model: [i18n.tr("Recent"), i18n.tr("Downloaded"), i18n.tr("Favourites")] |
1449 | + model: [i18n.tr("Recent"), i18n.tr("Downloads"), i18n.tr("Favourites")] |
1450 | onSelectedIndexChanged: { |
1451 | refreshModel(); |
1452 | } |
1453 | @@ -174,6 +121,169 @@ |
1454 | } |
1455 | } |
1456 | |
1457 | + PageHeader { |
1458 | + id: selectionHeader |
1459 | + visible: episodeList.ViewItems.selectMode |
1460 | + |
1461 | + onVisibleChanged: { |
1462 | + if (visible) { |
1463 | + episodesPage.header = selectionHeader |
1464 | + } |
1465 | + } |
1466 | + |
1467 | + StyleHints { |
1468 | + backgroundColor: podbird.appTheme.background |
1469 | + } |
1470 | + |
1471 | + leadingActionBar.actions: [ |
1472 | + Action { |
1473 | + iconName: "back" |
1474 | + text: i18n.tr("Back") |
1475 | + onTriggered: { |
1476 | + episodeList.closeSelection() |
1477 | + } |
1478 | + } |
1479 | + ] |
1480 | + |
1481 | + trailingActionBar { |
1482 | + numberOfSlots: 6 |
1483 | + actions: [ |
1484 | + Action { |
1485 | + iconName: "select" |
1486 | + text: i18n.tr("Mark Listened") |
1487 | + enabled: episodeList.ViewItems.selectedIndices.length !== 0 |
1488 | + onTriggered: { |
1489 | + var db = Podcasts.init(); |
1490 | + db.transaction(function (tx) { |
1491 | + for (var i=0; i<episodeList.ViewItems.selectedIndices.length; i++) { |
1492 | + var index = episodeList.ViewItems.selectedIndices[i] |
1493 | + tx.executeSql("UPDATE Episode SET listened=1 WHERE guid=?", [episodesModel.get(index).guid]); |
1494 | + } |
1495 | + }); |
1496 | + |
1497 | + refreshModel(); |
1498 | + episodeList.closeSelection() |
1499 | + } |
1500 | + }, |
1501 | + |
1502 | + Action { |
1503 | + iconName: "save" |
1504 | + text: i18n.tr("Download episode(s)") |
1505 | + enabled: episodeList.ViewItems.selectedIndices.length !== 0 |
1506 | + visible: episodesPageHeaderSections.selectedIndex !== 1 |
1507 | + |
1508 | + onTriggered: { |
1509 | + var db = Podcasts.init(); |
1510 | + db.transaction(function (tx) { |
1511 | + for (var i=0; i<episodeList.ViewItems.selectedIndices.length; i++) { |
1512 | + var index = episodeList.ViewItems.selectedIndices[i] |
1513 | + if (!episodesModel.get(index).downloadedfile) { |
1514 | + episodesModel.setProperty(index, "queued", 1) |
1515 | + tx.executeSql("UPDATE Episode SET queued=1 WHERE guid = ?", [episodesModel.get(index).guid]); |
1516 | + if (episodesModel.get(index).audiourl) { |
1517 | + podbird.downloadEpisode(episodesModel.get(index).image, episodesModel.get(index).name, episodesModel.get(index).guid, episodesModel.get(index).audiourl) |
1518 | + } else { |
1519 | + console.log("[ERROR]: Invalid download url: " + episodesModel.get(index).audiourl) |
1520 | + } |
1521 | + } |
1522 | + } |
1523 | + }); |
1524 | + |
1525 | + refreshModel(); |
1526 | + episodeList.closeSelection() |
1527 | + } |
1528 | + }, |
1529 | + |
1530 | + Action { |
1531 | + iconName: "delete" |
1532 | + text: i18n.tr("Delete episode(s)") |
1533 | + enabled: episodeList.ViewItems.selectedIndices.length !== 0 |
1534 | + |
1535 | + onTriggered: { |
1536 | + var db = Podcasts.init(); |
1537 | + db.transaction(function (tx) { |
1538 | + for (var i=0; i<episodeList.ViewItems.selectedIndices.length; i++) { |
1539 | + var index = episodeList.ViewItems.selectedIndices[i] |
1540 | + if (episodesModel.get(index).downloadedfile) { |
1541 | + fileManager.deleteFile(episodesModel.get(index).downloadedfile); |
1542 | + tx.executeSql("UPDATE Episode SET downloadedfile = NULL WHERE guid = ?", [episodesModel.get(index).guid]); |
1543 | + episodesModel.setProperty(index, "downloadedfile", "") |
1544 | + } |
1545 | + } |
1546 | + }); |
1547 | + |
1548 | + refreshModel(); |
1549 | + episodeList.closeSelection() |
1550 | + } |
1551 | + }, |
1552 | + |
1553 | + Action { |
1554 | + iconName: "like" |
1555 | + text: i18n.tr("Favourite episode(s)") |
1556 | + visible: episodesPageHeaderSections.selectedIndex !== 2 |
1557 | + enabled: episodeList.ViewItems.selectedIndices.length !== 0 |
1558 | + |
1559 | + onTriggered: { |
1560 | + var db = Podcasts.init(); |
1561 | + db.transaction(function (tx) { |
1562 | + for (var i=0; i<episodeList.ViewItems.selectedIndices.length; i++) { |
1563 | + var index = episodeList.ViewItems.selectedIndices[i] |
1564 | + if (!episodesModel.get(index).favourited) { |
1565 | + tx.executeSql("UPDATE Episode SET favourited=1 WHERE guid=?", [episodesModel.get(index).guid]) |
1566 | + episodesModel.setProperty(index, "favourited", 1) |
1567 | + } |
1568 | + } |
1569 | + }); |
1570 | + |
1571 | + refreshModel(); |
1572 | + episodeList.closeSelection() |
1573 | + } |
1574 | + }, |
1575 | + |
1576 | + Action { |
1577 | + iconName: "unlike" |
1578 | + text: i18n.tr("Unfavourite episode(s)") |
1579 | + visible: episodesPageHeaderSections.selectedIndex === 2 |
1580 | + enabled: episodeList.ViewItems.selectedIndices.length !== 0 |
1581 | + |
1582 | + onTriggered: { |
1583 | + var db = Podcasts.init(); |
1584 | + db.transaction(function (tx) { |
1585 | + for (var i=0; i<episodeList.ViewItems.selectedIndices.length; i++) { |
1586 | + var index = episodeList.ViewItems.selectedIndices[i] |
1587 | + if (episodesModel.get(index).favourited) { |
1588 | + tx.executeSql("UPDATE Episode SET favourited=0 WHERE guid=?", [episodesModel.get(index).guid]) |
1589 | + episodesModel.setProperty(index, "favourited", 0) |
1590 | + } |
1591 | + } |
1592 | + }); |
1593 | + |
1594 | + refreshModel(); |
1595 | + episodeList.closeSelection() |
1596 | + } |
1597 | + }, |
1598 | + |
1599 | + Action { |
1600 | + iconName: "add-to-playlist" |
1601 | + text: i18n.tr("Add to queue") |
1602 | + enabled: episodeList.ViewItems.selectedIndices.length !== 0 |
1603 | + |
1604 | + onTriggered: { |
1605 | + for (var i=0; i<episodeList.ViewItems.selectedIndices.length; i++) { |
1606 | + var index = episodeList.ViewItems.selectedIndices[i] |
1607 | + if (episodesModel.get(index).audiourl) { |
1608 | + var url = episodesModel.get(index).downloadedfile ? "file://" + episodesModel.get(index).downloadedfile : episodesModel.get(index).audiourl |
1609 | + player.addEpisodeToQueue(episodesModel.get(index).guid, episodesModel.get(index).image, episodesModel.get(index).name, episodesModel.get(index).artist, url) |
1610 | + } |
1611 | + } |
1612 | + |
1613 | + episodeList.closeSelection() |
1614 | + } |
1615 | + } |
1616 | + ] |
1617 | + } |
1618 | + } |
1619 | + |
1620 | Loader { |
1621 | id: emptyState |
1622 | |
1623 | @@ -185,7 +295,7 @@ |
1624 | verticalCenterOffset: Qt.inputMethod.visible ? units.gu(4) : 0 |
1625 | } |
1626 | |
1627 | - sourceComponent: episodesModel.count === 0 || sortedEpisodeModel.count === 0 ? emptyStateComponent : undefined |
1628 | + sourceComponent: (episodesModel.count === 0 || sortedEpisodeModel.count === 0) && downloader.downloads.length === 0 ? emptyStateComponent : undefined |
1629 | } |
1630 | |
1631 | Component { |
1632 | @@ -315,6 +425,9 @@ |
1633 | ListView { |
1634 | id: episodeList |
1635 | |
1636 | + signal clearSelection() |
1637 | + signal closeSelection() |
1638 | + |
1639 | Component.onCompleted: { |
1640 | // FIXME: workaround for qtubuntu not returning values depending on the grid unit definition |
1641 | // for Flickable.maximumFlickVelocity and Flickable.flickDeceleration |
1642 | @@ -332,35 +445,77 @@ |
1643 | |
1644 | clip: true |
1645 | model: sortedEpisodeModel |
1646 | + |
1647 | + header: Column { |
1648 | + width: episodeList.width |
1649 | + visible: height !== 0 |
1650 | + height: downloader.downloads.length > 0 && episodesPageHeaderSections.selectedIndex === 1 ? childrenRect.height : 0 |
1651 | + |
1652 | + CustomSectionHeader { |
1653 | + title: i18n.tr("Downloads in progress") |
1654 | + } |
1655 | + |
1656 | + Repeater { |
1657 | + model: downloader.downloads |
1658 | + delegate: ListItem { |
1659 | + divider.visible: false |
1660 | + height: inProgressLayout.height |
1661 | + SlotsLayout { |
1662 | + id: inProgressLayout |
1663 | + |
1664 | + Image { |
1665 | + height: width |
1666 | + width: units.gu(6) |
1667 | + source: modelData.image !== undefined ? modelData.image : Qt.resolvedUrl("../graphics/podbird.png") |
1668 | + SlotsLayout.position: SlotsLayout.Leading |
1669 | + sourceSize { width: width; height: height } |
1670 | + } |
1671 | + |
1672 | + mainSlot: Column { |
1673 | + spacing: units.gu(0.5) |
1674 | + |
1675 | + Label { |
1676 | + text: modelData.title |
1677 | + width: parent.width |
1678 | + elide: Text.ElideRight |
1679 | + } |
1680 | + |
1681 | + CustomProgressBar { |
1682 | + width: parent.width |
1683 | + height: modelData.progress > 0 ? units.dp(5) : 0 |
1684 | + progress: modelData.progress |
1685 | + indeterminateProgress: modelData.progress < 0 || modelData.progress > 100 |
1686 | + } |
1687 | + } |
1688 | + } |
1689 | + } |
1690 | + } |
1691 | + |
1692 | + CustomSectionHeader { |
1693 | + title: i18n.tr("Downloaded episodes") |
1694 | + visible: sortedEpisodeModel.count !== 0 || episodesModel.count !== 0 |
1695 | + } |
1696 | + } |
1697 | + |
1698 | section.property: "diff" |
1699 | section.labelPositioning: ViewSection.InlineLabels |
1700 | - |
1701 | - section.delegate: ListItem { |
1702 | - height: headerText.title.text !== "" ? headerText.height + divider.height : units.gu(0) |
1703 | - divider.anchors.leftMargin: units.gu(2) |
1704 | - divider.anchors.rightMargin: units.gu(2) |
1705 | - |
1706 | - ListItemLayout { |
1707 | - id: headerText |
1708 | - title.text: { |
1709 | - if (section === "Today") { |
1710 | - return i18n.tr("Today") |
1711 | - } |
1712 | - |
1713 | - else if (section === "Yesterday") { |
1714 | - return i18n.tr("Yesterday") |
1715 | - } |
1716 | - |
1717 | - else if (section === "Older") { |
1718 | - return i18n.tr("Older") |
1719 | - } |
1720 | - |
1721 | - else { |
1722 | - return "" |
1723 | - } |
1724 | - } |
1725 | - title.color: podbird.appTheme.baseText |
1726 | - title.font.weight: Font.DemiBold |
1727 | + section.delegate: CustomSectionHeader { |
1728 | + title: { |
1729 | + if (section === "Today") { |
1730 | + return i18n.tr("Today") |
1731 | + } |
1732 | + |
1733 | + else if (section === "Yesterday") { |
1734 | + return i18n.tr("Yesterday") |
1735 | + } |
1736 | + |
1737 | + else if (section === "Older") { |
1738 | + return i18n.tr("Older") |
1739 | + } |
1740 | + |
1741 | + else { |
1742 | + return "" |
1743 | + } |
1744 | } |
1745 | } |
1746 | |
1747 | @@ -380,8 +535,8 @@ |
1748 | id: listItemLayout |
1749 | |
1750 | title.text: model.name !== undefined ? model.name.trim() : "Undefined" |
1751 | - title.color: currentGuid === model.guid || downloader.downloadingGuid === model.guid ? podbird.appTheme.focusText |
1752 | - : podbird.appTheme.baseText |
1753 | + title.color: downloader.downloadingGuid === model.guid ? podbird.appTheme.focusText |
1754 | + : podbird.appTheme.baseText |
1755 | // #FIXME: Change this 2 to prevent title eliding when UITK is updated to rev > 1800 |
1756 | title.maximumLineCount: 1 |
1757 | |
1758 | @@ -439,7 +594,11 @@ |
1759 | tx.executeSql("UPDATE Episode SET queued=1 WHERE guid = ?", [model.guid]); |
1760 | }); |
1761 | episodesModel.setProperty(model.index, "queued", 1) |
1762 | - downloader.addDownload(model.guid, model.audiourl); |
1763 | + if (model.audiourl) { |
1764 | + podbird.downloadEpisode(model.image, model.name, model.guid, model.audiourl) |
1765 | + } else { |
1766 | + console.log("[ERROR]: Invalid download url: " + model.audiourl) |
1767 | + } |
1768 | } |
1769 | } |
1770 | }, |
1771 | @@ -465,6 +624,14 @@ |
1772 | }, |
1773 | |
1774 | Action { |
1775 | + iconName: "add-to-playlist" |
1776 | + onTriggered: { |
1777 | + var url = model.downloadedfile ? "file://" + model.downloadedfile : model.audiourl |
1778 | + player.addEpisodeToQueue(model.guid, model.image, model.name, model.artist, url) |
1779 | + } |
1780 | + }, |
1781 | + |
1782 | + Action { |
1783 | iconName: model.favourited ? "unlike" : "like" |
1784 | onTriggered: { |
1785 | var db = Podcasts.init(); |
1786 | @@ -496,21 +663,29 @@ |
1787 | |
1788 | onClicked: { |
1789 | Haptics.play() |
1790 | - var db = Podcasts.init(); |
1791 | - db.transaction(function (tx) { |
1792 | + if (selectMode) { |
1793 | + selected = !selected |
1794 | + } else { |
1795 | if (currentGuid !== model.guid) { |
1796 | - currentGuid = ""; |
1797 | - currentUrl = model.downloadedfile ? model.downloadedfile : model.audiourl; |
1798 | - var rs = tx.executeSql("SELECT position FROM Episode WHERE guid=?", [model.guid]); |
1799 | - playerLoader.item.play(); |
1800 | - playerLoader.item.seek(rs.rows.item(0).position); |
1801 | - currentName = model.name; |
1802 | - currentArtist = model.artist; |
1803 | - currentImage = model.image; |
1804 | - currentGuid = model.guid; |
1805 | + currentUrl = model.downloadedfile ? "file://" + model.downloadedfile : model.audiourl; |
1806 | + player.playEpisode(model.guid, model.image, model.name, model.artist, currentUrl) |
1807 | } |
1808 | - }); |
1809 | - } |
1810 | + } |
1811 | + } |
1812 | + |
1813 | + onPressAndHold: { |
1814 | + ListView.view.ViewItems.selectMode = !ListView.view.ViewItems.selectMode |
1815 | + } |
1816 | + } |
1817 | + |
1818 | + onClearSelection: { |
1819 | + ViewItems.selectedIndices = [] |
1820 | + } |
1821 | + |
1822 | + onCloseSelection: { |
1823 | + clearSelection() |
1824 | + ViewItems.selectMode = false |
1825 | + episodesPage.header = standardHeader |
1826 | } |
1827 | |
1828 | Scrollbar { |
1829 | |
1830 | === modified file 'app/ui/NowPlayingPage.qml' |
1831 | --- app/ui/NowPlayingPage.qml 2016-02-25 11:09:44 +0000 |
1832 | +++ app/ui/NowPlayingPage.qml 2016-03-28 22:39:33 +0000 |
1833 | @@ -17,8 +17,9 @@ |
1834 | */ |
1835 | |
1836 | import QtQuick 2.4 |
1837 | -import QtMultimedia 5.4 |
1838 | +import QtMultimedia 5.6 |
1839 | import Ubuntu.Components 1.3 |
1840 | +import QtQuick.LocalStorage 2.0 |
1841 | import "../podcasts.js" as Podcasts |
1842 | import "../components" |
1843 | |
1844 | @@ -26,245 +27,349 @@ |
1845 | id: nowPlayingPage |
1846 | |
1847 | visible: false |
1848 | - title: i18n.tr("Now Playing") |
1849 | |
1850 | property bool isNowPlayingPage: true |
1851 | - property bool isLandscapeMode: width > height |
1852 | - |
1853 | - // Landscape rule |
1854 | - states: [ |
1855 | - State { |
1856 | - name: "landscape" |
1857 | - when: isLandscapeMode |
1858 | - |
1859 | - PropertyChanges { |
1860 | - target: blurredBackground |
1861 | - width: parent.width/2.2 |
1862 | - height: parent.height |
1863 | - } |
1864 | - |
1865 | - AnchorChanges { |
1866 | - target: blurredBackground |
1867 | + |
1868 | + header: PageHeader { |
1869 | + title: i18n.tr("Now Playing") |
1870 | + |
1871 | + StyleHints { |
1872 | + backgroundColor: podbird.appTheme.background |
1873 | + } |
1874 | + |
1875 | + trailingActionBar.actions: Action { |
1876 | + iconName: "delete" |
1877 | + visible: nowPlayingPageSections.selectedIndex === 1 |
1878 | + onTriggered: { |
1879 | + Podcasts.clearQueue() |
1880 | + player.playlist.clear() |
1881 | + mainStack.pop() |
1882 | + } |
1883 | + } |
1884 | + |
1885 | + extension: Sections { |
1886 | + id: nowPlayingPageSections |
1887 | + |
1888 | + anchors { |
1889 | + left: parent.left |
1890 | + bottom: parent.bottom |
1891 | + } |
1892 | + |
1893 | + StyleHints { |
1894 | + selectedSectionColor: podbird.appTheme.focusText |
1895 | + } |
1896 | + model: [i18n.tr("Full view"), i18n.tr("Queue")] |
1897 | + } |
1898 | + } |
1899 | + |
1900 | + VisualItemModel { |
1901 | + id: tabs |
1902 | + |
1903 | + Item { |
1904 | + id: nowPlayingItem |
1905 | + |
1906 | + width: tabView.width |
1907 | + height: tabView.height |
1908 | + |
1909 | + property bool isLandscapeMode: nowPlayingPage.width > nowPlayingPage.height |
1910 | + |
1911 | + // Landscape rule |
1912 | + states: [ |
1913 | + State { |
1914 | + name: "landscape" |
1915 | + when: nowPlayingItem.isLandscapeMode |
1916 | + |
1917 | + PropertyChanges { |
1918 | + target: blurredBackground |
1919 | + width: nowPlayingPage.width/2.2 |
1920 | + height: nowPlayingPage.height |
1921 | + } |
1922 | + |
1923 | + AnchorChanges { |
1924 | + target: blurredBackground |
1925 | + anchors { |
1926 | + top: nowPlayingItem.top |
1927 | + left: parent.left |
1928 | + right: undefined |
1929 | + } |
1930 | + } |
1931 | + |
1932 | + AnchorChanges { |
1933 | + target: dataContainer |
1934 | + anchors { |
1935 | + top: nowPlayingItem.top |
1936 | + left: blurredBackground.right |
1937 | + right: parent.right |
1938 | + bottom: parent.bottom |
1939 | + } |
1940 | + } |
1941 | + } |
1942 | + ] |
1943 | + |
1944 | + BlurredBackground { |
1945 | + id: blurredBackground |
1946 | + |
1947 | + anchors.left: parent.left |
1948 | + anchors.top: nowPlayingItem.top |
1949 | + anchors.right: parent.right |
1950 | + height: title.lineCount === 1 ? nowPlayingPage.height/2.3 + units.gu(3) |
1951 | + : nowPlayingPage.height/2.3 |
1952 | + art: currentImage |
1953 | + |
1954 | + Image { |
1955 | + width: Math.min(nowPlayingPage.width/2, nowPlayingPage.height/2) |
1956 | + height: width |
1957 | + sourceSize.height: width |
1958 | + sourceSize.width: width |
1959 | + source: currentImage |
1960 | + asynchronous: true |
1961 | + anchors.centerIn: parent |
1962 | + } |
1963 | + } |
1964 | + |
1965 | + Item { |
1966 | + id: dataContainer |
1967 | + |
1968 | anchors { |
1969 | - top: parent.top |
1970 | + top: blurredBackground.bottom |
1971 | left: parent.left |
1972 | - right: undefined |
1973 | - } |
1974 | - } |
1975 | - |
1976 | - AnchorChanges { |
1977 | - target: dataContainer |
1978 | - anchors { |
1979 | - top: parent.top |
1980 | - left: blurredBackground.right |
1981 | right: parent.right |
1982 | bottom: parent.bottom |
1983 | + margins: units.gu(2) |
1984 | + bottomMargin: nowPlayingItem.isLandscapeMode ? units.gu(4) : units.gu(2) |
1985 | + } |
1986 | + |
1987 | + Label { |
1988 | + id: title |
1989 | + anchors.left: parent.left |
1990 | + anchors.right: parent.right |
1991 | + anchors.top: parent.top |
1992 | + text: currentName |
1993 | + elide: Text.ElideRight |
1994 | + textSize: Label.Large |
1995 | + maximumLineCount: 2 |
1996 | + wrapMode: Text.WordWrap |
1997 | + color: podbird.appTheme.baseText |
1998 | + } |
1999 | + |
2000 | + Label { |
2001 | + id: artist |
2002 | + anchors.left: title.left |
2003 | + anchors.right: title.right |
2004 | + anchors.top: title.bottom |
2005 | + anchors.topMargin: units.gu(1) |
2006 | + text: currentArtist |
2007 | + elide: Text.ElideRight |
2008 | + textSize: Label.Small |
2009 | + color: podbird.appTheme.baseSubText |
2010 | + } |
2011 | + |
2012 | + Slider { |
2013 | + id: scrubber |
2014 | + |
2015 | + anchors { |
2016 | + left: parent.left |
2017 | + right: parent.right |
2018 | + bottom: controls.top |
2019 | + bottomMargin: nowPlayingItem.isLandscapeMode && title.lineCount < 2 ? units.gu(4) : units.gu(2) |
2020 | + } |
2021 | + |
2022 | + live: true |
2023 | + minimumValue: 0 |
2024 | + maximumValue: player.duration |
2025 | + value: player.position |
2026 | + height: units.gu(2) |
2027 | + |
2028 | + onValueChanged: { |
2029 | + if (pressed) { |
2030 | + player.seek(value); |
2031 | + } |
2032 | + } |
2033 | + |
2034 | + function formatValue(v) { return Podcasts.formatTime(v/1000); } |
2035 | + StyleHints { foregroundColor: podbird.appTheme.focusText } |
2036 | + } |
2037 | + |
2038 | + Connections { |
2039 | + target: player |
2040 | + onPositionChanged: scrubber.value = player.position |
2041 | + } |
2042 | + |
2043 | + Label { |
2044 | + id: startTime |
2045 | + textSize: Label.Small |
2046 | + anchors.left: scrubber.left |
2047 | + anchors.top: scrubber.bottom |
2048 | + color: podbird.appTheme.baseText |
2049 | + text: Podcasts.formatTime(player.position / 1000) |
2050 | + } |
2051 | + |
2052 | + Label { |
2053 | + id: endTime |
2054 | + textSize: Label.Small |
2055 | + anchors.right: scrubber.right |
2056 | + anchors.top: scrubber.bottom |
2057 | + color: podbird.appTheme.baseText |
2058 | + text: Podcasts.formatTime(player.duration / 1000) |
2059 | + } |
2060 | + |
2061 | + Row { |
2062 | + id: controls |
2063 | + |
2064 | + anchors.bottom: parent.bottom |
2065 | + anchors.horizontalCenter: parent.horizontalCenter |
2066 | + spacing: units.gu(1) |
2067 | + |
2068 | + AbstractButton { |
2069 | + id: mediaBackwardButton |
2070 | + width: units.gu(6) |
2071 | + height: width |
2072 | + anchors.verticalCenter: parent.verticalCenter |
2073 | + enabled: player.playlist.canGoPrevious |
2074 | + opacity: enabled ? 1.0 : 0.4 |
2075 | + onClicked: player.playlist.previous() |
2076 | + |
2077 | + Icon { |
2078 | + id: mediaBackwardIcon |
2079 | + width: units.gu(3) |
2080 | + height: width |
2081 | + anchors.centerIn: parent |
2082 | + color: podbird.appTheme.baseIcon |
2083 | + name: "media-skip-backward" |
2084 | + } |
2085 | + } |
2086 | + |
2087 | + AbstractButton { |
2088 | + id: skipBackwardButton |
2089 | + width: units.gu(6) |
2090 | + height: width |
2091 | + anchors.verticalCenter: parent.verticalCenter |
2092 | + opacity: player.position === 0 ? 0.4 : 1.0 |
2093 | + onClicked: { |
2094 | + if (player.position > 0) { |
2095 | + player.seek(player.position - podbird.settings.skipBack * 1000); |
2096 | + } |
2097 | + } |
2098 | + |
2099 | + Row { |
2100 | + spacing: units.gu(1) |
2101 | + anchors.centerIn: parent |
2102 | + |
2103 | + Label { |
2104 | + // TRANSLATORS: The string shown in the UI is -15s to denote the number of seconds that the podcast playback will skip backward. |
2105 | + // xgettext: no-c-format |
2106 | + text: i18n.tr("-%1s").arg(podbird.settings.skipBack) |
2107 | + textSize: Label.XxSmall |
2108 | + color: podbird.appTheme.baseText |
2109 | + anchors.verticalCenter: skipBackwardIcon.verticalCenter |
2110 | + } |
2111 | + |
2112 | + Icon { |
2113 | + id: skipBackwardIcon |
2114 | + width: units.gu(3) |
2115 | + height: width |
2116 | + name: "media-seek-backward" |
2117 | + color: podbird.appTheme.baseIcon |
2118 | + } |
2119 | + } |
2120 | + } |
2121 | + |
2122 | + AbstractButton { |
2123 | + id: playButton |
2124 | + width: units.gu(10) |
2125 | + height: width |
2126 | + opacity: playButton.pressed ? 0.4 : 1.0 |
2127 | + onClicked: player.playbackState === MediaPlayer.PlayingState ? player.pause() : player.play() |
2128 | + |
2129 | + Icon { |
2130 | + id: playIcon |
2131 | + width: units.gu(6) |
2132 | + height: width |
2133 | + anchors.centerIn: parent |
2134 | + color: podbird.appTheme.baseIcon |
2135 | + name: player.playbackState === MediaPlayer.PlayingState ? "media-playback-pause" |
2136 | + : "media-playback-start" |
2137 | + } |
2138 | + } |
2139 | + |
2140 | + AbstractButton { |
2141 | + id: skipForwardButton |
2142 | + width: units.gu(6) |
2143 | + height: width |
2144 | + anchors.verticalCenter: parent.verticalCenter |
2145 | + opacity: player.position === 0 ? 0.4 : 1.0 |
2146 | + onClicked: { |
2147 | + if (player.position > 0) { |
2148 | + player.seek(player.position + podbird.settings.skipForward * 1000); |
2149 | + } |
2150 | + } |
2151 | + |
2152 | + Row { |
2153 | + spacing: units.gu(1) |
2154 | + anchors.centerIn: parent |
2155 | + |
2156 | + Icon { |
2157 | + id: skipForwardIcon |
2158 | + width: units.gu(3) |
2159 | + height: width |
2160 | + name: "media-seek-forward" |
2161 | + color: podbird.appTheme.baseIcon |
2162 | + } |
2163 | + |
2164 | + Label { |
2165 | + // TRANSLATORS: The string shown in the UI is +15s to denote the number of seconds that the podcast playback will skip forward. |
2166 | + // xgettext: no-c-format |
2167 | + text: i18n.tr("+%1s").arg(podbird.settings.skipForward) |
2168 | + textSize: Label.XxSmall |
2169 | + color: podbird.appTheme.baseText |
2170 | + anchors.verticalCenter: skipForwardIcon.verticalCenter |
2171 | + } |
2172 | + } |
2173 | + } |
2174 | + |
2175 | + AbstractButton { |
2176 | + id: mediaForwardButton |
2177 | + width: units.gu(6) |
2178 | + height: width |
2179 | + anchors.verticalCenter: parent.verticalCenter |
2180 | + enabled: player.playlist.canGoNext |
2181 | + opacity: enabled ? 1.0 : 0.4 |
2182 | + onClicked: player.playlist.next() |
2183 | + |
2184 | + Icon { |
2185 | + id: mediaForwardIcon |
2186 | + width: units.gu(3) |
2187 | + height: width |
2188 | + anchors.centerIn: parent |
2189 | + color: podbird.appTheme.baseIcon |
2190 | + name: "media-skip-forward" |
2191 | + } |
2192 | + } |
2193 | } |
2194 | } |
2195 | } |
2196 | - ] |
2197 | - |
2198 | - BlurredBackground { |
2199 | - id: blurredBackground |
2200 | - |
2201 | - anchors.left: parent.left |
2202 | - anchors.top: parent.top |
2203 | - anchors.right: parent.right |
2204 | - height: title.lineCount === 1 ? parent.height/2 + units.gu(3) |
2205 | - : parent.height/2 |
2206 | - art: currentImage |
2207 | - |
2208 | - Image { |
2209 | - width: Math.min(parent.width/2, parent.height) |
2210 | - height: width |
2211 | - sourceSize.height: width |
2212 | - sourceSize.width: width |
2213 | - source: currentImage |
2214 | - asynchronous: true |
2215 | - anchors.centerIn: parent |
2216 | + |
2217 | + Queue { |
2218 | + width: tabView.width |
2219 | + height: tabView.height |
2220 | } |
2221 | } |
2222 | |
2223 | - Item { |
2224 | - id: dataContainer |
2225 | + ListView { |
2226 | + id: tabView |
2227 | + model: tabs |
2228 | + interactive: false |
2229 | |
2230 | anchors { |
2231 | - top: blurredBackground.bottom |
2232 | + top: nowPlayingPage.header.bottom |
2233 | left: parent.left |
2234 | right: parent.right |
2235 | bottom: parent.bottom |
2236 | - margins: units.gu(2) |
2237 | - bottomMargin: isLandscapeMode ? units.gu(4) : units.gu(2) |
2238 | - } |
2239 | - |
2240 | - Label { |
2241 | - id: title |
2242 | - anchors.left: parent.left |
2243 | - anchors.right: parent.right |
2244 | - anchors.top: parent.top |
2245 | - text: currentName |
2246 | - elide: Text.ElideRight |
2247 | - textSize: Label.Large |
2248 | - maximumLineCount: 2 |
2249 | - wrapMode: Text.WordWrap |
2250 | - color: podbird.appTheme.baseText |
2251 | - } |
2252 | - |
2253 | - Label { |
2254 | - id: artist |
2255 | - anchors.left: title.left |
2256 | - anchors.right: title.right |
2257 | - anchors.top: title.bottom |
2258 | - anchors.topMargin: units.gu(1) |
2259 | - text: currentArtist |
2260 | - elide: Text.ElideRight |
2261 | - textSize: Label.Small |
2262 | - color: podbird.appTheme.baseSubText |
2263 | - } |
2264 | - |
2265 | - Slider { |
2266 | - id: scrubber |
2267 | - |
2268 | - anchors { |
2269 | - left: parent.left |
2270 | - right: parent.right |
2271 | - bottom: controls.top |
2272 | - bottomMargin: isLandscapeMode && title.lineCount < 2 ? units.gu(4) : units.gu(2) |
2273 | - } |
2274 | - |
2275 | - live: true |
2276 | - minimumValue: 0 |
2277 | - maximumValue: playerLoader.item.duration |
2278 | - value: playerLoader.item.position |
2279 | - height: units.gu(2) |
2280 | - |
2281 | - onValueChanged: { |
2282 | - if (pressed) { |
2283 | - playerLoader.item.seek(value); |
2284 | - } |
2285 | - } |
2286 | - |
2287 | - function formatValue(v) { return Podcasts.formatTime(v/1000); } |
2288 | - StyleHints { foregroundColor: podbird.appTheme.focusText } |
2289 | - } |
2290 | - |
2291 | - Connections { |
2292 | - target: playerLoader.item |
2293 | - onPositionChanged: scrubber.value = playerLoader.item.position |
2294 | - } |
2295 | - |
2296 | - Label { |
2297 | - id: startTime |
2298 | - textSize: Label.Small |
2299 | - anchors.left: scrubber.left |
2300 | - anchors.top: scrubber.bottom |
2301 | - color: podbird.appTheme.baseText |
2302 | - text: Podcasts.formatTime(playerLoader.item.position / 1000) |
2303 | - } |
2304 | - |
2305 | - Label { |
2306 | - id: endTime |
2307 | - textSize: Label.Small |
2308 | - anchors.right: scrubber.right |
2309 | - anchors.top: scrubber.bottom |
2310 | - color: podbird.appTheme.baseText |
2311 | - text: Podcasts.formatTime(playerLoader.item.duration / 1000) |
2312 | - } |
2313 | - |
2314 | - Row { |
2315 | - id: controls |
2316 | - |
2317 | - anchors.bottom: parent.bottom |
2318 | - anchors.horizontalCenter: parent.horizontalCenter |
2319 | - spacing: units.gu(2) |
2320 | - |
2321 | - AbstractButton { |
2322 | - id: skipBackwardButton |
2323 | - width: units.gu(6) |
2324 | - height: width |
2325 | - anchors.verticalCenter: parent.verticalCenter |
2326 | - opacity: playerLoader.item.position === 0 ? 0.4 : 1.0 |
2327 | - onClicked: { |
2328 | - if (playerLoader.item.position > 0) { |
2329 | - playerLoader.item.seek(playerLoader.item.position - podbird.settings.skipBack * 1000); |
2330 | - } |
2331 | - } |
2332 | - |
2333 | - Row { |
2334 | - spacing: units.gu(1) |
2335 | - anchors.centerIn: parent |
2336 | - |
2337 | - Label { |
2338 | - // TRANSLATORS: The string shown in the UI is -15s to denote the number of seconds that the podcast playback will skip backward. |
2339 | - // xgettext: no-c-format |
2340 | - text: i18n.tr("-%1s").arg(podbird.settings.skipBack) |
2341 | - textSize: Label.XxSmall |
2342 | - color: podbird.appTheme.baseText |
2343 | - anchors.verticalCenter: skipBackwardIcon.verticalCenter |
2344 | - } |
2345 | - |
2346 | - Icon { |
2347 | - id: skipBackwardIcon |
2348 | - width: units.gu(3) |
2349 | - height: width |
2350 | - name: "media-seek-backward" |
2351 | - color: podbird.appTheme.baseIcon |
2352 | - } |
2353 | - } |
2354 | - } |
2355 | - |
2356 | - AbstractButton { |
2357 | - id: playButton |
2358 | - width: units.gu(10) |
2359 | - height: width |
2360 | - opacity: playButton.pressed ? 0.4 : 1.0 |
2361 | - onClicked: playerLoader.item.playbackState === MediaPlayer.PlayingState ? playerLoader.item.pause() : playerLoader.item.play() |
2362 | - |
2363 | - Icon { |
2364 | - id: playIcon |
2365 | - width: units.gu(6) |
2366 | - height: width |
2367 | - anchors.centerIn: parent |
2368 | - color: podbird.appTheme.baseIcon |
2369 | - name: playerLoader.item.playbackState === MediaPlayer.PlayingState ? "media-playback-pause" |
2370 | - : "media-playback-start" |
2371 | - } |
2372 | - } |
2373 | - |
2374 | - AbstractButton { |
2375 | - id: skipForwardButton |
2376 | - width: units.gu(6) |
2377 | - height: width |
2378 | - anchors.verticalCenter: parent.verticalCenter |
2379 | - opacity: playerLoader.item.position === 0 ? 0.4 : 1.0 |
2380 | - onClicked: { |
2381 | - if (playerLoader.item.position > 0) { |
2382 | - playerLoader.item.seek(playerLoader.item.position + podbird.settings.skipForward * 1000); |
2383 | - } |
2384 | - } |
2385 | - |
2386 | - Row { |
2387 | - spacing: units.gu(1) |
2388 | - anchors.centerIn: parent |
2389 | - |
2390 | - Icon { |
2391 | - id: skipForwardIcon |
2392 | - width: units.gu(3) |
2393 | - height: width |
2394 | - name: "media-seek-forward" |
2395 | - color: podbird.appTheme.baseIcon |
2396 | - } |
2397 | - |
2398 | - Label { |
2399 | - // TRANSLATORS: The string shown in the UI is +15s to denote the number of seconds that the podcast playback will skip forward. |
2400 | - // xgettext: no-c-format |
2401 | - text: i18n.tr("+%1s").arg(podbird.settings.skipForward) |
2402 | - textSize: Label.XxSmall |
2403 | - color: podbird.appTheme.baseText |
2404 | - anchors.verticalCenter: skipForwardIcon.verticalCenter |
2405 | - } |
2406 | - } |
2407 | - } |
2408 | - } |
2409 | + } |
2410 | + |
2411 | + orientation: Qt.Horizontal |
2412 | + snapMode: ListView.SnapOneItem |
2413 | + currentIndex: nowPlayingPageSections.selectedIndex |
2414 | + highlightMoveDuration: UbuntuAnimation.SlowDuration |
2415 | } |
2416 | } |
2417 | |
2418 | === modified file 'app/ui/PlayerControls.qml' |
2419 | --- app/ui/PlayerControls.qml 2016-02-25 11:09:44 +0000 |
2420 | +++ app/ui/PlayerControls.qml 2016-03-28 22:39:33 +0000 |
2421 | @@ -17,7 +17,7 @@ |
2422 | */ |
2423 | |
2424 | import QtQuick 2.4 |
2425 | -import QtMultimedia 5.4 |
2426 | +import QtMultimedia 5.6 |
2427 | import Ubuntu.Components 1.3 |
2428 | |
2429 | Rectangle { |
2430 | @@ -49,7 +49,7 @@ |
2431 | anchors.top: cover.bottom |
2432 | color: podbird.appTheme.focusText |
2433 | height: units.gu(0.25) |
2434 | - width: playerLoader.item.duration > 0 ? (playerLoader.item.position / playerLoader.item.duration) * parent.width : 0 |
2435 | + width: player.duration > 0 ? (player.position / player.duration) * parent.width : 0 |
2436 | } |
2437 | |
2438 | Column { |
2439 | @@ -97,16 +97,16 @@ |
2440 | visible: playButton.pressed |
2441 | } |
2442 | |
2443 | - onClicked: playerLoader.item.playbackState === MediaPlayer.PlayingState ? playerLoader.item.pause() |
2444 | - : playerLoader.item.play() |
2445 | + onClicked: player.playbackState === MediaPlayer.PlayingState ? player.pause() |
2446 | + : player.play() |
2447 | |
2448 | Icon { |
2449 | color: "white" |
2450 | width: units.gu(3) |
2451 | height: width |
2452 | anchors.centerIn: playButtonBackground |
2453 | - name: playerLoader.item.playbackState === MediaPlayer.PlayingState ? "media-playback-pause" |
2454 | - : "media-playback-start" |
2455 | + name: player.playbackState === MediaPlayer.PlayingState ? "media-playback-pause" |
2456 | + : "media-playback-start" |
2457 | opacity: playButton.pressed ? 0.4 : 1.0 |
2458 | } |
2459 | } |
2460 | |
2461 | === modified file 'app/ui/PodcastsTab.qml' |
2462 | --- app/ui/PodcastsTab.qml 2016-03-28 22:39:32 +0000 |
2463 | +++ app/ui/PodcastsTab.qml 2016-03-28 22:39:33 +0000 |
2464 | @@ -17,10 +17,10 @@ |
2465 | */ |
2466 | |
2467 | import QtQuick 2.4 |
2468 | -import QtMultimedia 5.4 |
2469 | +import QtMultimedia 5.6 |
2470 | import QtQuick.LocalStorage 2.0 |
2471 | import Ubuntu.Components 1.3 |
2472 | -import Ubuntu.DownloadManager 0.1 |
2473 | +import Ubuntu.DownloadManager 1.2 |
2474 | import Ubuntu.Components.Popups 1.0 |
2475 | import "../podcasts.js" as Podcasts |
2476 | import "../components" |
2477 | @@ -166,12 +166,13 @@ |
2478 | CardView { |
2479 | id: cardView |
2480 | clip: true |
2481 | + heightOffset: units.gu(4) |
2482 | model: sortedPodcastModel |
2483 | delegate: Card { |
2484 | id: albumCard |
2485 | coverArt: model.image !== undefined ? model.image : Qt.resolvedUrl("../graphics/podbird.png") |
2486 | primaryText: model.name !== undefined ? model.name.trim() : "Undefined" |
2487 | - secondaryText: model.episodeCount > 0 ? i18n.tr("%1 unheard episode", "%1 unheard episodes", model.episodeCount).arg(model.episodeCount) |
2488 | + secondaryText: model.episodeCount > 0 ? model.episodeCount |
2489 | : "" |
2490 | onClicked: { |
2491 | if(podcastPage.header === searchHeader) { |
2492 | |
2493 | === added file 'app/ui/Queue.qml' |
2494 | --- app/ui/Queue.qml 1970-01-01 00:00:00 +0000 |
2495 | +++ app/ui/Queue.qml 2016-03-28 22:39:33 +0000 |
2496 | @@ -0,0 +1,88 @@ |
2497 | +/* |
2498 | + * Copyright 2016 Podbird Team |
2499 | + * |
2500 | + * This file is part of Podbird. |
2501 | + * |
2502 | + * Podbird is free software; you can redistribute it and/or modify |
2503 | + * it under the terms of the GNU General Public License as published by |
2504 | + * the Free Software Foundation; version 3. |
2505 | + * |
2506 | + * Podbird is distributed in the hope that it will be useful, |
2507 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
2508 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
2509 | + * GNU General Public License for more details. |
2510 | + * |
2511 | + * You should have received a copy of the GNU General Public License |
2512 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
2513 | + */ |
2514 | + |
2515 | +import QtQuick 2.4 |
2516 | +import Ubuntu.Components 1.3 |
2517 | +import "../podcasts.js" as Podcasts |
2518 | +import "../components" |
2519 | + |
2520 | +Item { |
2521 | + id: queuePage |
2522 | + |
2523 | + ListView { |
2524 | + id: queueList |
2525 | + |
2526 | + anchors.fill: parent |
2527 | + model: player.playlist |
2528 | + |
2529 | + delegate: ListItem { |
2530 | + id: listItem |
2531 | + |
2532 | + height: layout.height |
2533 | + divider.visible: false |
2534 | + |
2535 | + ListItemLayout { |
2536 | + id: layout |
2537 | + |
2538 | + // Grab the metaData for the current index using its unique source url |
2539 | + property var metaModel: player.metaForSource(model.source) |
2540 | + |
2541 | + Image { |
2542 | + id: imgFrame |
2543 | + width: units.gu(6) |
2544 | + height: width |
2545 | + source: Qt.resolvedUrl(layout.metaModel.image) |
2546 | + sourceSize.height: width |
2547 | + sourceSize.width: width |
2548 | + SlotsLayout.position: SlotsLayout.First |
2549 | + } |
2550 | + |
2551 | + title.text: layout.metaModel.name |
2552 | + // #FIXME: Change this 2 to prevent title eliding when UITK is updated to rev > 1800 |
2553 | + title.maximumLineCount: 1 |
2554 | + title.color: player.playlist.currentIndex === index ? podbird.appTheme.focusText |
2555 | + : podbird.appTheme.baseText |
2556 | + |
2557 | + subtitle.text: layout.metaModel.artist |
2558 | + subtitle.color: podbird.appTheme.baseSubText |
2559 | + } |
2560 | + |
2561 | + leadingActions: ListItemActions { |
2562 | + actions: [ |
2563 | + Action { |
2564 | + iconName: "delete" |
2565 | + onTriggered: { |
2566 | + player.playlist.removeItem(index) |
2567 | + var source = model.source |
2568 | + source = source.toString() |
2569 | + Podcasts.removeItemFromQueue(source) |
2570 | + } |
2571 | + } |
2572 | + ] |
2573 | + } |
2574 | + |
2575 | + onClicked: { |
2576 | + if (player.playlist.currentIndex === index) { |
2577 | + player.toggle() |
2578 | + } else { |
2579 | + player.playlist.currentIndex = index |
2580 | + } |
2581 | + } |
2582 | + } |
2583 | + } |
2584 | +} |
2585 | |
2586 | === modified file 'po/com.mikeasoft.podbird.pot' |
2587 | --- po/com.mikeasoft.podbird.pot 2016-03-28 22:39:32 +0000 |
2588 | +++ po/com.mikeasoft.podbird.pot 2016-03-28 22:39:33 +0000 |
2589 | @@ -8,7 +8,7 @@ |
2590 | msgstr "" |
2591 | "Project-Id-Version: \n" |
2592 | "Report-Msgid-Bugs-To: \n" |
2593 | -"POT-Creation-Date: 2016-03-19 07:47+0530\n" |
2594 | +"POT-Creation-Date: 2016-03-19 19:13+0530\n" |
2595 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" |
2596 | "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" |
2597 | "Language-Team: LANGUAGE <LL@li.org>\n" |
2598 | @@ -34,52 +34,42 @@ |
2599 | msgid "Skip" |
2600 | msgstr "" |
2601 | |
2602 | -#. TRANSLATORS: this refers to a number of songs greater than one. The actual number will be prepended to the string automatically (plural forms are not yet fully supported in usermetrics, the library that displays that string) |
2603 | -#: ../app/podbird.qml:175 |
2604 | -#, qt-format |
2605 | -msgid "Podcasts listened to today: <b>%1</b>" |
2606 | -msgstr "" |
2607 | - |
2608 | -#: ../app/podbird.qml:176 |
2609 | -msgid "No podcasts listened to today" |
2610 | -msgstr "" |
2611 | - |
2612 | -#: ../app/podcasts.js:182 |
2613 | +#: ../app/podcasts.js:282 |
2614 | #, no-c-format, qt-format |
2615 | msgid "%1 hr %2 min" |
2616 | msgstr "" |
2617 | |
2618 | -#: ../app/podcasts.js:191 |
2619 | +#: ../app/podcasts.js:291 |
2620 | #, no-c-format, qt-format |
2621 | msgid "%1 hr" |
2622 | msgstr "" |
2623 | |
2624 | -#: ../app/podcasts.js:199 |
2625 | +#: ../app/podcasts.js:299 |
2626 | #, no-c-format, qt-format |
2627 | msgid "%1 min" |
2628 | msgstr "" |
2629 | |
2630 | #. TRANSLATORS: About as in information about the app |
2631 | -#: ../app/settings/About.qml:28 ../app/settings/About.qml:47 |
2632 | +#: ../app/settings/About.qml:28 ../app/settings/About.qml:46 |
2633 | #: ../app/ui/SettingsPage.qml:222 |
2634 | msgid "About" |
2635 | msgstr "" |
2636 | |
2637 | -#: ../app/settings/About.qml:47 |
2638 | +#: ../app/settings/About.qml:46 |
2639 | msgid "Credits" |
2640 | msgstr "" |
2641 | |
2642 | #. TRANSLATORS: Podbird version number e.g Version 0.7 |
2643 | -#: ../app/settings/About.qml:93 |
2644 | +#: ../app/settings/About.qml:92 |
2645 | #, qt-format |
2646 | msgid "Version %1" |
2647 | msgstr "" |
2648 | |
2649 | -#: ../app/settings/About.qml:114 |
2650 | +#: ../app/settings/About.qml:113 |
2651 | msgid "Released under the terms of the GNU GPL v3" |
2652 | msgstr "" |
2653 | |
2654 | -#: ../app/settings/About.qml:124 |
2655 | +#: ../app/settings/About.qml:123 |
2656 | #, qt-format |
2657 | msgid "Source code available on %1" |
2658 | msgstr "" |
2659 | @@ -168,7 +158,7 @@ |
2660 | msgid "Search Episode" |
2661 | msgstr "" |
2662 | |
2663 | -#: ../app/ui/EpisodesPage.qml:82 ../app/ui/EpisodesTab.qml:72 |
2664 | +#: ../app/ui/EpisodesPage.qml:82 |
2665 | msgid "Mark all listened" |
2666 | msgstr "" |
2667 | |
2668 | @@ -177,7 +167,7 @@ |
2669 | msgid "Unsubscribe" |
2670 | msgstr "" |
2671 | |
2672 | -#: ../app/ui/EpisodesPage.qml:136 ../app/ui/EpisodesTab.qml:173 |
2673 | +#: ../app/ui/EpisodesPage.qml:136 ../app/ui/EpisodesTab.qml:120 |
2674 | msgid "Search episode" |
2675 | msgstr "" |
2676 | |
2677 | @@ -195,11 +185,11 @@ |
2678 | msgid "Cancel" |
2679 | msgstr "" |
2680 | |
2681 | -#: ../app/ui/EpisodesPage.qml:223 ../app/ui/EpisodesTab.qml:295 |
2682 | +#: ../app/ui/EpisodesPage.qml:223 ../app/ui/EpisodesTab.qml:405 |
2683 | msgid "Episode Description" |
2684 | msgstr "" |
2685 | |
2686 | -#: ../app/ui/EpisodesPage.qml:234 ../app/ui/EpisodesTab.qml:306 |
2687 | +#: ../app/ui/EpisodesPage.qml:234 ../app/ui/EpisodesTab.qml:416 |
2688 | #: ../app/ui/SearchPage.qml:170 |
2689 | msgid "Close" |
2690 | msgstr "" |
2691 | @@ -220,82 +210,122 @@ |
2692 | msgid "Listened" |
2693 | msgstr "" |
2694 | |
2695 | -#: ../app/ui/EpisodesPage.qml:413 ../app/ui/EpisodesTab.qml:135 |
2696 | +#: ../app/ui/EpisodesPage.qml:413 |
2697 | msgid "Downloaded" |
2698 | msgstr "" |
2699 | |
2700 | -#: ../app/ui/EpisodesTab.qml:87 |
2701 | -msgid "Download all" |
2702 | -msgstr "" |
2703 | - |
2704 | -#: ../app/ui/EpisodesTab.qml:104 |
2705 | -msgid "Delete all" |
2706 | -msgstr "" |
2707 | - |
2708 | -#: ../app/ui/EpisodesTab.qml:135 |
2709 | +#: ../app/ui/EpisodesTab.qml:82 |
2710 | msgid "Recent" |
2711 | msgstr "" |
2712 | |
2713 | -#: ../app/ui/EpisodesTab.qml:135 |
2714 | +#: ../app/ui/EpisodesTab.qml:82 |
2715 | +msgid "Downloads" |
2716 | +msgstr "" |
2717 | + |
2718 | +#: ../app/ui/EpisodesTab.qml:82 |
2719 | msgid "Favourites" |
2720 | msgstr "" |
2721 | |
2722 | -#: ../app/ui/EpisodesTab.qml:198 |
2723 | +#: ../app/ui/EpisodesTab.qml:141 |
2724 | +msgid "Back" |
2725 | +msgstr "" |
2726 | + |
2727 | +#: ../app/ui/EpisodesTab.qml:153 |
2728 | +msgid "Mark Listened" |
2729 | +msgstr "" |
2730 | + |
2731 | +#: ../app/ui/EpisodesTab.qml:171 |
2732 | +msgid "Download episode(s)" |
2733 | +msgstr "" |
2734 | + |
2735 | +#: ../app/ui/EpisodesTab.qml:199 |
2736 | +msgid "Delete episode(s)" |
2737 | +msgstr "" |
2738 | + |
2739 | +#: ../app/ui/EpisodesTab.qml:222 |
2740 | +msgid "Favourite episode(s)" |
2741 | +msgstr "" |
2742 | + |
2743 | +#: ../app/ui/EpisodesTab.qml:245 |
2744 | +msgid "Unfavourite episode(s)" |
2745 | +msgstr "" |
2746 | + |
2747 | +#: ../app/ui/EpisodesTab.qml:268 |
2748 | +msgid "Add to queue" |
2749 | +msgstr "" |
2750 | + |
2751 | +#: ../app/ui/EpisodesTab.qml:308 |
2752 | msgid "No New Episodes" |
2753 | msgstr "" |
2754 | |
2755 | -#: ../app/ui/EpisodesTab.qml:200 |
2756 | +#: ../app/ui/EpisodesTab.qml:310 |
2757 | msgid "No Downloaded Episodes" |
2758 | msgstr "" |
2759 | |
2760 | -#: ../app/ui/EpisodesTab.qml:202 |
2761 | +#: ../app/ui/EpisodesTab.qml:312 |
2762 | msgid "No Favourited Episodes" |
2763 | msgstr "" |
2764 | |
2765 | -#: ../app/ui/EpisodesTab.qml:204 |
2766 | +#: ../app/ui/EpisodesTab.qml:314 |
2767 | msgid "No Episodes Found" |
2768 | msgstr "" |
2769 | |
2770 | -#: ../app/ui/EpisodesTab.qml:210 |
2771 | +#: ../app/ui/EpisodesTab.qml:320 |
2772 | msgid "No more episodes to listen to!" |
2773 | msgstr "" |
2774 | |
2775 | -#: ../app/ui/EpisodesTab.qml:212 |
2776 | +#: ../app/ui/EpisodesTab.qml:322 |
2777 | msgid "No episodes have been downloaded for offline listening" |
2778 | msgstr "" |
2779 | |
2780 | -#: ../app/ui/EpisodesTab.qml:214 |
2781 | +#: ../app/ui/EpisodesTab.qml:324 |
2782 | msgid "No episodes have been favourited." |
2783 | msgstr "" |
2784 | |
2785 | -#: ../app/ui/EpisodesTab.qml:216 |
2786 | +#: ../app/ui/EpisodesTab.qml:326 |
2787 | msgid "No Episodes found matching the search term." |
2788 | msgstr "" |
2789 | |
2790 | -#: ../app/ui/EpisodesTab.qml:347 |
2791 | +#: ../app/ui/EpisodesTab.qml:455 |
2792 | +msgid "Downloads in progress" |
2793 | +msgstr "" |
2794 | + |
2795 | +#: ../app/ui/EpisodesTab.qml:495 |
2796 | +msgid "Downloaded episodes" |
2797 | +msgstr "" |
2798 | + |
2799 | +#: ../app/ui/EpisodesTab.qml:505 |
2800 | msgid "Today" |
2801 | msgstr "" |
2802 | |
2803 | -#: ../app/ui/EpisodesTab.qml:351 |
2804 | +#: ../app/ui/EpisodesTab.qml:509 |
2805 | msgid "Yesterday" |
2806 | msgstr "" |
2807 | |
2808 | -#: ../app/ui/EpisodesTab.qml:355 |
2809 | +#: ../app/ui/EpisodesTab.qml:513 |
2810 | msgid "Older" |
2811 | msgstr "" |
2812 | |
2813 | -#: ../app/ui/NowPlayingPage.qml:29 |
2814 | +#: ../app/ui/NowPlayingPage.qml:34 |
2815 | msgid "Now Playing" |
2816 | msgstr "" |
2817 | |
2818 | +#: ../app/ui/NowPlayingPage.qml:61 |
2819 | +msgid "Full view" |
2820 | +msgstr "" |
2821 | + |
2822 | +#: ../app/ui/NowPlayingPage.qml:61 |
2823 | +msgid "Queue" |
2824 | +msgstr "" |
2825 | + |
2826 | #. TRANSLATORS: The string shown in the UI is -15s to denote the number of seconds that the podcast playback will skip backward. |
2827 | -#: ../app/ui/NowPlayingPage.qml:200 |
2828 | +#: ../app/ui/NowPlayingPage.qml:261 |
2829 | #, no-c-format, qt-format |
2830 | msgid "-%1s" |
2831 | msgstr "" |
2832 | |
2833 | #. TRANSLATORS: The string shown in the UI is +15s to denote the number of seconds that the podcast playback will skip forward. |
2834 | -#: ../app/ui/NowPlayingPage.qml:261 |
2835 | +#: ../app/ui/NowPlayingPage.qml:322 |
2836 | #, no-c-format, qt-format |
2837 | msgid "+%1s" |
2838 | msgstr "" |
2839 | @@ -331,7 +361,7 @@ |
2840 | msgid "No podcasts found matching the search term." |
2841 | msgstr "" |
2842 | |
2843 | -#: ../app/ui/PodcastsTab.qml:174 ../app/ui/PodcastsTab.qml:221 |
2844 | +#: ../app/ui/PodcastsTab.qml:222 |
2845 | #, qt-format |
2846 | msgid "%1 unheard episode" |
2847 | msgid_plural "%1 unheard episodes" |
2848 | @@ -529,10 +559,10 @@ |
2849 | msgid "Finish" |
2850 | msgstr "" |
2851 | |
2852 | -#: /home/krnekhelesh/Development/devel-branch-sync-1-build/po/Podbird.desktop.in.h:1 |
2853 | +#: /home/krnekhelesh/Development/devel-branch-sync-2-build/po/Podbird.desktop.in.h:1 |
2854 | msgid "The chirpiest podcast manager for Ubuntu" |
2855 | msgstr "" |
2856 | |
2857 | -#: /home/krnekhelesh/Development/devel-branch-sync-1-build/po/Podbird.desktop.in.h:2 |
2858 | +#: /home/krnekhelesh/Development/devel-branch-sync-2-build/po/Podbird.desktop.in.h:2 |
2859 | msgid "podcast;audio;itunes;broadcast;digital;stream;podcatcher;video;vodcast;" |
2860 | msgstr "" |
Looks good