Merge lp:~nik90/podbird/add-queue-support into lp:podbird/devel

Proposed by Nekhelesh Ramananthan
Status: Merged
Merged at revision: 128
Proposed branch: lp:~nik90/podbird/add-queue-support
Merge into: lp:podbird/devel
Diff against target: 1283 lines (+696/-336)
9 files modified
app/podbird.qml (+119/-52)
app/podcasts.js (+99/-0)
app/ui/EpisodesPage.qml (+15/-17)
app/ui/EpisodesTab.qml (+15/-17)
app/ui/NowPlayingPage.qml (+330/-224)
app/ui/PlayerControls.qml (+6/-6)
app/ui/PodcastsTab.qml (+1/-1)
app/ui/Queue.qml (+94/-0)
po/podbird.nik90.pot (+17/-19)
To merge this branch: bzr merge lp:~nik90/podbird/add-queue-support
Reviewer Review Type Date Requested Status
Podbird Developers Pending
Review via email: mp+288875@code.launchpad.net

Description of the change

Added Queue Support

This however removes,
 - usermetrics since it won't be updated due to the app being in the background
 - playing from the previously saved position...again this won't work when the app is in the background.

To post a comment you must log in.
lp:~nik90/podbird/add-queue-support updated
138. By Nekhelesh Ramananthan

Removed unnecessary comments and getNextIndex() function

139. By Nekhelesh Ramananthan

Removed unnecessary js queue functions

140. By Nekhelesh Ramananthan

Fixed empty bottom player controls

141. By Nekhelesh Ramananthan

Another small fix

142. By Nekhelesh Ramananthan

Revert manifest framework change

143. By Nekhelesh Ramananthan

Small code style change

144. By Nekhelesh Ramananthan

