Cleanup episode events, mark episodes as old, fresh episodes fix

This commit is contained in:
Thomas Perl 2014-02-10 19:09:51 +01:00
parent 8eebb09707
commit 0ff355737c
11 changed files with 126 additions and 93 deletions

View file

@ -28,16 +28,13 @@ Python {
property string progname: 'gpodder' property string progname: 'gpodder'
property bool ready: false property bool ready: false
property bool refreshing: false property bool refreshing: false
signal downloading(int episode_id)
signal downloadProgress(int episode_id, real progress) signal downloadProgress(int episode_id, real progress)
signal playbackProgress(int episode_id, real progress) signal playbackProgress(int episode_id, real progress)
signal downloaded(int episode_id)
signal deleted(int episode_id)
signal isNewChanged(int episode_id, bool is_new)
signal stateChanged(int episode_id, int state)
signal podcastListChanged() signal podcastListChanged()
signal updatingPodcast(int podcast_id) signal updatingPodcast(int podcast_id)
signal updatedPodcast(var podcast) signal updatedPodcast(var podcast)
signal episodeListChanged(int podcast_id)
signal updatedEpisode(var episode)
Component.onCompleted: { Component.onCompleted: {
setHandler('hello', function (coreversion, uiversion) { setHandler('hello', function (coreversion, uiversion) {
@ -47,17 +44,14 @@ Python {
console.log('Python ' + py.pythonVersion()); console.log('Python ' + py.pythonVersion());
}); });
setHandler('downloading', py.downloading);
setHandler('download-progress', py.downloadProgress); setHandler('download-progress', py.downloadProgress);
setHandler('playback-progress', py.playbackProgress); setHandler('playback-progress', py.playbackProgress);
setHandler('downloaded', py.downloaded);
setHandler('deleted', py.deleted);
setHandler('is-new-changed', py.isNewChanged);
setHandler('state-changed', py.stateChanged);
setHandler('podcast-list-changed', py.podcastListChanged); setHandler('podcast-list-changed', py.podcastListChanged);
setHandler('updating-podcast', py.updatingPodcast); setHandler('updating-podcast', py.updatingPodcast);
setHandler('updated-podcast', py.updatedPodcast); setHandler('updated-podcast', py.updatedPodcast);
setHandler('refreshing', function(v) { py.refreshing = v; }); setHandler('refreshing', function(v) { py.refreshing = v; });
setHandler('episode-list-changed', py.episodeListChanged);
setHandler('updated-episode', py.updatedEpisode);
addImportPath(Qt.resolvedUrl('../..')); addImportPath(Qt.resolvedUrl('../..'));

View file

@ -26,7 +26,22 @@ import 'constants.js' as Constants
ListModel { ListModel {
id: episodeListModel id: episodeListModel
property var podcast_id
function loadEpisodes(podcast_id) { function loadEpisodes(podcast_id) {
episodeListModel.podcast_id = podcast_id;
reload();
}
function loadFreshEpisodes(callback) {
episodeListModel.podcast_id = -1;
py.call('main.get_fresh_episodes', [], function (episodes) {
Util.updateModelFrom(episodeListModel, episodes);
callback();
});
}
function reload() {
py.call('main.load_episodes', [podcast_id], function (episodes) { py.call('main.load_episodes', [podcast_id], function (episodes) {
Util.updateModelFrom(episodeListModel, episodes); Util.updateModelFrom(episodeListModel, episodes);
}); });
@ -43,21 +58,18 @@ ListModel {
Util.updateModelWith(episodeListModel, 'id', episode_id, Util.updateModelWith(episodeListModel, 'id', episode_id,
{'playbackProgress': progress}); {'playbackProgress': progress});
} }
onDownloaded: { onUpdatedEpisode: {
Util.updateModelWith(episodeListModel, 'id', episode_id, for (var i=0; i<episodeListModel.count; i++) {
{'progress': 0, 'downloadState': Constants.state.downloaded}); if (episodeListModel.get(i).id == episode.id) {
episodeListModel.set(i, episode);
break;
}
}
} }
onDeleted: { onEpisodeListChanged: {
Util.updateModelWith(episodeListModel, 'id', episode_id, if (episodeListModel.podcast_id == podcast_id) {
{'downloadState': Constants.state.deleted, 'isNew': false}); episodeListModel.reload();
} }
onIsNewChanged: {
Util.updateModelWith(episodeListModel, 'id', episode_id,
{'isNew': is_new});
}
onStateChanged: {
Util.updateModelWith(episodeListModel, 'id', episode_id,
{'downloadState': state});
} }
} }
} }

61
main.py
View file

@ -79,6 +79,11 @@ class gPotherSide:
if podcast.id == podcast_id: if podcast.id == podcast_id:
return podcast return podcast
def _episode_state_changed(self, episode):
pyotherside.send('updated-episode', self.convert_episode(episode))
pyotherside.send('updated-podcast', self.convert_podcast(episode.parent))
pyotherside.send('update-stats')
def get_stats(self): def get_stats(self):
podcasts = self.core.model.get_podcasts() podcasts = self.core.model.get_podcasts()
@ -139,6 +144,8 @@ class gPotherSide:
'downloadState': episode.state, 'downloadState': episode.state,
'isNew': episode.is_new, 'isNew': episode.is_new,
'playbackProgress': self._get_playback_progress(episode), 'playbackProgress': self._get_playback_progress(episode),
'published': util.format_date(episode.published),
'hasShownotes': episode.description != '',
} }
def load_episodes(self, id): def load_episodes(self, id):
@ -151,6 +158,7 @@ class gPotherSide:
_, _, new, _, _ = podcast.get_statistics() _, _, new, _, _ = podcast.get_statistics()
if new: if new:
summary.append({ summary.append({
'title': podcast.title,
'coverart': self._get_cover(podcast), 'coverart': self._get_cover(podcast),
'newEpisodes': new, 'newEpisodes': new,
}) })
@ -158,16 +166,6 @@ class gPotherSide:
summary.sort(key=lambda e: e['newEpisodes'], reverse=True) summary.sort(key=lambda e: e['newEpisodes'], reverse=True)
return summary[:int(count)] return summary[:int(count)]
def convert_fresh_episode(self, episode):
return {
'id': episode.id,
'title': episode.title,
'podcast': episode.channel.title,
'published': util.format_date(episode.published),
'progress': episode.download_progress(),
'downloadState': episode.state,
}
def get_fresh_episodes(self): def get_fresh_episodes(self):
fresh_episodes = [] fresh_episodes = []
for podcast in self.core.model.get_podcasts(): for podcast in self.core.model.get_podcasts():
@ -176,7 +174,7 @@ class gPotherSide:
fresh_episodes.append(episode) fresh_episodes.append(episode)
fresh_episodes.sort(key=lambda e: e.published, reverse=True) fresh_episodes.sort(key=lambda e: e.published, reverse=True)
return [self.convert_fresh_episode(e) for e in fresh_episodes] return [self.convert_episode(e) for e in fresh_episodes]
@run_in_background_thread @run_in_background_thread
def subscribe(self, url): def subscribe(self, url):
@ -210,26 +208,23 @@ class gPotherSide:
@run_in_background_thread @run_in_background_thread
def download_episode(self, episode_id): def download_episode(self, episode_id):
def progress_callback(progress):
pyotherside.send('download-progress', episode_id, progress)
episode = self._get_episode_by_id(episode_id) episode = self._get_episode_by_id(episode_id)
if episode.state == gpodder.STATE_DOWNLOADED: if episode.state == gpodder.STATE_DOWNLOADED:
return return
pyotherside.send('downloading', episode_id) def progress_callback(progress):
if episode.download(progress_callback): self._episode_state_changed(episode)
pyotherside.send('downloaded', episode_id)
else: # TODO: Handle the case where there is already a DownloadTask
pyotherside.send('download-failed', episode_id) episode.download(progress_callback)
self.core.save() self.core.save()
pyotherside.send('update-stats') self._episode_state_changed(episode)
def delete_episode(self, episode_id): def delete_episode(self, episode_id):
episode = self._get_episode_by_id(episode_id) episode = self._get_episode_by_id(episode_id)
episode.delete() episode.delete()
self.core.save() self.core.save()
pyotherside.send('deleted', episode_id) self._episode_state_changed(episode)
pyotherside.send('update-stats')
def toggle_new(self, episode_id): def toggle_new(self, episode_id):
episode = self._get_episode_by_id(episode_id) episode = self._get_episode_by_id(episode_id)
@ -238,8 +233,25 @@ class gPotherSide:
episode.state = gpodder.STATE_NORMAL episode.state = gpodder.STATE_NORMAL
episode.save() episode.save()
self.core.save() self.core.save()
pyotherside.send('is-new-changed', episode_id, episode.is_new) self._episode_state_changed(episode)
pyotherside.send('state-changed', episode_id, episode.state)
def mark_episodes_as_old(self, podcast_id):
podcast = self._get_podcast_by_id(podcast_id)
any_changed = False
for episode in podcast.episodes:
if episode.is_new and episode.state == gpodder.STATE_NORMAL:
any_changed = True
episode.is_new = False
episode.save()
if any_changed:
pyotherside.send('episode-list-changed', podcast_id)
pyotherside.send('updated-podcast', self.convert_podcast(podcast))
pyotherside.send('update-stats')
self.core.save()
@run_in_background_thread @run_in_background_thread
def check_for_episodes(self): def check_for_episodes(self):
@ -268,7 +280,7 @@ class gPotherSide:
episode = self._get_episode_by_id(episode_id) episode = self._get_episode_by_id(episode_id)
episode.playback_mark() episode.playback_mark()
self.core.save() self.core.save()
pyotherside.send('is-new-changed', episode_id, episode.is_new) self._episode_state_changed(episode)
return { return {
'title': episode.title, 'title': episode.title,
'podcast_title': episode.parent.title, 'podcast_title': episode.parent.title,
@ -319,3 +331,4 @@ toggle_new = gpotherside.toggle_new
rename_podcast = gpotherside.rename_podcast rename_podcast = gpotherside.rename_podcast
change_section = gpotherside.change_section change_section = gpotherside.change_section
report_playback_event = gpotherside.report_playback_event report_playback_event = gpotherside.report_playback_event
mark_episodes_as_old = gpotherside.mark_episodes_as_old

View file

@ -63,7 +63,7 @@ Item {
text: 'Download' text: 'Download'
color: (episodeItem.isPlaying || progress > 0) ? titleLabel.color : Constants.colors.download color: (episodeItem.isPlaying || progress > 0) ? titleLabel.color : Constants.colors.download
icon: Icons.cloud_download icon: Icons.cloud_download
visible: downloadState != Constants.state.downloaded enabled: downloadState != Constants.state.downloaded
onClicked: { onClicked: {
episodeList.selectedIndex = -1; episodeList.selectedIndex = -1;
py.call('main.download_episode', [id]); py.call('main.download_episode', [id]);
@ -74,7 +74,7 @@ Item {
text: 'Delete' text: 'Delete'
color: (episodeItem.isPlaying || progress > 0) ? titleLabel.color : Constants.colors.destructive color: (episodeItem.isPlaying || progress > 0) ? titleLabel.color : Constants.colors.destructive
icon: Icons.trash icon: Icons.trash
visible: downloadState != Constants.state.deleted enabled: downloadState != Constants.state.deleted
onClicked: { onClicked: {
var ctx = { py: py, id: id }; var ctx = { py: py, id: id };
pgst.showConfirmation('Delete episode', Icons.trash, function () { pgst.showConfirmation('Delete episode', Icons.trash, function () {
@ -96,6 +96,7 @@ Item {
color: titleLabel.color color: titleLabel.color
icon: Icons.article icon: Icons.article
onClicked: pgst.loadPage('EpisodeDetail.qml', {episode_id: id, title: title}); onClicked: pgst.loadPage('EpisodeDetail.qml', {episode_id: id, title: title});
enabled: hasShownotes
} }
} }
} }
@ -175,8 +176,10 @@ Item {
return Constants.colors.download; return Constants.colors.download;
} else if (episodeItem.opened) { } else if (episodeItem.opened) {
return Constants.colors.highlight; return Constants.colors.highlight;
} else if (isNew) { } else if (isNew && downloadState == Constants.state.downloaded) {
return Constants.colors.highlight; return Constants.colors.highlight;
} else if (isNew) {
return Constants.colors.fresh;
} else { } else {
return Constants.colors.text; return Constants.colors.text;
} }
@ -202,7 +205,7 @@ Item {
} }
visible: downloadState == Constants.state.downloaded visible: downloadState == Constants.state.downloaded
icon: Icons.cd icon: Icons.folder
} }
} }
} }

