@@ -26,7 +26,7 @@ import "../podcasts.js" as Podcasts
2626import "../components"
2727
2828Tab {
29− id: whatsNewTab
29+ id: episodesTab
3030
3131 property var today: new Date()
3232 property int dayToMs: 86400000
@@ -38,14 +38,14 @@ Tab {
3838 }
3939
4040 page: Page {
41− id: whatsNewPage
41+ id: episodesPage
4242
4343 header: standardHeader
4444
4545 PageHeader {
4646 id: standardHeader
47− visible: whatsNewPage.header === standardHeader
48− title: i18n.tr("What's New")
47+ visible: episodesPage.header === standardHeader
48+ title: i18n.tr("Episodes")
4949
5050 StyleHints {
5151 backgroundColor: podbird.appTheme.background
@@ -61,47 +61,87 @@ Tab {
6161 iconName: "search"
6262 text: i18n.tr("Search Episode")
6363 onTriggered: {
64− whatsNewPage.header = searchHeader
64+ episodesPage.header = searchHeader
6565 searchField.item.forceActiveFocus()
6666 }
6767 },
6868
6969 Action {
7070 iconName: "select"
71+ visible: episodesPageHeaderSections.selectedIndex === 0
7172 text: i18n.tr("Mark all listened")
7273 onTriggered: {
7374 var db = Podcasts.init();
7475 db.transaction(function (tx) {
75− for (var i=0; i<whatsNewModel.count; i++) {
76− tx.executeSql("UPDATE Episode SET listened=1 WHERE guid=?", [whatsNewModel.get(i).guid]);
76+ for (var i=0; i<episodesModel.count; i++) {
77+ tx.executeSql("UPDATE Episode SET listened=1 WHERE guid=?", [episodesModel.get(i).guid]);
7778 }
78− whatsNewModel.clear()
79+ episodesModel.clear()
7980 });
8081 }
8182 },
8283
8384 Action {
8485 iconName: "save"
86+ visible: episodesPageHeaderSections.selectedIndex === 0
8587 text: i18n.tr("Download all")
8688 onTriggered: {
8789 var db = Podcasts.init();
8890 db.transaction(function (tx) {
89− for (var i=0; i<whatsNewModel.count; i++) {
90− if (!whatsNewModel.get(i).downloadedfile) {
91− whatsNewModel.setProperty(i, "queued", 1)
92− tx.executeSql("UPDATE Episode SET queued=1 WHERE guid = ?", [whatsNewModel.get(i).guid]);
93− downloader.addDownload(whatsNewModel.get(i).guid, whatsNewModel.get(i).audiourl);
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);
9496 }
9597 }
9698 });
9799 }
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+ }
98119 }
99120 ]
121+
122+ extension: Sections {
123+ id: episodesPageHeaderSections
124+
125+ anchors {
126+ left: parent.left
127+ leftMargin: units.gu(2)
128+ bottom: parent.bottom
129+ }
130+
131+ StyleHints {
132+ selectedSectionColor: podbird.appTheme.focusText
133+ }
134+
135+ model: [i18n.tr("Recent"), i18n.tr("Downloaded"), i18n.tr("Favourites")]
136+ onSelectedIndexChanged: {
137+ refreshModel();
138+ }
139+ }
100140 }
101141
102142 PageHeader {
103143 id: searchHeader
104− visible: whatsNewPage.header === searchHeader
144+ visible: episodesPage.header === searchHeader
105145
106146 StyleHints {
107147 backgroundColor: podbird.appTheme.background
@@ -109,19 +149,18 @@ Tab {
109149
110150 contents: Loader {
111151 id: searchField
112− sourceComponent: whatsNewPage.header === searchHeader ? searchFieldComponent : undefined
152+ sourceComponent: episodesPage.header === searchHeader ? searchFieldComponent : undefined
113153 anchors.left: parent ? parent.left : undefined
114154 anchors.right: parent ? parent.right : undefined
115155 anchors.verticalCenter: parent ? parent.verticalCenter : undefined
116156 }
117157
118− trailingActionBar.actions: [
158+ leadingActionBar.actions: [
119159 Action {
120− iconName: "edit-clear"
121− text: i18n.tr("Cancel")
160+ iconName: "back"
122161 onTriggered: {
123162 episodeList.forceActiveFocus()
124− whatsNewPage.header = standardHeader
163+ episodesPage.header = standardHeader
125164 }
126165 }
127166 ]
@@ -146,28 +185,50 @@ Tab {
146185 verticalCenterOffset: Qt.inputMethod.visible ? units.gu(4) : 0
147186 }
148187
149− sourceComponent: whatsNewModel.count === 0 || sortedEpisodeModel.count === 0 ? emptyStateComponent : undefined
188+ sourceComponent: episodesModel.count === 0 || sortedEpisodeModel.count === 0 ? emptyStateComponent : undefined
150189 }
151190
152191 Component {
153192 id: emptyStateComponent
154193 EmptyState {
155− icon.source: whatsNewModel.count === 0 ? Qt.resolvedUrl("../graphics/owlSearch.svg") : Qt.resolvedUrl("../graphics/notFound.svg")
156− title: whatsNewModel.count === 0 ? i18n.tr("No New Episodes") : i18n.tr("No Episodes Found")
157− subTitle: whatsNewModel.count === 0 ? i18n.tr("No more episodes to listen to!") : i18n.tr("No Episodes found matching the search term.")
194+ icon.source: episodesModel.count === 0 ? Qt.resolvedUrl("../graphics/owlSearch.svg") : Qt.resolvedUrl("../graphics/notFound.svg")
195+ title: {
196+ if (episodesModel.count === 0 && episodesPage.header === standardHeader) {
197+ if (episodesPageHeaderSections.selectedIndex === 0)
198+ return i18n.tr("No New Episodes")
199+ else if (episodesPageHeaderSections.selectedIndex === 1)
200+ return i18n.tr("No Downloaded Episodes")
201+ else if (episodesPageHeaderSections.selectedIndex === 2)
202+ return i18n.tr("No Favourited Episodes")
203+ } else {
204+ return i18n.tr("No Episodes Found")
205+ }
206+ }
207+ subTitle: {
208+ if (episodesModel.count === 0 && episodesPage.header === standardHeader) {
209+ if (episodesPageHeaderSections.selectedIndex === 0)
210+ return i18n.tr("No more episodes to listen to!")
211+ else if (episodesPageHeaderSections.selectedIndex === 1)
212+ return i18n.tr("No episodes have been downloaded for offline listening")
213+ else if (episodesPageHeaderSections.selectedIndex === 2)
214+ return i18n.tr("No episodes have been favourited.")
215+ } else {
216+ return i18n.tr("No Episodes found matching the search term.")
217+ }
218+ }
158219 }
159220 }
160221
161222 ListModel {
162− id: whatsNewModel
223+ id: episodesModel
163224 }
164225
165226 SortFilterModel {
166227 id: sortedEpisodeModel
167− model: whatsNewModel
228+ model: episodesModel
168229 filter.property: "name"
169− filter.pattern: whatsNewPage.state === "search" && searchField.status == Loader.Ready ? RegExp(searchField.item.text, "gi")
170− : RegExp("", "gi")
230+ filter.pattern: episodesPage.header === searchHeader && searchField.status == Loader.Ready ? RegExp(searchField.item.text, "gi")
231+ : RegExp("", "gi")
171232 }
172233
173234 onVisibleChanged: {
@@ -176,7 +237,7 @@ Tab {
176237 if (downloader.downloadingGuid != "")
177238 tempGuid = downloader.downloadingGuid
178239 } else {
179− whatsNewPage.header = standardHeader
240+ episodesPage.header = standardHeader
180241 }
181242 }
182243
@@ -187,16 +248,16 @@ Tab {
187248 db.transaction(function (tx) {
188249 /*
189250 If tempGuid is NULL, then the episode currently being downloaded is not found within
190− this podcast. On the other hand, if it is within this podcast, then update the whatsNewModel
251+ this podcast. On the other hand, if it is within this podcast, then update the episodesModel
191252 with the downloadedfile location we just received from the downloader.
192253 */
193254 if (tempGuid != "NULL") {
194255 var rs2 = tx.executeSql("SELECT downloadedfile FROM Episode WHERE guid=?", [tempGuid]);
195− for (var i=0; i<whatsNewModel.count; i++) {
196− if (whatsNewModel.get(i).guid == tempGuid) {
256+ for (var i=0; i<episodesModel.count; i++) {
257+ if (episodesModel.get(i).guid == tempGuid) {
197258 console.log("[LOG]: Setting episode download URL to " + rs2.rows.item(0).downloadedfile)
198− whatsNewModel.setProperty(i, "downloadedfile", rs2.rows.item(0).downloadedfile)
199− whatsNewModel.setProperty(i, "queued", 0)
259+ episodesModel.setProperty(i, "downloadedfile", rs2.rows.item(0).downloadedfile)
260+ episodesModel.setProperty(i, "queued", 0)
200261 break
201262 }
202263 }
@@ -236,7 +297,6 @@ Tab {
236297 Label {
237298 width: parent.width
238299 wrapMode: Text.WordWrap
239− color: UbuntuColors.darkGrey
240300 linkColor: "Blue"
241301 text: dialogInternal.description
242302 onLinkActivated: Qt.openUrlExternally(link)
@@ -252,7 +312,7 @@ Tab {
252312 }
253313 }
254314
255− UbuntuListView {
315+ ListView {
256316 id: episodeList
257317
258318 Component.onCompleted: {
@@ -264,33 +324,25 @@ Tab {
264324 }
265325
266326 anchors {
267− top: whatsNewPage.header.bottom
327+ top: episodesPage.header.bottom
268328 left: parent.left
269329 right: parent.right
270330 bottom: parent.bottom
271331 }
272332
273333 clip: true
274−
275334 model: sortedEpisodeModel
276− currentIndex: -1
277335 section.property: "diff"
278336 section.labelPositioning: ViewSection.InlineLabels
279337
280− section.delegate: Rectangle {
281− width: parent.width
282− color: "Transparent"
283− height: header.implicitHeight + units.gu(2)
284− Label {
285− id: header
286− anchors {
287− left: parent.left
288− right: parent.right
289− margins: units.gu(2)
290− verticalCenter: parent.verticalCenter
291− }
292− textSize: Label.XLarge
293− text: {
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)
342+
343+ ListItemLayout {
344+ id: headerText
345+ title.text: {
294346 if (section === "Today") {
295347 return i18n.tr("Today")
296348 }
@@ -299,9 +351,16 @@ Tab {
299351 return i18n.tr("Yesterday")
300352 }
301353
302− else if (section === "Older")
354+ else if (section === "Older") {
303355 return i18n.tr("Older")
356+ }
357+
358+ else {
359+ return ""
360+ }
304361 }
362+ title.color: podbird.appTheme.baseText
363+ title.font.weight: Font.DemiBold
305364 }
306365 }
307366
@@ -314,9 +373,8 @@ Tab {
314373 id: listItem
315374
316375 divider.visible: false
317− highlightColor: "Transparent"
376+ highlightColor: podbird.appTheme.hightlightListView
318377 height: downloader.downloadingGuid === model.guid ? listItemLayout.height + progressBarLoader.height + units.gu(1) : listItemLayout.height + units.gu(0.5)
319− color: index % 2 === 0 ? podbird.appTheme.hightlightListView : "Transparent"
320378
321379 ListItemLayout {
322380 id: listItemLayout
@@ -372,24 +430,56 @@ Tab {
372430 db.transaction(function (tx) {
373431 tx.executeSql("UPDATE Episode SET downloadedfile = NULL WHERE guid = ?", [model.guid]);
374432 });
375− whatsNewModel.setProperty(model.index, "downloadedfile", "")
433+ episodesModel.setProperty(model.index, "downloadedfile", "")
434+ if (episodesPageHeaderSections.selectedIndex === 1) {
435+ episodesModel.remove(model.index, 1)
436+ }
376437 } else {
377438 db.transaction(function (tx) {
378439 tx.executeSql("UPDATE Episode SET queued=1 WHERE guid = ?", [model.guid]);
379440 });
380− whatsNewModel.setProperty(model.index, "queued", 1)
441+ episodesModel.setProperty(model.index, "queued", 1)
381442 downloader.addDownload(model.guid, model.audiourl);
382443 }
383444 }
384445 },
385446
386447 Action {
387− iconName: "select"
448+ iconName: model.listened ? "view-collapse" : "select"
388449 onTriggered: {
389450 var db = Podcasts.init();
390451 db.transaction(function (tx) {
391− tx.executeSql("UPDATE Episode SET listened=1 WHERE guid=?", [model.guid])
392− whatsNewModel.remove(model.index, 1)
452+ if (model.listened) {
453+ tx.executeSql("UPDATE Episode SET listened=0 WHERE guid=?", [model.guid])
454+ episodesModel.setProperty(model.index, "listened", 0)
455+ }
456+ else {
457+ tx.executeSql("UPDATE Episode SET listened=1 WHERE guid=?", [model.guid])
458+ episodesModel.setProperty(model.index, "listened", 1)
459+ if (episodesPageHeaderSections.selectedIndex === 0) {
460+ episodesModel.remove(model.index, 1)
461+ }
462+ }
463+ });
464+ }
465+ },
466+
467+ Action {
468+ iconName: model.favourited ? "unlike" : "like"
469+ onTriggered: {
470+ var db = Podcasts.init();
471+ db.transaction(function (tx) {
472+ if (model.favourited) {
473+ tx.executeSql("UPDATE Episode SET favourited=0 WHERE guid=?", [model.guid])
474+ episodesModel.setProperty(model.index, "favourited", 0)
475+ if (episodesPageHeaderSections.selectedIndex === 2) {
476+ episodesModel.remove(model.index, 1)
477+ }
478+ }
479+ else {
480+ tx.executeSql("UPDATE Episode SET favourited=1 WHERE guid=?", [model.guid])
481+ episodesModel.setProperty(model.index, "favourited", 1)
482+ }
393483 });
394484 }
395485 },
@@ -397,7 +487,7 @@ Tab {
397487 Action {
398488 iconName: "info"
399489 onTriggered: {
400− var popup = PopupUtils.open(episodeDescriptionDialog, whatsNewTab);
490+ var popup = PopupUtils.open(episodeDescriptionDialog, episodesTab);
401491 popup.description = model.description
402492 }
403493 }
@@ -437,44 +527,91 @@ Tab {
437527 }
438528
439529 function refreshModel() {
440− var today = new Date()
441− var dayToMs = 86400000; //1 * 24 * 60 * 60 * 1000
442− var i, j, episode, diff
443− var todayCount, yesterdayCount
530+ var i, j, episode
531+ var db = Podcasts.init()
444532
445− whatsNewModel.clear()
446− todayCount = 0
447− yesterdayCount = 0
533+ episodesModel.clear()
534+
535+ // Episode Model for the what's new view
536+ if (episodesPageHeaderSections.selectedIndex === 0) {
537+ var today = new Date()
538+ var dayToMs = 86400000; //1 * 24 * 60 * 60 * 1000
539+ var todayCount, yesterdayCount, diff
540+
541+ todayCount = 0
542+ yesterdayCount = 0
543+
544+ db.transaction(function (tx) {
545+ var rs = tx.executeSql("SELECT rowid, * FROM Podcast ORDER BY name ASC");
546+ for (i=0; i < rs.rows.length; i++) {
547+ var podcast = rs.rows.item(i);
548+ var rs2 = tx.executeSql("SELECT rowid, * FROM Episode WHERE podcast=? ORDER BY published DESC", [rs.rows.item(i).rowid]);
549+ for (j=0; j < rs2.rows.length; j++) {
550+ episode = rs2.rows.item(j)
551+ diff = Math.floor((today - episode.published)/dayToMs)
552+ if (diff < 7 && !episode.listened) {
553+ if (diff < 1) {
554+ episodesModel.insert(todayCount, {"guid" : episode.guid, "listened" : episode.listened, "published": episode.published, "name" : episode.name, "description" : episode.description, "duration" : episode.duration, "position" : episode.position, "downloadedfile" : episode.downloadedfile, "image" : podcast.image, "artist" : podcast.artist, "audiourl" : episode.audiourl, "queued": episode.queued, "favourited": episode.favourited, "diff": "Today"})
555+ todayCount++;
556+ } else if (diff < 2) {
557+ episodesModel.insert(todayCount + yesterdayCount, {"guid" : episode.guid, "listened" : episode.listened, "published": episode.published, "name" : episode.name, "description" : episode.description, "duration" : episode.duration, "position" : episode.position, "downloadedfile" : episode.downloadedfile, "image" : podcast.image, "artist" : podcast.artist, "audiourl" : episode.audiourl, "queued": episode.queued, "favourited": episode.favourited, "diff": "Yesterday"})
558+ yesterdayCount++;
559+ } else {
560+ episodesModel.append({"guid" : episode.guid, "listened" : episode.listened, "published": episode.published, "name" : episode.name, "description" : episode.description, "duration" : episode.duration, "position" : episode.position, "downloadedfile" : episode.downloadedfile, "image" : podcast.image, "artist" : podcast.artist, "audiourl" : episode.audiourl, "queued": episode.queued, "favourited": episode.favourited, "diff": "Older"})
561+ }
562+ } else if (diff >= 7){
563+ break
564+ }
565+ }
448566
449− var db = Podcasts.init()
450− db.transaction(function (tx) {
451− var rs = tx.executeSql("SELECT rowid, * FROM Podcast ORDER BY name ASC");
452− for (i=0; i < rs.rows.length; i++) {
453− var podcast = rs.rows.item(i);
454− var rs2 = tx.executeSql("SELECT rowid, * FROM Episode WHERE podcast=? ORDER BY published DESC", [rs.rows.item(i).rowid]);
455− for (j=0; j < rs2.rows.length; j++) {
456− episode = rs2.rows.item(j)
457− diff = Math.floor((today - episode.published)/dayToMs)
458− if (diff < 7 && !episode.listened) {
459− if (diff < 1) {
460− whatsNewModel.insert(todayCount, {"guid" : episode.guid, "listened" : episode.listened, "published": episode.published, "name" : episode.name, "description" : episode.description, "duration" : episode.duration, "position" : episode.position, "downloadedfile" : episode.downloadedfile, "image" : podcast.image, "artist" : podcast.artist, "audiourl" : episode.audiourl, "queued": episode.queued, "diff": "Today"})
461− todayCount++;
462− } else if (diff < 2) {
463− whatsNewModel.insert(todayCount + yesterdayCount, {"guid" : episode.guid, "listened" : episode.listened, "published": episode.published, "name" : episode.name, "description" : episode.description, "duration" : episode.duration, "position" : episode.position, "downloadedfile" : episode.downloadedfile, "image" : podcast.image, "artist" : podcast.artist, "audiourl" : episode.audiourl, "queued": episode.queued, "diff": "Yesterday"})
464− yesterdayCount++;
465− } else {
466− whatsNewModel.append({"guid" : episode.guid, "listened" : episode.listened, "published": episode.published, "name" : episode.name, "description" : episode.description, "duration" : episode.duration, "position" : episode.position, "downloadedfile" : episode.downloadedfile, "image" : podcast.image, "artist" : podcast.artist, "audiourl" : episode.audiourl, "queued": episode.queued, "diff": "Older"})
567+ if (podcast.lastupdate === null && !episodesUpdating) {
568+ updateEpisodesDatabase();
569+ }
570+ }
571+ });
572+ }
573+
574+ // Episode Model for the downloaded view
575+ else if (episodesPageHeaderSections.selectedIndex === 1) {
576+ db.transaction(function (tx) {
577+ var rs = tx.executeSql("SELECT rowid, * FROM Podcast ORDER BY name ASC");
578+ for (i=0; i < rs.rows.length; i++) {
579+ var podcast = rs.rows.item(i);
580+ var rs2 = tx.executeSql("SELECT rowid, * FROM Episode WHERE podcast=? ORDER BY published DESC", [rs.rows.item(i).rowid]);
581+ for (j=0; j < rs2.rows.length; j++) {
582+ episode = rs2.rows.item(j)
583+ if (episode.downloadedfile) {
584+ episodesModel.append({"guid" : episode.guid, "listened" : episode.listened, "published": episode.published, "name" : episode.name, "description" : episode.description, "duration" : episode.duration, "position" : episode.position, "downloadedfile" : episode.downloadedfile, "image" : podcast.image, "artist" : podcast.artist, "audiourl" : episode.audiourl, "queued": episode.queued, "favourited": episode.favourited, "diff": "Null"})
467585 }
468− } else if (diff >= 7){
469− break
586+ }
587+
588+ if (podcast.lastupdate === null && !episodesUpdating) {
589+ updateEpisodesDatabase();
470590 }
471591 }
592+ });
593+ }
594+
595+ // Episode Model for the favourites view
596+ else if (episodesPageHeaderSections.selectedIndex === 2) {
597+ db.transaction(function (tx) {
598+ var rs = tx.executeSql("SELECT rowid, * FROM Podcast ORDER BY name ASC");
599+ for (i=0; i < rs.rows.length; i++) {
600+ var podcast = rs.rows.item(i);
601+ var rs2 = tx.executeSql("SELECT rowid, * FROM Episode WHERE podcast=? ORDER BY published DESC", [rs.rows.item(i).rowid]);
602+ for (j=0; j < rs2.rows.length; j++) {
603+ episode = rs2.rows.item(j)
604+ if (episode.favourited) {
605+ episodesModel.append({"guid" : episode.guid, "listened" : episode.listened, "published": episode.published, "name" : episode.name, "description" : episode.description, "duration" : episode.duration, "position" : episode.position, "downloadedfile" : episode.downloadedfile, "image" : podcast.image, "artist" : podcast.artist, "audiourl" : episode.audiourl, "queued": episode.queued, "favourited": episode.favourited, "diff": "Null"})
606+ }
607+ }
472608
473− if (podcast.lastupdate === null && !episodesUpdating) {
474− updateEpisodesDatabase();
609+ if (podcast.lastupdate === null && !episodesUpdating) {
610+ updateEpisodesDatabase();
611+ }
475612 }
476− }
477− });
613+ });
614+ }
478615
479616 episodesUpdating = false;
480617 }