Changed string fullview

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'app/podbird.qml'
2--- app/podbird.qml 2016-03-05 16:54:12 +0000
3+++ app/podbird.qml 2016-03-14 00:33:23 +0000
4@@ -19,7 +19,7 @@
5 import QtQuick 2.4
6 import Podbird 1.0
7 import UserMetrics 0.1
8-import QtMultimedia 5.4
9+import QtMultimedia 5.6
10 import Ubuntu.Connectivity 1.0
11 import Qt.labs.settings 1.0
12 import Ubuntu.Components 1.3
13@@ -50,6 +50,7 @@
14 db.transaction(function (tx) {
15 tx.executeSql('UPDATE Episode SET queued=0 WHERE queued=1');
16 })
17+ Podcasts.clearQueue()
18 }
19
20 // RefreshModel function to call refreshModel() function of the tab currently
21@@ -167,55 +168,121 @@
22 }
23 }
24
25- // UserMetrics to show Podbird stats on welcome screen
26- Metric {
27- id: podcastsMetric
28- name: "podcast-metrics"
29- // 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)
30- format: i18n.tr("Podcasts listened to today: <b>%1</b>")
31- emptyFormat: i18n.tr("No podcasts listened to today")
32- domain: "com.mikeasoft.podbird"
33- }
34-
35- // Load the media player only when the user starts to play some media. This
36- // should improve app-startup slightly.
37- Loader {
38- id: playerLoader
39- sourceComponent: currentUrl != "" ? playerComponent : undefined
40- }
41-
42- Component {
43- id: playerComponent
44- MediaPlayer {
45- id: player
46-
47- property bool podcastCounted: false
48-
49- source: currentUrl
50-
51- onSourceChanged: {
52- podcastCounted = false
53- }
54-
55- onPositionChanged: {
56- if (currentGuid == "" || duration <= 0) {
57- return;
58- }
59-
60- if (position > 10000 && !podcastCounted) {
61- podcastCounted = true
62- podcastsMetric.increment()
63- console.log("[LOG]: Podcast User metric incremented")
64- }
65-
66- var db = Podcasts.init();
67- db.transaction(function (tx) {
68- tx.executeSql("UPDATE Episode SET position=? WHERE guid=?", [position >= duration ? 120 : position, currentGuid]);
69- if (position >= duration - 120) {
70- tx.executeSql("UPDATE Episode SET listened = 1 WHERE guid=?", [currentGuid]);
71- }
72- });
73- }
74+ MediaPlayer {
75+ id: player
76+
77+ // Wrapper function around decodeURIComponent() to prevent exceptions
78+ // from bubbling up to the app.
79+ function decodeFileURI(filename)
80+ {
81+ var newFilename = "";
82+ try {
83+ newFilename = decodeURIComponent(filename);
84+ } catch (e) {
85+ newFilename = filename;
86+ console.log("Unicode decoding error:", filename, e.message)
87+ }
88+
89+ return newFilename;
90+ }
91+
92+ function metaForSource(source) {
93+ var blankMeta = {
94+ name: "",
95+ artist: "",
96+ image: "",
97+ guid: "",
98+ }
99+
100+ source = source.toString()
101+
102+ if (source.indexOf("file://") === 0) {
103+ source = source.substring(7);
104+ }
105+
106+ return Podcasts.lookup(decodeFileURI(source)) || blankMeta;
107+ }
108+
109+ function toggle() {
110+ if (playbackState === MediaPlayer.PlayingState) {
111+ pause()
112+ } else {
113+ play()
114+ }
115+ }
116+
117+ function playEpisode(guid, image, name, artist, url) {
118+ // Clear current queue
119+ player.playlist.clear()
120+ Podcasts.clearQueue()
121+
122+ // Add episode to queue
123+ player.playlist.addItem(Qt.resolvedUrl(url))
124+ Podcasts.addItemToQueue(guid, image, name, artist, url)
125+
126+ // Play episode
127+ player.play()
128+ }
129+
130+ function addEpisodeToQueue(guid, image, name, artist, url) {
131+ player.playlist.addItem(Qt.resolvedUrl(url))
132+ Podcasts.addItemToQueue(guid, image, name, artist, url)
133+
134+ // If added episode is the first one in the queue, then set the current metadata
135+ // so that the bottom player controls will be shown, allowing the user to play
136+ // the episode if he chooses to.
137+ if (player.playlist.itemCount === 0) {
138+ currentGuid = guid
139+ currentName = name
140+ currentArtist = artist
141+ currentImage = image
142+ currentUrl = url
143+ }
144+ }
145+
146+ property bool endOfMedia: false
147+ property double progress: 0
148+
149+ playlist: Playlist {
150+ playbackMode: Playlist.Sequential
151+
152+ readonly property bool canGoPrevious: currentIndex !== 0
153+ readonly property bool canGoNext: currentIndex !== itemCount - 1
154+
155+ onCurrentItemSourceChanged: {
156+ var meta = player.metaForSource(currentItemSource)
157+ currentGuid = "";
158+ currentName = meta.name
159+ currentArtist = meta.artist
160+ currentImage = meta.image
161+ currentGuid = meta.guid
162+ }
163+ }
164+
165+ onStatusChanged: {
166+ if (status === MediaPlayer.EndOfMedia) {
167+ console.log("[LOG]: End of Media. Stopping.")
168+ endOfMedia = true
169+ stop()
170+ }
171+ }
172+
173+ onStopped: {
174+ if (playlist.itemCount > 0) {
175+ if (endOfMedia) {
176+ // We just ended media, so jump to start of playlist
177+ playlist.currentIndex = 0;
178+
179+ // Play then pause otherwise when we come from EndOfMedia
180+ // it calls next() until EndOfMedia again.
181+ play()
182+ }
183+
184+ pause()
185+ }
186+
187+ // Always reset endOfMedia
188+ endOfMedia = false
189 }
190 }
191
192@@ -290,13 +357,13 @@
193 states: [
194 State {
195 name: "shown"
196- when: currentUrl != "" && !mainStack.currentPage.isNowPlayingPage
197+ when: player.playlist.itemCount !== 0 && !mainStack.currentPage.isNowPlayingPage
198 PropertyChanges { target: playerControlLoader; anchors.bottomMargin: 0 }
199 },
200
201 State {
202 name: "hidden"
203- when: currentUrl == "" || mainStack.currentPage.isNowPlayingPage || !playerControl.visible
204+ when: player.playlist.itemCount === 0 || mainStack.currentPage.isNowPlayingPage || !playerControl.visible
205 PropertyChanges { target: playerControlLoader; anchors.bottomMargin: -units.gu(7) }
206 }
207 ]
208
209=== modified file 'app/podcasts.js'
210--- app/podcasts.js 2016-03-05 20:10:55 +0000
211+++ app/podcasts.js 2016-03-14 00:33:23 +0000
212@@ -22,6 +22,7 @@
213 db.transaction(function(tx) {
214 tx.executeSql('CREATE TABLE IF NOT EXISTS Podcast(artist TEXT, name TEXT, description TEXT, feed TEXT, image TEXT, lastupdate TIMESTAMP)');
215 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))');
216+ tx.executeSql('CREATE TABLE IF NOT EXISTS Queue(ind INTEGER NOT NULL, guid TEXT, image TEXT, name TEXT, artist TEXT, url TEXT)');
217 });
218
219 /*
220@@ -39,6 +40,104 @@
221 return db;
222 }
223
224+// Function to add item to queue
225+function addItemToQueue(guid, image, name, artist, url) {
226+ var db = init()
227+
228+ db.transaction(function(tx) {
229+ var ind = getNextIndex(tx);
230+ var rs = tx.executeSql("INSERT OR REPLACE INTO Queue (ind, guid, image, name, artist, url) VALUES (?, ?, ?, ?, ?, ?)", [ind, guid, image, name, artist, url]);
231+ if (rs.rowsAffected > 0) {
232+ console.log("[LOG]: QUEUE add OK")
233+ } else {
234+ console.log("[LOG]: QUEUE add FAIL")
235+ }
236+ });
237+}
238+
239+function removeItemFromQueue(source) {
240+ var db = init()
241+
242+ db.transaction(function(tx) {
243+ // Remove selected source from the queue
244+ tx.executeSql("DELETE FROM Queue WHERE url = ?", source)
245+
246+ // Rebuild queue in order
247+ var rs = tx.executeSql("SELECT ind FROM Queue ORDER BY ind ASC")
248+
249+ for (var i=0; i<rs.rows.length; i++) {
250+ tx.executeSql("UPDATE Queue SET ind = ? WHERE ind = ?", [i, rs.rows.item(i).ind])
251+ }
252+ })
253+}
254+
255+// Function to clear the queue
256+function clearQueue() {
257+ var db = init();
258+ db.transaction(function(tx) {
259+ tx.executeSql("DELETE FROM Queue");
260+ });
261+}
262+
263+function lookup(source) {
264+ var db = init();
265+ var meta = {
266+ name: "",
267+ artist: "",
268+ image: "",
269+ guid: "",
270+ }
271+
272+ db.transaction(function(tx) {
273+ var rs = tx.executeSql("SELECT * FROM Queue ORDER BY ind ASC");
274+ for(var i = 0; i < rs.rows.length; i++) {
275+ var episode = rs.rows.item(i);
276+ if (source === episode.url) {
277+ meta.name = episode.name
278+ meta.artist = episode.artist
279+ meta.image = episode.image
280+ meta.guid = episode.guid
281+ break
282+ }
283+ }
284+ });
285+
286+ return meta
287+}
288+
289+// Function to get the next index for the queue
290+function getNextIndex(tx) {
291+ var ind;
292+
293+ if (tx === undefined) {
294+ var db = init();
295+ db.transaction(function(tx) {
296+ ind = getNextIndex(tx);
297+ });
298+ } else {
299+ var rs = tx.executeSql('SELECT MAX(ind) FROM Queue')
300+ ind = isQueueEmpty(tx) ? 0 : rs.rows.item(0)["MAX(ind)"] + 1
301+ }
302+
303+ return ind;
304+}
305+
306+function isQueueEmpty(tx) {
307+ var empty = false;
308+
309+ if (tx === undefined) {
310+ var db = init();
311+ db.transaction( function(tx) {
312+ empty = isQueueEmpty(tx)
313+ });
314+ } else {
315+ var rs = tx.executeSql("SELECT count(*) as value FROM Queue")
316+ empty = rs.rows.item(0).value === 0
317+ }
318+
319+ return empty
320+}
321+
322 function subscribe(artist, name, feed, img) {
323 var db = init();
324 db.transaction(function(tx) {
325
326=== modified file 'app/ui/EpisodesPage.qml'
327--- app/ui/EpisodesPage.qml 2016-03-06 23:00:20 +0000
328+++ app/ui/EpisodesPage.qml 2016-03-14 00:33:23 +0000
329@@ -17,7 +17,7 @@
330 */
331
332 import QtQuick 2.4
333-import QtMultimedia 5.4
334+import QtMultimedia 5.6
335 import Ubuntu.Components 1.3
336 import QtQuick.LocalStorage 2.0
337 import Ubuntu.DownloadManager 0.1
338@@ -443,8 +443,8 @@
339 id: listItemLayout
340
341 title.text: model.name !== undefined ? model.name.trim() : "Undefined"
342- title.color: currentGuid === model.guid || downloader.downloadingGuid === model.guid ? podbird.appTheme.focusText
343- : podbird.appTheme.baseText
344+ title.color: downloader.downloadingGuid === model.guid ? podbird.appTheme.focusText
345+ : podbird.appTheme.baseText
346 // #FIXME: Change this 2 to prevent title eliding when UITK is updated to rev > 1800
347 title.maximumLineCount: 1
348
349@@ -512,6 +512,14 @@
350 },
351
352 Action {
353+ iconName: "add-to-playlist"
354+ onTriggered: {
355+ var url = model.downloadedfile ? model.downloadedfile : model.audiourl
356+ player.addEpisodeToQueue(model.guid, model.image, model.name, model.artist, url)
357+ }
358+ },
359+
360+ Action {
361 iconName: model.favourited ? "unlike" : "like"
362 onTriggered: {
363 var db = Podcasts.init();
364@@ -537,20 +545,10 @@
365
366 onClicked: {
367 Haptics.play()
368- var db = Podcasts.init();
369- db.transaction(function (tx) {
370- if (currentGuid !== model.guid) {
371- currentGuid = "";
372- currentUrl = model.downloadedfile ? model.downloadedfile : model.audiourl;
373- var rs = tx.executeSql("SELECT position FROM Episode WHERE guid=?", [model.guid]);
374- playerLoader.item.play();
375- playerLoader.item.seek(rs.rows.item(0).position);
376- currentName = model.name;
377- currentArtist = model.artist;
378- currentImage = model.image;
379- currentGuid = model.guid;
380- }
381- });
382+ if (currentGuid !== model.guid) {
383+ currentUrl = model.downloadedfile ? model.downloadedfile : model.audiourl;
384+ player.playEpisode(model.guid, model.image, model.name, model.artist, currentUrl)
385+ }
386 }
387 }
388
389
390=== modified file 'app/ui/EpisodesTab.qml'
391--- app/ui/EpisodesTab.qml 2016-03-06 23:00:20 +0000
392+++ app/ui/EpisodesTab.qml 2016-03-14 00:33:23 +0000
393@@ -17,7 +17,7 @@
394 */
395
396 import QtQuick 2.4
397-import QtMultimedia 5.4
398+import QtMultimedia 5.6
399 import Ubuntu.Components 1.3
400 import QtQuick.LocalStorage 2.0
401 import Ubuntu.DownloadManager 0.1
402@@ -381,8 +381,8 @@
403 id: listItemLayout
404
405 title.text: model.name !== undefined ? model.name.trim() : "Undefined"
406- title.color: currentGuid === model.guid || downloader.downloadingGuid === model.guid ? podbird.appTheme.focusText
407- : podbird.appTheme.baseText
408+ title.color: downloader.downloadingGuid === model.guid ? podbird.appTheme.focusText
409+ : podbird.appTheme.baseText
410 // #FIXME: Change this 2 to prevent title eliding when UITK is updated to rev > 1800
411 title.maximumLineCount: 1
412
413@@ -466,6 +466,14 @@
414 },
415
416 Action {
417+ iconName: "add-to-playlist"
418+ onTriggered: {
419+ var url = model.downloadedfile ? model.downloadedfile : model.audiourl
420+ player.addEpisodeToQueue(model.guid, model.image, model.name, model.artist, url)
421+ }
422+ },
423+
424+ Action {
425 iconName: model.favourited ? "unlike" : "like"
426 onTriggered: {
427 var db = Podcasts.init();
428@@ -497,20 +505,10 @@
429
430 onClicked: {
431 Haptics.play()
432- var db = Podcasts.init();
433- db.transaction(function (tx) {
434- if (currentGuid !== model.guid) {
435- currentGuid = "";
436- currentUrl = model.downloadedfile ? model.downloadedfile : model.audiourl;
437- var rs = tx.executeSql("SELECT position FROM Episode WHERE guid=?", [model.guid]);
438- playerLoader.item.play();
439- playerLoader.item.seek(rs.rows.item(0).position);
440- currentName = model.name;
441- currentArtist = model.artist;
442- currentImage = model.image;
443- currentGuid = model.guid;
444- }
445- });
446+ if (currentGuid !== model.guid) {
447+ currentUrl = model.downloadedfile ? model.downloadedfile : model.audiourl;
448+ player.playEpisode(model.guid, model.image, model.name, model.artist, currentUrl)
449+ }
450 }
451 }
452
453
454=== modified file 'app/ui/NowPlayingPage.qml'
455--- app/ui/NowPlayingPage.qml 2016-03-04 10:40:54 +0000
456+++ app/ui/NowPlayingPage.qml 2016-03-14 00:33:23 +0000
457@@ -17,8 +17,9 @@
458 */
459
460 import QtQuick 2.4
461-import QtMultimedia 5.4
462+import QtMultimedia 5.6
463 import Ubuntu.Components 1.3
464+import QtQuick.LocalStorage 2.0
465 import "../podcasts.js" as Podcasts
466 import "../components"
467
468@@ -26,245 +27,350 @@
469 id: nowPlayingPage
470
471 visible: false
472- title: i18n.tr("Now Playing")
473
474 property bool isNowPlayingPage: true
475- property bool isLandscapeMode: width > height
476-
477- // Landscape rule
478- states: [
479- State {
480- name: "landscape"
481- when: isLandscapeMode
482-
483- PropertyChanges {
484- target: blurredBackground
485- width: parent.width/2.2
486- height: parent.height
487- }
488-
489- AnchorChanges {
490- target: blurredBackground
491+
492+ header: PageHeader {
493+ title: i18n.tr("Now Playing")
494+
495+ StyleHints {
496+ backgroundColor: podbird.appTheme.background
497+ }
498+
499+ trailingActionBar.actions: Action {
500+ iconName: "delete"
501+ visible: nowPlayingPageSections.selectedIndex === 1
502+ onTriggered: {
503+ Podcasts.clearQueue()
504+ player.playlist.clear()
505+ mainStack.pop()
506+ }
507+ }
508+
509+ extension: Sections {
510+ id: nowPlayingPageSections
511+
512+ anchors {
513+ left: parent.left
514+ leftMargin: units.gu(2)
515+ bottom: parent.bottom
516+ }
517+
518+ StyleHints {
519+ selectedSectionColor: podbird.appTheme.focusText
520+ }
521+ model: [i18n.tr("Full view"), i18n.tr("Queue")]
522+ }
523+ }
524+
525+ VisualItemModel {
526+ id: tabs
527+
528+ Item {
529+ id: nowPlayingItem
530+
531+ width: tabView.width
532+ height: tabView.height
533+
534+ property bool isLandscapeMode: nowPlayingPage.width > nowPlayingPage.height
535+
536+ // Landscape rule
537+ states: [
538+ State {
539+ name: "landscape"
540+ when: nowPlayingItem.isLandscapeMode
541+
542+ PropertyChanges {
543+ target: blurredBackground
544+ width: nowPlayingPage.width/2.2
545+ height: nowPlayingPage.height
546+ }
547+
548+ AnchorChanges {
549+ target: blurredBackground
550+ anchors {
551+ top: nowPlayingItem.top
552+ left: parent.left
553+ right: undefined
554+ }
555+ }
556+
557+ AnchorChanges {
558+ target: dataContainer
559+ anchors {
560+ top: nowPlayingItem.top
561+ left: blurredBackground.right
562+ right: parent.right
563+ bottom: parent.bottom
564+ }
565+ }
566+ }
567+ ]
568+
569+ BlurredBackground {
570+ id: blurredBackground
571+
572+ anchors.left: parent.left
573+ anchors.top: nowPlayingItem.top
574+ anchors.right: parent.right
575+ height: title.lineCount === 1 ? nowPlayingPage.height/2.3 + units.gu(3)
576+ : nowPlayingPage.height/2.3
577+ art: currentImage
578+
579+ Image {
580+ width: Math.min(nowPlayingPage.width/2, nowPlayingPage.height/2)
581+ height: width
582+ sourceSize.height: width
583+ sourceSize.width: width
584+ source: currentImage
585+ asynchronous: true
586+ anchors.centerIn: parent
587+ }
588+ }
589+
590+ Item {
591+ id: dataContainer
592+
593 anchors {
594- top: parent.top
595+ top: blurredBackground.bottom
596 left: parent.left
597- right: undefined
598- }
599- }
600-
601- AnchorChanges {
602- target: dataContainer
603- anchors {
604- top: parent.top
605- left: blurredBackground.right
606 right: parent.right
607 bottom: parent.bottom
608+ margins: units.gu(2)
609+ bottomMargin: nowPlayingItem.isLandscapeMode ? units.gu(4) : units.gu(2)
610+ }
611+
612+ Label {
613+ id: title
614+ anchors.left: parent.left
615+ anchors.right: parent.right
616+ anchors.top: parent.top
617+ text: currentName
618+ elide: Text.ElideRight
619+ textSize: Label.Large
620+ maximumLineCount: 2
621+ wrapMode: Text.WordWrap
622+ color: podbird.appTheme.baseText
623+ }
624+
625+ Label {
626+ id: artist
627+ anchors.left: title.left
628+ anchors.right: title.right
629+ anchors.top: title.bottom
630+ anchors.topMargin: units.gu(1)
631+ text: currentArtist
632+ elide: Text.ElideRight
633+ textSize: Label.Small
634+ color: podbird.appTheme.baseSubText
635+ }
636+
637+ Slider {
638+ id: scrubber
639+
640+ anchors {
641+ left: parent.left
642+ right: parent.right
643+ bottom: controls.top
644+ bottomMargin: nowPlayingItem.isLandscapeMode && title.lineCount < 2 ? units.gu(4) : units.gu(2)
645+ }
646+
647+ live: true
648+ minimumValue: 0
649+ maximumValue: player.duration
650+ value: player.position
651+ height: units.gu(2)
652+
653+ onValueChanged: {
654+ if (pressed) {
655+ player.seek(value);
656+ }
657+ }
658+
659+ function formatValue(v) { return Podcasts.formatTime(v/1000); }
660+ StyleHints { foregroundColor: podbird.appTheme.focusText }
661+ }
662+
663+ Connections {
664+ target: player
665+ onPositionChanged: scrubber.value = player.position
666+ }
667+
668+ Label {
669+ id: startTime
670+ textSize: Label.Small
671+ anchors.left: scrubber.left
672+ anchors.top: scrubber.bottom
673+ color: podbird.appTheme.baseText
674+ text: Podcasts.formatTime(player.position / 1000)
675+ }
676+
677+ Label {
678+ id: endTime
679+ textSize: Label.Small
680+ anchors.right: scrubber.right
681+ anchors.top: scrubber.bottom
682+ color: podbird.appTheme.baseText
683+ text: Podcasts.formatTime(player.duration / 1000)
684+ }
685+
686+ Row {
687+ id: controls
688+
689+ anchors.bottom: parent.bottom
690+ anchors.horizontalCenter: parent.horizontalCenter
691+ spacing: units.gu(1)
692+
693+ AbstractButton {
694+ id: mediaBackwardButton
695+ width: units.gu(6)
696+ height: width
697+ anchors.verticalCenter: parent.verticalCenter
698+ enabled: player.playlist.canGoPrevious
699+ opacity: enabled ? 1.0 : 0.4
700+ onClicked: player.playlist.previous()
701+
702+ Icon {
703+ id: mediaBackwardIcon
704+ width: units.gu(3)
705+ height: width
706+ anchors.centerIn: parent
707+ color: podbird.appTheme.baseIcon
708+ name: "media-skip-backward"
709+ }
710+ }
711+
712+ AbstractButton {
713+ id: skipBackwardButton
714+ width: units.gu(6)
715+ height: width
716+ anchors.verticalCenter: parent.verticalCenter
717+ opacity: player.position === 0 ? 0.4 : 1.0
718+ onClicked: {
719+ if (player.position > 0) {
720+ player.seek(player.position - podbird.settings.skipBack * 1000);
721+ }
722+ }
723+
724+ Row {
725+ spacing: units.gu(1)
726+ anchors.centerIn: parent
727+
728+ Label {
729+ // TRANSLATORS: The string shown in the UI is -15s to denote the number of seconds that the podcast playback will skip backward.
730+ // xgettext: no-c-format
731+ text: i18n.tr("-%1s").arg(podbird.settings.skipBack)
732+ textSize: Label.XxSmall
733+ color: podbird.appTheme.baseText
734+ anchors.verticalCenter: skipBackwardIcon.verticalCenter
735+ }
736+
737+ Icon {
738+ id: skipBackwardIcon
739+ width: units.gu(3)
740+ height: width
741+ name: "media-seek-backward"
742+ color: podbird.appTheme.baseIcon
743+ }
744+ }
745+ }
746+
747+ AbstractButton {
748+ id: playButton
749+ width: units.gu(10)
750+ height: width
751+ opacity: playButton.pressed ? 0.4 : 1.0
752+ onClicked: player.playbackState === MediaPlayer.PlayingState ? player.pause() : player.play()
753+
754+ Icon {
755+ id: playIcon
756+ width: units.gu(6)
757+ height: width
758+ anchors.centerIn: parent
759+ color: podbird.appTheme.baseIcon
760+ name: player.playbackState === MediaPlayer.PlayingState ? "media-playback-pause"
761+ : "media-playback-start"
762+ }
763+ }
764+
765+ AbstractButton {
766+ id: skipForwardButton
767+ width: units.gu(6)
768+ height: width
769+ anchors.verticalCenter: parent.verticalCenter
770+ opacity: player.position === 0 ? 0.4 : 1.0
771+ onClicked: {
772+ if (player.position > 0) {
773+ player.seek(player.position + podbird.settings.skipForward * 1000);
774+ }
775+ }
776+
777+ Row {
778+ spacing: units.gu(1)
779+ anchors.centerIn: parent
780+
781+ Icon {
782+ id: skipForwardIcon
783+ width: units.gu(3)
784+ height: width
785+ name: "media-seek-forward"
786+ color: podbird.appTheme.baseIcon
787+ }
788+
789+ Label {
790+ // TRANSLATORS: The string shown in the UI is +15s to denote the number of seconds that the podcast playback will skip forward.
791+ // xgettext: no-c-format
792+ text: i18n.tr("+%1s").arg(podbird.settings.skipForward)
793+ textSize: Label.XxSmall
794+ color: podbird.appTheme.baseText
795+ anchors.verticalCenter: skipForwardIcon.verticalCenter
796+ }
797+ }
798+ }
799+
800+ AbstractButton {
801+ id: mediaForwardButton
802+ width: units.gu(6)
803+ height: width
804+ anchors.verticalCenter: parent.verticalCenter
805+ enabled: player.playlist.canGoNext
806+ opacity: enabled ? 1.0 : 0.4
807+ onClicked: player.playlist.next()
808+
809+ Icon {
810+ id: mediaForwardIcon
811+ width: units.gu(3)
812+ height: width
813+ anchors.centerIn: parent
814+ color: podbird.appTheme.baseIcon
815+ name: "media-skip-forward"
816+ }
817+ }
818 }
819 }
820 }
821- ]
822-
823- BlurredBackground {
824- id: blurredBackground
825-
826- anchors.left: parent.left
827- anchors.top: parent.top
828- anchors.right: parent.right
829- height: title.lineCount === 1 ? parent.height/2 + units.gu(3)
830- : parent.height/2
831- art: currentImage
832-
833- Image {
834- width: Math.min(parent.width/2, parent.height)
835- height: width
836- sourceSize.height: width
837- sourceSize.width: width
838- source: currentImage
839- asynchronous: true
840- anchors.centerIn: parent
841+
842+ Queue {
843+ width: tabView.width
844+ height: tabView.height
845 }
846 }
847
848- Item {
849- id: dataContainer
850+ ListView {
851+ id: tabView
852+ model: tabs
853+ interactive: false
854
855 anchors {
856- top: blurredBackground.bottom
857+ top: nowPlayingPage.header.bottom
858 left: parent.left
859 right: parent.right
860 bottom: parent.bottom
861- margins: units.gu(2)
862- bottomMargin: isLandscapeMode ? units.gu(4) : units.gu(2)
863- }
864-
865- Label {
866- id: title
867- anchors.left: parent.left
868- anchors.right: parent.right
869- anchors.top: parent.top
870- text: currentName
871- elide: Text.ElideRight
872- textSize: Label.Large
873- maximumLineCount: 2
874- wrapMode: Text.WordWrap
875- color: podbird.appTheme.baseText
876- }
877-
878- Label {
879- id: artist
880- anchors.left: title.left
881- anchors.right: title.right
882- anchors.top: title.bottom
883- anchors.topMargin: units.gu(1)
884- text: currentArtist
885- elide: Text.ElideRight
886- textSize: Label.Small
887- color: podbird.appTheme.baseSubText
888- }
889-
890- Slider {
891- id: scrubber
892-
893- anchors {
894- left: parent.left
895- right: parent.right
896- bottom: controls.top
897- bottomMargin: isLandscapeMode && title.lineCount < 2 ? units.gu(4) : units.gu(2)
898- }
899-
900- live: true
901- minimumValue: 0
902- maximumValue: playerLoader.item.duration
903- value: playerLoader.item.position
904- height: units.gu(2)
905-
906- onValueChanged: {
907- if (pressed) {
908- playerLoader.item.seek(value);
909- }
910- }
911-
912- function formatValue(v) { return Podcasts.formatTime(v/1000); }
913- StyleHints { foregroundColor: podbird.appTheme.focusText }
914- }
915-
916- Connections {
917- target: playerLoader.item
918- onPositionChanged: scrubber.value = playerLoader.item.position
919- }
920-
921- Label {
922- id: startTime
923- textSize: Label.Small
924- anchors.left: scrubber.left
925- anchors.top: scrubber.bottom
926- color: podbird.appTheme.baseText
927- text: Podcasts.formatTime(playerLoader.item.position / 1000)
928- }
929-
930- Label {
931- id: endTime
932- textSize: Label.Small
933- anchors.right: scrubber.right
934- anchors.top: scrubber.bottom
935- color: podbird.appTheme.baseText
936- text: Podcasts.formatTime(playerLoader.item.duration / 1000)
937- }
938-
939- Row {
940- id: controls
941-
942- anchors.bottom: parent.bottom
943- anchors.horizontalCenter: parent.horizontalCenter
944- spacing: units.gu(2)
945-
946- AbstractButton {
947- id: skipBackwardButton
948- width: units.gu(6)
949- height: width
950- anchors.verticalCenter: parent.verticalCenter
951- opacity: playerLoader.item.position === 0 ? 0.4 : 1.0
952- onClicked: {
953- if (playerLoader.item.position > 0) {
954- playerLoader.item.seek(playerLoader.item.position - podbird.settings.skipBack * 1000);
955- }
956- }
957-
958- Row {
959- spacing: units.gu(1)
960- anchors.centerIn: parent
961-
962- Label {
963- // TRANSLATORS: The string shown in the UI is -15s to denote the number of seconds that the podcast playback will skip backward.
964- // xgettext: no-c-format
965- text: i18n.tr("-%1s").arg(podbird.settings.skipBack)
966- textSize: Label.XxSmall
967- color: podbird.appTheme.baseText
968- anchors.verticalCenter: skipBackwardIcon.verticalCenter
969- }
970-
971- Icon {
972- id: skipBackwardIcon
973- width: units.gu(3)
974- height: width
975- name: "media-seek-backward"
976- color: podbird.appTheme.baseIcon
977- }
978- }
979- }
980-
981- AbstractButton {
982- id: playButton
983- width: units.gu(10)
984- height: width
985- opacity: playButton.pressed ? 0.4 : 1.0
986- onClicked: playerLoader.item.playbackState === MediaPlayer.PlayingState ? playerLoader.item.pause() : playerLoader.item.play()
987-
988- Icon {
989- id: playIcon
990- width: units.gu(6)
991- height: width
992- anchors.centerIn: parent
993- color: podbird.appTheme.baseIcon
994- name: playerLoader.item.playbackState === MediaPlayer.PlayingState ? "media-playback-pause"
995- : "media-playback-start"
996- }
997- }
998-
999- AbstractButton {
1000- id: skipForwardButton
1001- width: units.gu(6)
1002- height: width
1003- anchors.verticalCenter: parent.verticalCenter
1004- opacity: playerLoader.item.position === 0 ? 0.4 : 1.0
1005- onClicked: {
1006- if (playerLoader.item.position > 0) {
1007- playerLoader.item.seek(playerLoader.item.position + podbird.settings.skipForward * 1000);
1008- }
1009- }
1010-
1011- Row {
1012- spacing: units.gu(1)
1013- anchors.centerIn: parent
1014-
1015- Icon {
1016- id: skipForwardIcon
1017- width: units.gu(3)
1018- height: width
1019- name: "media-seek-forward"
1020- color: podbird.appTheme.baseIcon
1021- }
1022-
1023- Label {
1024- // TRANSLATORS: The string shown in the UI is +15s to denote the number of seconds that the podcast playback will skip forward.
1025- // xgettext: no-c-format
1026- text: i18n.tr("+%1s").arg(podbird.settings.skipForward)
1027- textSize: Label.XxSmall
1028- color: podbird.appTheme.baseText
1029- anchors.verticalCenter: skipForwardIcon.verticalCenter
1030- }
1031- }
1032- }
1033- }
1034+ }
1035+
1036+ orientation: Qt.Horizontal
1037+ snapMode: ListView.SnapOneItem
1038+ currentIndex: nowPlayingPageSections.selectedIndex
1039+ highlightMoveDuration: UbuntuAnimation.SlowDuration
1040 }
1041 }
1042
1043=== modified file 'app/ui/PlayerControls.qml'
1044--- app/ui/PlayerControls.qml 2016-03-04 10:40:54 +0000
1045+++ app/ui/PlayerControls.qml 2016-03-14 00:33:23 +0000
1046@@ -17,7 +17,7 @@
1047 */
1048
1049 import QtQuick 2.4
1050-import QtMultimedia 5.4
1051+import QtMultimedia 5.6
1052 import Ubuntu.Components 1.3
1053
1054 Rectangle {
1055@@ -49,7 +49,7 @@
1056 anchors.top: cover.bottom
1057 color: podbird.appTheme.focusText
1058 height: units.gu(0.25)
1059- width: playerLoader.item.duration > 0 ? (playerLoader.item.position / playerLoader.item.duration) * parent.width : 0
1060+ width: player.duration > 0 ? (player.position / player.duration) * parent.width : 0
1061 }
1062
1063 Column {
1064@@ -97,16 +97,16 @@
1065 visible: playButton.pressed
1066 }
1067
1068- onClicked: playerLoader.item.playbackState === MediaPlayer.PlayingState ? playerLoader.item.pause()
1069- : playerLoader.item.play()
1070+ onClicked: player.playbackState === MediaPlayer.PlayingState ? player.pause()
1071+ : player.play()
1072
1073 Icon {
1074 color: "white"
1075 width: units.gu(3)
1076 height: width
1077 anchors.centerIn: playButtonBackground
1078- name: playerLoader.item.playbackState === MediaPlayer.PlayingState ? "media-playback-pause"
1079- : "media-playback-start"
1080+ name: player.playbackState === MediaPlayer.PlayingState ? "media-playback-pause"
1081+ : "media-playback-start"
1082 opacity: playButton.pressed ? 0.4 : 1.0
1083 }
1084 }
1085
1086=== modified file 'app/ui/PodcastsTab.qml'
1087--- app/ui/PodcastsTab.qml 2016-03-06 23:00:20 +0000
1088+++ app/ui/PodcastsTab.qml 2016-03-14 00:33:23 +0000
1089@@ -17,7 +17,7 @@
1090 */
1091
1092 import QtQuick 2.4
1093-import QtMultimedia 5.4
1094+import QtMultimedia 5.6
1095 import QtQuick.LocalStorage 2.0
1096 import Ubuntu.Components 1.3
1097 import Ubuntu.DownloadManager 0.1
1098
1099=== added file 'app/ui/Queue.qml'
1100--- app/ui/Queue.qml 1970-01-01 00:00:00 +0000
1101+++ app/ui/Queue.qml 2016-03-14 00:33:23 +0000
1102@@ -0,0 +1,94 @@
1103+/*
1104+ * Copyright 2016 Podbird Team
1105+ *
1106+ * This file is part of Podbird.
1107+ *
1108+ * Podbird is free software; you can redistribute it and/or modify
1109+ * it under the terms of the GNU General Public License as published by
1110+ * the Free Software Foundation; version 3.
1111+ *
1112+ * Podbird is distributed in the hope that it will be useful,
1113+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1114+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1115+ * GNU General Public License for more details.
1116+ *
1117+ * You should have received a copy of the GNU General Public License
1118+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1119+ */
1120+
1121+import QtQuick 2.4
1122+import Ubuntu.Components 1.3
1123+import "../podcasts.js" as Podcasts
1124+import "../components"
1125+
1126+Item {
1127+ id: queuePage
1128+
1129+ ListView {
1130+ id: queueList
1131+
1132+ anchors.fill: parent
1133+ model: player.playlist
1134+
1135+ delegate: ListItem {
1136+ id: listItem
1137+
1138+ height: layout.height
1139+ divider.visible: false
1140+
1141+ ListItemLayout {
1142+ id: layout
1143+
1144+ // Grab the metaData for the current index using its unique source url
1145+ property var metaModel: player.metaForSource(model.source)
1146+
1147+ Image {
1148+ id: imgFrame
1149+ width: units.gu(6)
1150+ height: width
1151+ source: Qt.resolvedUrl(layout.metaModel.image)
1152+ sourceSize.height: width
1153+ sourceSize.width: width
1154+ SlotsLayout.position: SlotsLayout.First
1155+ }
1156+
1157+ title.text: layout.metaModel.name
1158+ // #FIXME: Change this 2 to prevent title eliding when UITK is updated to rev > 1800
1159+ title.maximumLineCount: 1
1160+ title.color: player.playlist.currentIndex === index ? podbird.appTheme.focusText
1161+ : podbird.appTheme.baseText
1162+
1163+ subtitle.text: layout.metaModel.artist
1164+ subtitle.color: podbird.appTheme.baseSubText
1165+ }
1166+
1167+ leadingActions: ListItemActions {
1168+ actions: [
1169+ Action {
1170+ iconName: "delete"
1171+ onTriggered: {
1172+ player.playlist.removeItem(index)
1173+ var source = model.source
1174+
1175+ source = source.toString()
1176+
1177+ if (source.indexOf("file://") === 0) {
1178+ source = source.substring(7);
1179+ }
1180+
1181+ Podcasts.removeItemFromQueue(source)
1182+ }
1183+ }
1184+ ]
1185+ }
1186+
1187+ onClicked: {
1188+ if (player.playlist.currentIndex === index) {
1189+ player.toggle()
1190+ } else {
1191+ player.playlist.currentIndex = index
1192+ }
1193+ }
1194+ }
1195+ }
1196+}
1197
1198=== modified file 'po/podbird.nik90.pot'
1199--- po/podbird.nik90.pot 2016-03-06 23:40:40 +0000
1200+++ po/podbird.nik90.pot 2016-03-14 00:33:23 +0000
1201@@ -8,7 +8,7 @@
1202 msgstr ""
1203 "Project-Id-Version: \n"
1204 "Report-Msgid-Bugs-To: \n"
1205-"POT-Creation-Date: 2016-03-07 05:06+0530\n"
1206+"POT-Creation-Date: 2016-03-14 06:03+0530\n"
1207 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
1208 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
1209 "Language-Team: LANGUAGE <LL@li.org>\n"
1210@@ -34,27 +34,17 @@
1211 msgid "Skip"
1212 msgstr ""
1213
1214-#. 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)
1215-#: ../app/podbird.qml:175
1216-#, qt-format
1217-msgid "Podcasts listened to today: <b>%1</b>"
1218-msgstr ""
1219-
1220-#: ../app/podbird.qml:176
1221-msgid "No podcasts listened to today"
1222-msgstr ""
1223-
1224-#: ../app/podcasts.js:182
1225+#: ../app/podcasts.js:281
1226 #, no-c-format, qt-format
1227 msgid "%1 hr %2 min"
1228 msgstr ""
1229
1230-#: ../app/podcasts.js:191
1231+#: ../app/podcasts.js:290
1232 #, no-c-format, qt-format
1233 msgid "%1 hr"
1234 msgstr ""
1235
1236-#: ../app/podcasts.js:199
1237+#: ../app/podcasts.js:298
1238 #, no-c-format, qt-format
1239 msgid "%1 min"
1240 msgstr ""
1241@@ -284,18 +274,26 @@
1242 msgid "Older"
1243 msgstr ""
1244
1245-#: ../app/ui/NowPlayingPage.qml:29
1246+#: ../app/ui/NowPlayingPage.qml:34
1247 msgid "Now Playing"
1248 msgstr ""
1249
1250+#: ../app/ui/NowPlayingPage.qml:62
1251+msgid "Full view"
1252+msgstr ""
1253+
1254+#: ../app/ui/NowPlayingPage.qml:62
1255+msgid "Queue"
1256+msgstr ""
1257+
1258 #. TRANSLATORS: The string shown in the UI is -15s to denote the number of seconds that the podcast playback will skip backward.
1259-#: ../app/ui/NowPlayingPage.qml:200
1260+#: ../app/ui/NowPlayingPage.qml:262
1261 #, no-c-format, qt-format
1262 msgid "-%1s"
1263 msgstr ""
1264
1265 #. TRANSLATORS: The string shown in the UI is +15s to denote the number of seconds that the podcast playback will skip forward.
1266-#: ../app/ui/NowPlayingPage.qml:261
1267+#: ../app/ui/NowPlayingPage.qml:323
1268 #, no-c-format, qt-format
1269 msgid "+%1s"
1270 msgstr ""
1271@@ -529,10 +527,10 @@
1272 msgid "Finish"
1273 msgstr ""
1274
1275-#: /home/krnekhelesh/Development/devel-trunk-untouched-build/po/Podbird.desktop.in.h:1
1276+#: /home/krnekhelesh/Development/add-playlist-support-build/po/Podbird.desktop.in.h:1
1277 msgid "The chirpiest podcast manager for Ubuntu"
1278 msgstr ""
1279
1280-#: /home/krnekhelesh/Development/devel-trunk-untouched-build/po/Podbird.desktop.in.h:2
1281+#: /home/krnekhelesh/Development/add-playlist-support-build/po/Podbird.desktop.in.h:2
1282 msgid "podcast;audio;itunes;broadcast;digital;stream;podcatcher;video;vodcast;"
1283 msgstr ""

Subscribers

People subscribed via source and target branches