@@ -17,10 +17,10 @@
1717 */
1818
1919import QtQuick 2.4
20−import QtMultimedia 5.4
20+import QtMultimedia 5.6
2121import Ubuntu.Components 1.3
2222import QtQuick.LocalStorage 2.0
23−import Ubuntu.DownloadManager 0.1
23+import Ubuntu.DownloadManager 1.2
2424import Ubuntu.Components.Popups 1.3
2525import "../podcasts.js" as Podcasts
2626import "../components"
@@ -64,58 +64,6 @@ Tab {
6464 episodesPage.header = searchHeader
6565 searchField.item.forceActiveFocus()
6666 }
67− },
68−
69− Action {
70− iconName: "select"
71− visible: episodesPageHeaderSections.selectedIndex === 0
72− text: i18n.tr("Mark all listened")
73− onTriggered: {
74− var db = Podcasts.init();
75− db.transaction(function (tx) {
76− for (var i=0; i<episodesModel.count; i++) {
77− tx.executeSql("UPDATE Episode SET listened=1 WHERE guid=?", [episodesModel.get(i).guid]);
78− }
79− episodesModel.clear()
80− });
81− }
82− },
83−
84− Action {
85− iconName: "save"
86− visible: episodesPageHeaderSections.selectedIndex === 0
87− text: i18n.tr("Download all")
88− onTriggered: {
89− var db = Podcasts.init();
90− db.transaction(function (tx) {
91− for (var i=0; i<episodesModel.count; i++) {
92− if (!episodesModel.get(i).downloadedfile) {
93− episodesModel.setProperty(i, "queued", 1)
94− tx.executeSql("UPDATE Episode SET queued=1 WHERE guid = ?", [episodesModel.get(i).guid]);
95− downloader.addDownload(episodesModel.get(i).guid, episodesModel.get(i).audiourl);
96− }
97− }
98− });
99− }
100− },
101−
102− Action {
103− iconName: "delete"
104− text: i18n.tr("Delete all")
105− visible: episodesPageHeaderSections.selectedIndex === 1
106− onTriggered: {
107− var db = Podcasts.init();
108− db.transaction(function (tx) {
109− for (var i=0; i<episodesModel.count; i++) {
110− if (episodesModel.get(i).downloadedfile) {
111− fileManager.deleteFile(episodesModel.get(i).downloadedfile);
112− tx.executeSql("UPDATE Episode SET downloadedfile = NULL WHERE guid = ?", [episodesModel.get(i).guid]);
113− episodesModel.setProperty(i, "downloadedfile", "")
114− }
115− }
116− });
117− refreshModel();
118− }
11967 }
12068 ]
12169
@@ -124,7 +72,6 @@ Tab {
12472
12573 anchors {
12674 left: parent.left
127− leftMargin: units.gu(2)
12875 bottom: parent.bottom
12976 }
13077
@@ -132,7 +79,7 @@ Tab {
13279 selectedSectionColor: podbird.appTheme.focusText
13380 }
13481
135− model: [i18n.tr("Recent"), i18n.tr("Downloaded"), i18n.tr("Favourites")]
82+ model: [i18n.tr("Recent"), i18n.tr("Downloads"), i18n.tr("Favourites")]
13683 onSelectedIndexChanged: {
13784 refreshModel();
13885 }
@@ -174,6 +121,169 @@ Tab {
174121 }
175122 }
176123
124+ PageHeader {
125+ id: selectionHeader
126+ visible: episodeList.ViewItems.selectMode
127+
128+ onVisibleChanged: {
129+ if (visible) {
130+ episodesPage.header = selectionHeader
131+ }
132+ }
133+
134+ StyleHints {
135+ backgroundColor: podbird.appTheme.background
136+ }
137+
138+ leadingActionBar.actions: [
139+ Action {
140+ iconName: "back"
141+ text: i18n.tr("Back")
142+ onTriggered: {
143+ episodeList.closeSelection()
144+ }
145+ }
146+ ]
147+
148+ trailingActionBar {
149+ numberOfSlots: 6
150+ actions: [
151+ Action {
152+ iconName: "select"
153+ text: i18n.tr("Mark Listened")
154+ enabled: episodeList.ViewItems.selectedIndices.length !== 0
155+ onTriggered: {
156+ var db = Podcasts.init();
157+ db.transaction(function (tx) {
158+ for (var i=0; i<episodeList.ViewItems.selectedIndices.length; i++) {
159+ var index = episodeList.ViewItems.selectedIndices[i]
160+ tx.executeSql("UPDATE Episode SET listened=1 WHERE guid=?", [episodesModel.get(index).guid]);
161+ }
162+ });
163+
164+ refreshModel();
165+ episodeList.closeSelection()
166+ }
167+ },
168+
169+ Action {
170+ iconName: "save"
171+ text: i18n.tr("Download episode(s)")
172+ enabled: episodeList.ViewItems.selectedIndices.length !== 0
173+ visible: episodesPageHeaderSections.selectedIndex !== 1
174+
175+ onTriggered: {
176+ var db = Podcasts.init();
177+ db.transaction(function (tx) {
178+ for (var i=0; i<episodeList.ViewItems.selectedIndices.length; i++) {
179+ var index = episodeList.ViewItems.selectedIndices[i]
180+ if (!episodesModel.get(index).downloadedfile) {
181+ episodesModel.setProperty(index, "queued", 1)
182+ tx.executeSql("UPDATE Episode SET queued=1 WHERE guid = ?", [episodesModel.get(index).guid]);
183+ if (episodesModel.get(index).audiourl) {
184+ podbird.downloadEpisode(episodesModel.get(index).image, episodesModel.get(index).name, episodesModel.get(index).guid, episodesModel.get(index).audiourl)
185+ } else {
186+ console.log("[ERROR]: Invalid download url: " + episodesModel.get(index).audiourl)
187+ }
188+ }
189+ }
190+ });
191+
192+ refreshModel();
193+ episodeList.closeSelection()
194+ }
195+ },
196+
197+ Action {
198+ iconName: "delete"
199+ text: i18n.tr("Delete episode(s)")
200+ enabled: episodeList.ViewItems.selectedIndices.length !== 0
201+
202+ onTriggered: {
203+ var db = Podcasts.init();
204+ db.transaction(function (tx) {
205+ for (var i=0; i<episodeList.ViewItems.selectedIndices.length; i++) {
206+ var index = episodeList.ViewItems.selectedIndices[i]
207+ if (episodesModel.get(index).downloadedfile) {
208+ fileManager.deleteFile(episodesModel.get(index).downloadedfile);
209+ tx.executeSql("UPDATE Episode SET downloadedfile = NULL WHERE guid = ?", [episodesModel.get(index).guid]);
210+ episodesModel.setProperty(index, "downloadedfile", "")
211+ }
212+ }
213+ });
214+
215+ refreshModel();
216+ episodeList.closeSelection()
217+ }
218+ },
219+
220+ Action {
221+ iconName: "like"
222+ text: i18n.tr("Favourite episode(s)")
223+ visible: episodesPageHeaderSections.selectedIndex !== 2
224+ enabled: episodeList.ViewItems.selectedIndices.length !== 0
225+
226+ onTriggered: {
227+ var db = Podcasts.init();
228+ db.transaction(function (tx) {
229+ for (var i=0; i<episodeList.ViewItems.selectedIndices.length; i++) {
230+ var index = episodeList.ViewItems.selectedIndices[i]
231+ if (!episodesModel.get(index).favourited) {
232+ tx.executeSql("UPDATE Episode SET favourited=1 WHERE guid=?", [episodesModel.get(index).guid])
233+ episodesModel.setProperty(index, "favourited", 1)
234+ }
235+ }
236+ });
237+
238+ refreshModel();
239+ episodeList.closeSelection()
240+ }
241+ },
242+
243+ Action {
244+ iconName: "unlike"
245+ text: i18n.tr("Unfavourite episode(s)")
246+ visible: episodesPageHeaderSections.selectedIndex === 2
247+ enabled: episodeList.ViewItems.selectedIndices.length !== 0
248+
249+ onTriggered: {
250+ var db = Podcasts.init();
251+ db.transaction(function (tx) {
252+ for (var i=0; i<episodeList.ViewItems.selectedIndices.length; i++) {
253+ var index = episodeList.ViewItems.selectedIndices[i]
254+ if (episodesModel.get(index).favourited) {
255+ tx.executeSql("UPDATE Episode SET favourited=0 WHERE guid=?", [episodesModel.get(index).guid])
256+ episodesModel.setProperty(index, "favourited", 0)
257+ }
258+ }
259+ });
260+
261+ refreshModel();
262+ episodeList.closeSelection()
263+ }
264+ },
265+
266+ Action {
267+ iconName: "add-to-playlist"
268+ text: i18n.tr("Add to queue")
269+ enabled: episodeList.ViewItems.selectedIndices.length !== 0
270+
271+ onTriggered: {
272+ for (var i=0; i<episodeList.ViewItems.selectedIndices.length; i++) {
273+ var index = episodeList.ViewItems.selectedIndices[i]
274+ if (episodesModel.get(index).audiourl) {
275+ var url = episodesModel.get(index).downloadedfile ? "file://" + episodesModel.get(index).downloadedfile : episodesModel.get(index).audiourl
276+ player.addEpisodeToQueue(episodesModel.get(index).guid, episodesModel.get(index).image, episodesModel.get(index).name, episodesModel.get(index).artist, url)
277+ }
278+ }
279+
280+ episodeList.closeSelection()
281+ }
282+ }
283+ ]
284+ }
285+ }
286+
177287 Loader {
178288 id: emptyState
179289
@@ -185,7 +295,7 @@ Tab {
185295 verticalCenterOffset: Qt.inputMethod.visible ? units.gu(4) : 0
186296 }
187297
188− sourceComponent: episodesModel.count === 0 || sortedEpisodeModel.count === 0 ? emptyStateComponent : undefined
298+ sourceComponent: (episodesModel.count === 0 || sortedEpisodeModel.count === 0) && downloader.downloads.length === 0 ? emptyStateComponent : undefined
189299 }
190300
191301 Component {
@@ -315,6 +425,9 @@ Tab {
315425 ListView {
316426 id: episodeList
317427
428+ signal clearSelection()
429+ signal closeSelection()
430+
318431 Component.onCompleted: {
319432 // FIXME: workaround for qtubuntu not returning values depending on the grid unit definition
320433 // for Flickable.maximumFlickVelocity and Flickable.flickDeceleration
@@ -332,35 +445,77 @@ Tab {
332445
333446 clip: true
334447 model: sortedEpisodeModel
335− section.property: "diff"
336− section.labelPositioning: ViewSection.InlineLabels
337448
338− section.delegate: ListItem {
339− height: headerText.title.text !== "" ? headerText.height + divider.height : units.gu(0)
340− divider.anchors.leftMargin: units.gu(2)
341− divider.anchors.rightMargin: units.gu(2)
449+ header: Column {
450+ width: episodeList.width
451+ visible: height !== 0
452+ height: downloader.downloads.length > 0 && episodesPageHeaderSections.selectedIndex === 1 ? childrenRect.height : 0
342453
343− ListItemLayout {
344− id: headerText
345− title.text: {
346− if (section === "Today") {
347− return i18n.tr("Today")
348− }
454+ CustomSectionHeader {
455+ title: i18n.tr("Downloads in progress")
456+ }
349457
350− else if (section === "Yesterday") {
351− return i18n.tr("Yesterday")
352− }
458+ Repeater {
459+ model: downloader.downloads
460+ delegate: ListItem {
461+ divider.visible: false
462+ height: inProgressLayout.height
463+ SlotsLayout {
464+ id: inProgressLayout
465+
466+ Image {
467+ height: width
468+ width: units.gu(6)
469+ source: modelData.image !== undefined ? modelData.image : Qt.resolvedUrl("../graphics/podbird.png")
470+ SlotsLayout.position: SlotsLayout.Leading
471+ sourceSize { width: width; height: height }
472+ }
353473
354− else if (section === "Older") {
355− return i18n.tr("Older")
356− }
474+ mainSlot: Column {
475+ spacing: units.gu(0.5)
476+
477+ Label {
478+ text: modelData.title
479+ width: parent.width
480+ elide: Text.ElideRight
481+ }
357482
358− else {
359− return ""
483+ CustomProgressBar {
484+ width: parent.width
485+ height: modelData.progress > 0 ? units.dp(5) : 0
486+ progress: modelData.progress
487+ indeterminateProgress: modelData.progress < 0 || modelData.progress > 100
488+ }
489+ }
360490 }
361491 }
362− title.color: podbird.appTheme.baseText
363− title.font.weight: Font.DemiBold
492+ }
493+
494+ CustomSectionHeader {
495+ title: i18n.tr("Downloaded episodes")
496+ visible: sortedEpisodeModel.count !== 0 || episodesModel.count !== 0
497+ }
498+ }
499+
500+ section.property: "diff"
501+ section.labelPositioning: ViewSection.InlineLabels
502+ section.delegate: CustomSectionHeader {
503+ title: {
504+ if (section === "Today") {
505+ return i18n.tr("Today")
506+ }
507+
508+ else if (section === "Yesterday") {
509+ return i18n.tr("Yesterday")
510+ }
511+
512+ else if (section === "Older") {
513+ return i18n.tr("Older")
514+ }
515+
516+ else {
517+ return ""
518+ }
364519 }
365520 }
366521
@@ -380,8 +535,8 @@ Tab {
380535 id: listItemLayout
381536
382537 title.text: model.name !== undefined ? model.name.trim() : "Undefined"
383− title.color: currentGuid === model.guid || downloader.downloadingGuid === model.guid ? podbird.appTheme.focusText
384− : podbird.appTheme.baseText
538+ title.color: downloader.downloadingGuid === model.guid ? podbird.appTheme.focusText
539+ : podbird.appTheme.baseText
385540 // #FIXME: Change this 2 to prevent title eliding when UITK is updated to rev > 1800
386541 title.maximumLineCount: 1
387542
@@ -439,7 +594,11 @@ Tab {
439594 tx.executeSql("UPDATE Episode SET queued=1 WHERE guid = ?", [model.guid]);
440595 });
441596 episodesModel.setProperty(model.index, "queued", 1)
442− downloader.addDownload(model.guid, model.audiourl);
597+ if (model.audiourl) {
598+ podbird.downloadEpisode(model.image, model.name, model.guid, model.audiourl)
599+ } else {
600+ console.log("[ERROR]: Invalid download url: " + model.audiourl)
601+ }
443602 }
444603 }
445604 },
@@ -464,6 +623,14 @@ Tab {
464623 }
465624 },
466625
626+ Action {
627+ iconName: "add-to-playlist"
628+ onTriggered: {
629+ var url = model.downloadedfile ? "file://" + model.downloadedfile : model.audiourl
630+ player.addEpisodeToQueue(model.guid, model.image, model.name, model.artist, url)
631+ }
632+ },
633+
467634 Action {
468635 iconName: model.favourited ? "unlike" : "like"
469636 onTriggered: {
@@ -496,21 +663,29 @@ Tab {
496663
497664 onClicked: {
498665 Haptics.play()
499− var db = Podcasts.init();
500− db.transaction(function (tx) {
666+ if (selectMode) {
667+ selected = !selected
668+ } else {
501669 if (currentGuid !== model.guid) {
502− currentGuid = "";
503− currentUrl = model.downloadedfile ? model.downloadedfile : model.audiourl;
504− var rs = tx.executeSql("SELECT position FROM Episode WHERE guid=?", [model.guid]);
505− playerLoader.item.play();
506− playerLoader.item.seek(rs.rows.item(0).position);
507− currentName = model.name;
508− currentArtist = model.artist;
509− currentImage = model.image;
510− currentGuid = model.guid;
670+ currentUrl = model.downloadedfile ? "file://" + model.downloadedfile : model.audiourl;
671+ player.playEpisode(model.guid, model.image, model.name, model.artist, currentUrl)
511672 }
512− });
673+ }
513674 }
675+
676+ onPressAndHold: {
677+ ListView.view.ViewItems.selectMode = !ListView.view.ViewItems.selectMode
678+ }
679+ }
680+
681+ onClearSelection: {
682+ ViewItems.selectedIndices = []
683+ }
684+
685+ onCloseSelection: {
686+ clearSelection()
687+ ViewItems.selectMode = false
688+ episodesPage.header = standardHeader
514689 }
515690
516691 Scrollbar {