View file

@ -49,6 +49,16 @@ SlidePage {
} }
} }
PullMenuItem {
text: 'Mark all as old'
icon: Icons.eye
color: Constants.colors.text
onClicked: {
py.call('main.mark_episodes_as_old', [episodesPage.podcast_id]);
episodesPage.unPull();
}
}
PullMenuItem { PullMenuItem {
text: 'Unsubscribe' text: 'Unsubscribe'
icon: Icons.trash icon: Icons.trash

View file

@ -20,6 +20,7 @@
import QtQuick 2.0 import QtQuick 2.0
import 'common'
import 'common/util.js' as Util import 'common/util.js' as Util
SlidePage { SlidePage {
@ -27,8 +28,7 @@ SlidePage {
property bool ready: false property bool ready: false
Component.onCompleted: { Component.onCompleted: {
py.call('main.get_fresh_episodes', [], function (episodes) { freshEpisodesListModel.loadFreshEpisodes(function () {
Util.updateModelFrom(freshEpisodesListModel, episodes);
freshEpisodes.ready = true; freshEpisodes.ready = true;
}); });
} }
@ -39,33 +39,16 @@ SlidePage {
} }
PListView { PListView {
id: freshEpisodesList id: episodeList
property int selectedIndex: -1
title: 'Fresh episodes' title: 'Fresh episodes'
model: ListModel { id: freshEpisodesListModel } model: GPodderEpisodeListModel { id: freshEpisodesListModel }
section.property: 'published' section.property: 'published'
section.delegate: SectionHeader { text: section } section.delegate: SectionHeader { text: section }
delegate: EpisodeItem { delegate: EpisodeItem { }
onClicked: py.call('main.download_episode', [id]);
Connections {
target: py
onDownloadProgress: {
if (episode_id == id) {
freshEpisodesListModel.setProperty(index, 'progress', progress);
}
}
onDownloaded: {
if (id == episode_id) {
freshEpisodesListModel.remove(index);
}
}
}
//pgst.loadPage('EpisodeDetail.qml', {episode_id: id, title: title});
}
} }
} }

View file

@ -27,6 +27,7 @@ ButtonArea {
property alias text: label.text property alias text: label.text
property color color: Constants.colors.secondaryHighlight property color color: Constants.colors.secondaryHighlight
property color _real_color: enabled ? color : Constants.colors.placeholder
property alias icon: icon.icon property alias icon: icon.icon
property alias size: icon.size property alias size: icon.size
property bool alwaysShowText: false property bool alwaysShowText: false
@ -48,7 +49,7 @@ ButtonArea {
id: label id: label
font.pixelSize: 15 * pgst.scalef font.pixelSize: 15 * pgst.scalef
visible: parent.pressed || parent.alwaysShowText visible: parent.pressed || parent.alwaysShowText
color: parent.pressed ? Qt.darker(iconMenuItem.color, 1.1) : iconMenuItem.color color: parent.pressed ? Qt.darker(iconMenuItem._real_color, 1.1) : iconMenuItem._real_color
anchors { anchors {
bottom: icon.top bottom: icon.top

View file

@ -20,6 +20,8 @@
import QtQuick 2.0 import QtQuick 2.0
import 'common/constants.js' as Constants
ButtonArea { ButtonArea {
id: podcastItem id: podcastItem
@ -58,12 +60,24 @@ ButtonArea {
left: cover.right left: cover.right
leftMargin: 10 * pgst.scalef leftMargin: 10 * pgst.scalef
rightMargin: 10 * pgst.scalef rightMargin: 10 * pgst.scalef
right: parent.right right: downloadsLabel.left
verticalCenter: parent.verticalCenter verticalCenter: parent.verticalCenter
} }
elide: Text.ElideRight elide: Text.ElideRight
text: title text: title
color: newEpisodes ? Constants.colors.fresh : Constants.colors.text
}
PLabel {
id: downloadsLabel
anchors {
right: parent.right
rightMargin: 10 * pgst.scalef
verticalCenter: parent.verticalCenter
}
text: downloaded ? downloaded : ''
color: Constants.colors.text
} }
} }

View file

@ -42,7 +42,7 @@ SlidePage {
PullMenuItem { PullMenuItem {
text: 'Refresh feeds' text: 'Refresh feeds'
icon: Icons.reload icon: Icons.loop_alt2
onClicked: { onClicked: {
podcastsPage.unPull(); podcastsPage.unPull();
py.call('main.check_for_episodes'); py.call('main.check_for_episodes');

View file

@ -116,35 +116,35 @@ SlidePage {
StartPageButton { StartPageButton {
id: freshEpisodesPage id: freshEpisodesPage
enabled: freshEpisodesRepeater.count > 0
title: py.refreshing ? 'Refreshing feeds' : 'Fresh episodes' title: py.refreshing ? 'Refreshing feeds' : 'Fresh episodes'
onClicked: pgst.loadPage('FreshEpisodes.qml'); onClicked: pgst.loadPage('FreshEpisodes.qml');
Row { Row {
id: freshEpisodesRow
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.bottomMargin: 50 * pgst.scalef anchors.bottomMargin: 20 * pgst.scalef
anchors.leftMargin: 20 * pgst.scalef anchors.leftMargin: 20 * pgst.scalef
anchors.left: parent.left anchors.left: parent.left
spacing: 10 * pgst.scalef spacing: 10 * pgst.scalef
PLabel {
color: Constants.colors.placeholder
text: 'No fresh episodes'
visible: freshEpisodesRepeater.count == 0
}
Repeater { Repeater {
id: freshEpisodesRepeater id: freshEpisodesRepeater
Image { CoverArt {
source: modelData.coverart source: modelData.coverart
sourceSize { width: 80 * pgst.scalef; height: 80 * pgst.scalef } text: modelData.title
width: 80 * pgst.scalef width: 80 * pgst.scalef
height: 80 * pgst.scalef height: 80 * pgst.scalef
PLabel {
anchors {
horizontalCenter: parent.horizontalCenter
top: parent.bottom
margins: 5 * pgst.scalef
}
text: modelData.newEpisodes
}
} }
} }
} }
@ -165,7 +165,7 @@ SlidePage {
PIcon { PIcon {
id: refresher id: refresher
icon: Icons.reload icon: Icons.loop_alt2
color: Constants.colors.highlight color: Constants.colors.highlight
anchors { anchors {

View file

@ -13,3 +13,6 @@ var arrow_left = '\u2190';
var arrow_right = '\u2192'; var arrow_right = '\u2192';
var last = '\ue04d'; var last = '\ue04d';
var aperture = '\ue026'; var aperture = '\ue026';
var eye = '\ue025';
var loop_alt2 = '\ue033';
var folder = '\ue065';