@@ -1,488 +0,0 @@
1−/*
2− * Copyright 2015-2016 Michael Sheldon <mike@mikeasoft.com>
3− *
4− * This file is part of Podbird.
5− *
6− * Podbird is free software; you can redistribute it and/or modify
7− * it under the terms of the GNU General Public License as published by
8− * the Free Software Foundation; version 3.
9− *
10− * Podbird is distributed in the hope that it will be useful,
11− * but WITHOUT ANY WARRANTY; without even the implied warranty of
12− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13− * GNU General Public License for more details.
14− *
15− * You should have received a copy of the GNU General Public License
16− * along with this program. If not, see <http://www.gnu.org/licenses/>.
17− */
18−
19−import QtQuick 2.4
20−import QtMultimedia 5.4
21−import Ubuntu.Components 1.3
22−import QtQuick.LocalStorage 2.0
23−import Ubuntu.DownloadManager 0.1
24−import Ubuntu.Components.Popups 1.3
25−import "../podcasts.js" as Podcasts
26−import "../components"
27−
28−Tab {
29− id: whatsNewTab
30−
31− property var today: new Date()
32− property int dayToMs: 86400000
33− property string tempGuid: "NULL"
34− property bool episodesUpdating: false
35−
36− TabsList {
37− id: tabsList
38− }
39−
40− page: Page {
41− id: whatsNewPage
42−
43− header: standardHeader
44−
45− PageHeader {
46− id: standardHeader
47− visible: whatsNewPage.header === standardHeader
48− title: i18n.tr("What's New")
49−
50− StyleHints {
51− backgroundColor: podbird.appTheme.background
52− }
53−
54− leadingActionBar {
55− numberOfSlots: 0
56− actions: tabsList.actions
57− }
58−
59− trailingActionBar.actions: [
60− Action {
61− iconName: "search"
62− text: i18n.tr("Search Episode")
63− onTriggered: {
64− whatsNewPage.header = searchHeader
65− searchField.item.forceActiveFocus()
66− }
67− },
68−
69− Action {
70− iconName: "select"
71− text: i18n.tr("Mark all listened")
72− onTriggered: {
73− var db = Podcasts.init();
74− 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]);
77− }
78− whatsNewModel.clear()
79− });
80− }
81− },
82−
83− Action {
84− iconName: "save"
85− text: i18n.tr("Download all")
86− onTriggered: {
87− var db = Podcasts.init();
88− 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);
94− }
95− }
96− });
97− }
98− }
99− ]
100− }
101−
102− PageHeader {
103− id: searchHeader
104− visible: whatsNewPage.header === searchHeader
105−
106− StyleHints {
107− backgroundColor: podbird.appTheme.background
108− }
109−
110− contents: Loader {
111− id: searchField
112− sourceComponent: whatsNewPage.header === searchHeader ? searchFieldComponent : undefined
113− anchors.left: parent ? parent.left : undefined
114− anchors.right: parent ? parent.right : undefined
115− anchors.verticalCenter: parent ? parent.verticalCenter : undefined
116− }
117−
118− trailingActionBar.actions: [
119− Action {
120− iconName: "edit-clear"
121− text: i18n.tr("Cancel")
122− onTriggered: {
123− episodeList.forceActiveFocus()
124− whatsNewPage.header = standardHeader
125− }
126− }
127− ]
128− }
129−
130− Component {
131− id: searchFieldComponent
132− TextField {
133− inputMethodHints: Qt.ImhNoPredictiveText
134− placeholderText: i18n.tr("Search episode")
135− }
136− }
137−
138− Loader {
139− id: emptyState
140−
141− anchors {
142− left: parent.left
143− right: parent.right
144− margins: units.gu(2)
145− verticalCenter: parent.verticalCenter
146− verticalCenterOffset: Qt.inputMethod.visible ? units.gu(4) : 0
147− }
148−
149− sourceComponent: whatsNewModel.count === 0 || sortedEpisodeModel.count === 0 ? emptyStateComponent : undefined
150− }
151−
152− Component {
153− id: emptyStateComponent
154− 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.")
158− }
159− }
160−
161− ListModel {
162− id: whatsNewModel
163− }
164−
165− SortFilterModel {
166− id: sortedEpisodeModel
167− model: whatsNewModel
168− filter.property: "name"
169− filter.pattern: whatsNewPage.state === "search" && searchField.status == Loader.Ready ? RegExp(searchField.item.text, "gi")
170− : RegExp("", "gi")
171− }
172−
173− onVisibleChanged: {
174− if (visible) {
175− refreshModel()
176− if (downloader.downloadingGuid != "")
177− tempGuid = downloader.downloadingGuid
178− } else {
179− whatsNewPage.header = standardHeader
180− }
181− }
182−
183− Connections {
184− target: downloader
185− onDownloadingGuidChanged: {
186− var db = Podcasts.init();
187− db.transaction(function (tx) {
188− /*
189− 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
191− with the downloadedfile location we just received from the downloader.
192− */
193− if (tempGuid != "NULL") {
194− 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) {
197− 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)
200− break
201− }
202− }
203− tempGuid = "NULL"
204− }
205−
206− /*
207− Here it is checked if the currently downloaded episode belongs to the podcast
208− page being currently displayed. If it is, then the downloaded episode guid is
209− stored in the tempGuid variable to track it.
210− */
211− var rs = tx.executeSql("SELECT podcast FROM Episode WHERE guid=?", [downloader.downloadingGuid]);
212−
213− if (downloader.downloadingGuid != "" && tempGuid == "NULL") {
214− tempGuid = downloader.downloadingGuid
215− }
216− });
217− refreshModel();
218− }
219− }
220−
221− /*
222− Note (nik90): After the upgrade to Ubuntu.Components 1.2, it seems the new listitems don't have their trailing
223− action width clamped. As a result when the list item expands and the user swipes left, it leads to a rather huge
224− trailing edge action. This has been reported upstream at http://pad.lv/1465582. Until this is fixed, the
225− episode description is shown in a dialog.
226− */
227− Component {
228− id: episodeDescriptionDialog
229− Dialog {
230− id: dialogInternal
231−
232− property string description
233−
234− title: "<b>%1</b>".arg(i18n.tr("Episode Description"))
235−
236− Label {
237− width: parent.width
238− wrapMode: Text.WordWrap
239− color: UbuntuColors.darkGrey
240− linkColor: "Blue"
241− text: dialogInternal.description
242− onLinkActivated: Qt.openUrlExternally(link)
243− }
244−
245− Button {
246− text: i18n.tr("Close")
247− color: podbird.appTheme.positiveActionButton
248− onClicked: {
249− PopupUtils.close(dialogInternal)
250− }
251− }
252− }
253− }
254−
255− UbuntuListView {
256− id: episodeList
257−
258− Component.onCompleted: {
259− // FIXME: workaround for qtubuntu not returning values depending on the grid unit definition
260− // for Flickable.maximumFlickVelocity and Flickable.flickDeceleration
261− var scaleFactor = units.gridUnit / 8;
262− maximumFlickVelocity = maximumFlickVelocity * scaleFactor;
263− flickDeceleration = flickDeceleration * scaleFactor;
264− }
265−
266− anchors {
267− top: whatsNewPage.header.bottom
268− left: parent.left
269− right: parent.right
270− bottom: parent.bottom
271− }
272−
273− clip: true
274−
275− model: sortedEpisodeModel
276− currentIndex: -1
277− section.property: "diff"
278− section.labelPositioning: ViewSection.InlineLabels
279−
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: {
294− if (section === "Today") {
295− return i18n.tr("Today")
296− }
297−
298− else if (section === "Yesterday") {
299− return i18n.tr("Yesterday")
300− }
301−
302− else if (section === "Older")
303− return i18n.tr("Older")
304− }
305− }
306− }
307−
308− footer: Item {
309− width: parent.width
310− height: units.gu(8)
311− }
312−
313− delegate: ListItem {
314− id: listItem
315−
316− divider.visible: false
317− highlightColor: "Transparent"
318− 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"
320−
321− ListItemLayout {
322− id: listItemLayout
323−
324− title.text: model.name !== undefined ? model.name.trim() : "Undefined"
325− title.color: currentGuid === model.guid || downloader.downloadingGuid === model.guid ? podbird.appTheme.focusText
326− : podbird.appTheme.baseText
327− // #FIXME: Change this 2 to prevent title eliding when UITK is updated to rev > 1800
328− title.maximumLineCount: 1
329−
330− subtitle.text: model.duration === 0 || model.duration === undefined ? model.downloadedfile ? "📎 " + model.artist
331− : model.artist
332− : model.downloadedfile ? "📎 " + Podcasts.formatEpisodeTime(model.duration) + " | " + model.artist
333− : Podcasts.formatEpisodeTime(model.duration) + " | " + model.artist
334− subtitle.color: podbird.appTheme.baseSubText
335−
336− Image {
337− height: width
338− width: units.gu(6)
339− source: model.image !== undefined ? model.image : Qt.resolvedUrl("../graphics/podbird.png")
340− SlotsLayout.position: SlotsLayout.Leading
341− sourceSize { width: width; height: height }
342− }
343−
344− padding.top: units.gu(1)
345− padding.bottom: units.gu(0.5)
346− }
347−
348− Loader {
349− id: progressBarLoader
350− anchors { top: listItemLayout.bottom; left: parent.left; right: parent.right; leftMargin: units.gu(2); rightMargin: units.gu(2) }
351− height: sourceComponent !== undefined ? units.dp(5) : 0
352− visible: sourceComponent !== undefined
353− sourceComponent: downloader.downloadingGuid === model.guid ? progressBar : undefined
354− }
355−
356− Component {
357− id: progressBar
358− CustomProgressBar {
359− indeterminateProgress: downloader.progress < 0 || downloader.progress > 100 && downloader.downloadingGuid === model.guid
360− progress: downloader.progress
361− }
362− }
363−
364− trailingActions: ListItemActions {
365− actions: [
366− Action {
367− iconName: model.downloadedfile ? "delete" : (model.queued && downloader.downloadingGuid !== model.guid ? "history" : "save")
368− onTriggered: {
369− var db = Podcasts.init();
370− if (model.downloadedfile) {
371− fileManager.deleteFile(model.downloadedfile);
372− db.transaction(function (tx) {
373− tx.executeSql("UPDATE Episode SET downloadedfile = NULL WHERE guid = ?", [model.guid]);
374− });
375− whatsNewModel.setProperty(model.index, "downloadedfile", "")
376− } else {
377− db.transaction(function (tx) {
378− tx.executeSql("UPDATE Episode SET queued=1 WHERE guid = ?", [model.guid]);
379− });
380− whatsNewModel.setProperty(model.index, "queued", 1)
381− downloader.addDownload(model.guid, model.audiourl);
382− }
383− }
384− },
385−
386− Action {
387− iconName: "select"
388− onTriggered: {
389− var db = Podcasts.init();
390− db.transaction(function (tx) {
391− tx.executeSql("UPDATE Episode SET listened=1 WHERE guid=?", [model.guid])
392− whatsNewModel.remove(model.index, 1)
393− });
394− }
395− },
396−
397− Action {
398− iconName: "info"
399− onTriggered: {
400− var popup = PopupUtils.open(episodeDescriptionDialog, whatsNewTab);
401− popup.description = model.description
402− }
403− }
404− ]
405− }
406−
407− onClicked: {
408− Haptics.play()
409− var db = Podcasts.init();
410− db.transaction(function (tx) {
411− if (currentGuid !== model.guid) {
412− currentGuid = "";
413− currentUrl = model.downloadedfile ? model.downloadedfile : model.audiourl;
414− var rs = tx.executeSql("SELECT position FROM Episode WHERE guid=?", [model.guid]);
415− playerLoader.item.play();
416− playerLoader.item.seek(rs.rows.item(0).position);
417− currentName = model.name;
418− currentArtist = model.artist;
419− currentImage = model.image;
420− currentGuid = model.guid;
421− }
422− });
423− }
424− }
425−
426− Scrollbar {
427− flickableItem: episodeList
428− align: Qt.AlignTrailing
429− StyleHints { sliderColor: podbird.appTheme.focusText }
430− }
431−
432− PullToRefresh {
433− refreshing: episodesUpdating
434− onRefresh: updateEpisodesDatabase();
435− }
436− }
437− }
438−
439− 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
444−
445− whatsNewModel.clear()
446− todayCount = 0
447− yesterdayCount = 0
448−
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"})
467− }
468− } else if (diff >= 7){
469− break
470− }
471− }
472−
473− if (podcast.lastupdate === null && !episodesUpdating) {
474− updateEpisodesDatabase();
475− }
476− }
477− });
478−
479− episodesUpdating = false;
480− }
481−
482− function updateEpisodesDatabase() {
483− console.log("[LOG]: Checking for new episodes")
484− episodesUpdating = true;
485− Podcasts.updateEpisodes(refreshModel)
486− }
487−}
488−