commit a333def9c1950beb7b372ff65a9d7bb59df4b383 Author: Thomas Perl Date: Fri Aug 23 17:30:04 2013 +0200 Initial commit Based on private pyotherside devel branch (gPodder Cuatro, May 2013). diff --git a/README b/README new file mode 100644 index 0000000..5ccf4a1 --- /dev/null +++ b/README @@ -0,0 +1,15 @@ +gPodder 4 QML UI Reference Implementation +----------------------------------------- + +Nothing (much) to see here (yet), move along. + +Usage: qmlscene index.qml + +Dependencies: + +Package Min.Version URL +------------------------------------------------------------ +Qt 5.0.2 http://qt-project.org/ +Python 3.3.0 http://python.org/ +PyOtherSide 1.0.0 http://thp.io/2011/pyotherside/ +gPodder 4.0.0 http://gpodder.org/ diff --git a/index.qml b/index.qml new file mode 100644 index 0000000..d59aaf2 --- /dev/null +++ b/index.qml @@ -0,0 +1,29 @@ +/** + * + * gPodder QML UI Reference Implementation + * Copyright (c) 2013, Thomas Perl + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + * + */ + +import QtQuick 2.0 +import 'qml' + +Rectangle { + color: 'black' + width: 480 + height: 800 + + Main {} +} diff --git a/main.py b/main.py new file mode 100644 index 0000000..ca2d293 --- /dev/null +++ b/main.py @@ -0,0 +1,227 @@ +# +# gPodder QML UI Reference Implementation +# Copyright (c) 2013, Thomas Perl +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +# AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +# OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +# PERFORMANCE OF THIS SOFTWARE. +# + + +import sys +import os + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'gpodder', 'src')) + +import pyotherside +import gpodder + +from gpodder.api import core +from gpodder.api import util + +import logging +import functools + +logger = logging.getLogger(__name__) + +def run_in_background_thread(f): + """Decorator for functions that take longer to finish + + The function will be run in its own thread, and control + will be returned to the caller right away, which allows + other Python code to run while this function finishes. + + The function cannot return a value (control is usually + returned to the caller before execution is finished). + """ + @functools.wraps(f) + def wrapper(*args): + util.run_in_background(lambda: f(*args)) + + return wrapper + + +class gPotherSide: + def __init__(self): + self.core = core.Core() + self._checking_for_new_episodes = False + + def atexit(self): + self.core.shutdown() + + def _get_episode_by_id(self, episode_id): + for podcast in self.core.model.get_podcasts(): + for episode in podcast.episodes: + if episode.id == episode_id: + return episode + + def _get_podcast_by_id(self, podcast_id): + for podcast in self.core.model.get_podcasts(): + if podcast.id == podcast_id: + return podcast + + def get_stats(self): + podcasts = self.core.model.get_podcasts() + + total, deleted, new, downloaded, unplayed = 0, 0, 0, 0, 0 + for podcast in podcasts: + to, de, ne, do, un = podcast.get_statistics() + total += to + deleted += de + new += ne + downloaded += do + unplayed += un + + return '\n'.join([ + '%d podcasts' % len(podcasts), + '%d episodes' % total, + '%d new episodes' % new, + '%d downloads' % downloaded, + ]) + + def _get_cover(self, podcast): + filename = self.core.cover_downloader.get_cover(podcast) + if not filename: + return 'artwork/default.png' + return 'file://' + filename + + def convert_podcast(self, podcast): + total, deleted, new, downloaded, unplayed = podcast.get_statistics() + + return { + 'id': podcast.id, + 'title': podcast.title, + 'newEpisodes': new, + 'downloaded': downloaded, + 'coverart': self._get_cover(podcast), + 'updating': podcast._updating, + 'section': podcast.section, + } + + def load_podcasts(self): + podcasts = self.core.model.get_podcasts() + return [self.convert_podcast(podcast) for podcast in sorted(podcasts, + key=lambda podcast: (podcast.section, podcast.title))] + + def convert_episode(self, episode): + return { + 'id': episode.id, + 'title': episode.title, + 'progress': episode.download_progress(), + } + + def load_episodes(self, id): + podcast = self._get_podcast_by_id(id) + return [self.convert_episode(episode) for episode in podcast.episodes] + + def get_fresh_episodes_summary(self, count): + summary = [] + for podcast in self.core.model.get_podcasts(): + _, _, new, _, _ = podcast.get_statistics() + if new: + summary.append({ + 'coverart': self._get_cover(podcast), + 'newEpisodes': new, + }) + + summary.sort(key=lambda e: e['newEpisodes'], reverse=True) + 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(), + } + + def get_fresh_episodes(self): + fresh_episodes = [] + for podcast in self.core.model.get_podcasts(): + for episode in podcast.episodes: + if episode.is_fresh(): + fresh_episodes.append(episode) + + fresh_episodes.sort(key=lambda e: e.published, reverse=True) + return [self.convert_fresh_episode(e) for e in fresh_episodes] + + @run_in_background_thread + def subscribe(self, url): + url = util.normalize_feed_url(url) + self.core.model.load_podcast(url, create=True) + pyotherside.send('podcast-list-changed') + pyotherside.send('update-stats') + + @run_in_background_thread + def unsubscribe(self, podcast_id): + podcast = self._get_podcast_by_id(podcast_id) + podcast.unsubscribe() + pyotherside.send('podcast-list-changed') + pyotherside.send('update-stats') + + @run_in_background_thread + 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) + pyotherside.send('downloading', episode_id) + if episode.download(progress_callback): + pyotherside.send('downloaded', episode_id) + else: + pyotherside.send('download-failed', episode_id) + pyotherside.send('update-stats') + + @run_in_background_thread + def check_for_episodes(self): + if self._checking_for_new_episodes: + return + + self._checking_for_new_episodes = True + pyotherside.send('refreshing', True) + podcasts = self.core.model.get_podcasts() + for index, podcast in enumerate(podcasts): + pyotherside.send('refresh-progress', index, len(podcasts)) + pyotherside.send('updating-podcast', podcast.id) + try: + podcast.update() + except Exception as e: + logger.warn('Could not update %s: %s', podcast.url, + e, exc_info=True) + pyotherside.send('updated-podcast', self.convert_podcast(podcast)) + pyotherside.send('update-stats') + + self._checking_for_new_episodes = False + pyotherside.send('refreshing', False) + + def show_episode(self, episode_id): + episode = self._get_episode_by_id(episode_id) + return { + 'title': episode.trimmed_title, + 'description': util.remove_html_tags(episode.description), + } + +gpotherside = gPotherSide() +pyotherside.atexit(gpotherside.atexit) + +pyotherside.send('hello', gpodder.__version__, gpodder.__copyright__) + +# Exposed API Endpoints for calls from QML +load_podcasts = gpotherside.load_podcasts +load_episodes = gpotherside.load_episodes +show_episode = gpotherside.show_episode +subscribe = gpotherside.subscribe +unsubscribe = gpotherside.unsubscribe +check_for_episodes = gpotherside.check_for_episodes +get_stats = gpotherside.get_stats +get_fresh_episodes = gpotherside.get_fresh_episodes +get_fresh_episodes_summary = gpotherside.get_fresh_episodes_summary +download_episode = gpotherside.download_episode diff --git a/qml/ButtonArea.qml b/qml/ButtonArea.qml new file mode 100644 index 0000000..0f07557 --- /dev/null +++ b/qml/ButtonArea.qml @@ -0,0 +1,37 @@ + +/** + * + * gPodder QML UI Reference Implementation + * Copyright (c) 2013, Thomas Perl + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + * + */ + +import QtQuick 2.0 + +Rectangle { + id: buttonArea + + signal clicked + + property bool transparent: false + color: mouseArea.pressed?'#33ffffff':(transparent?'#00000000':'#88000000') + + MouseArea { + id: mouseArea + anchors.fill: parent + onClicked: buttonArea.clicked(); + } +} + diff --git a/qml/Dragging.qml b/qml/Dragging.qml new file mode 100644 index 0000000..7c94ebd --- /dev/null +++ b/qml/Dragging.qml @@ -0,0 +1,57 @@ + +/** + * + * gPodder QML UI Reference Implementation + * Copyright (c) 2013, Thomas Perl + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + * + */ + +import QtQuick 2.0 + +MouseArea { + id: dragging + + property Item stacking + property bool hasPull: false + property bool canClose: true + signal pulled + + anchors.fill: parent + + drag { + target: parent + axis: Drag.XAxis + minimumX: dragging.hasPull ? (-parent.width/4) : 0 + maximumX: canClose ? parent.width : 0 + filterChildren: true + } + + onPressedChanged: { + if (pressed) { + dragging.stacking.stopAllAnimations(); + } else { + if (hasPull && parent.x < -parent.width / 4 + 10) { + pulled(); + parent.x = -parent.width / 4; + //dragging.stacking.fadeInAgain(); + } else if (parent.x > parent.width / 3) { + dragging.stacking.startFadeOut(); + } else { + dragging.stacking.fadeInAgain(); + } + } + } +} + diff --git a/qml/EpisodeDetail.qml b/qml/EpisodeDetail.qml new file mode 100644 index 0000000..fed4fed --- /dev/null +++ b/qml/EpisodeDetail.qml @@ -0,0 +1,65 @@ + +/** + * + * gPodder QML UI Reference Implementation + * Copyright (c) 2013, Thomas Perl + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + * + */ + +import QtQuick 2.0 + +import 'constants.js' as Constants + +SlidePage { + id: detailPage + + property int episode_id + property string title + + Component.onCompleted: { + label.text = 'Loading...'; + py.call('main.show_episode', [episode_id], function (episode) { + label.text = episode.description; + }); + } + + Flickable { + id: flickable + anchors.fill: parent + + contentWidth: detailColumn.width + contentHeight: detailColumn.height + detailColumn.spacing + + Column { + id: detailColumn + + width: detailPage.width + spacing: 10 * pgst.scalef + + SlidePageHeader { + title: detailPage.title + } + + PLabel { + id: label + width: parent.width * .8 + font.pixelSize: 30 * pgst.scalef + anchors.horizontalCenter: parent.horizontalCenter + wrapMode: Text.WordWrap + } + } + } +} + diff --git a/qml/EpisodeItem.qml b/qml/EpisodeItem.qml new file mode 100644 index 0000000..431b0b0 --- /dev/null +++ b/qml/EpisodeItem.qml @@ -0,0 +1,59 @@ + +/** + * + * gPodder QML UI Reference Implementation + * Copyright (c) 2013, Thomas Perl + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + * + */ + +import QtQuick 2.0 + +import 'constants.js' as Constants + +ButtonArea { + id: podcastItem + + Rectangle { + anchors { + top: parent.top + bottom: parent.bottom + left: parent.left + } + width: parent.width * progress + color: Constants.colors.download + opacity: .4 + } + + transparent: true + height: 80 * pgst.scalef + + anchors { + left: parent.left + right: parent.right + } + + PLabel { + anchors { + left: parent.left + right: parent.right + verticalCenter: parent.verticalCenter + margins: 30 * pgst.scalef + } + + elide: Text.ElideRight + text: title + } +} + diff --git a/qml/EpisodesPage.qml b/qml/EpisodesPage.qml new file mode 100644 index 0000000..70cacab --- /dev/null +++ b/qml/EpisodesPage.qml @@ -0,0 +1,70 @@ + +/** + * + * gPodder QML UI Reference Implementation + * Copyright (c) 2013, Thomas Perl + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + * + */ + +import QtQuick 2.0 +import io.thp.pyotherside 1.0 + +import 'util.js' as Util + +SlidePage { + id: episodesPage + + hasPull: true + + property int podcast_id + property string title + + width: parent.width + height: parent.height + + Component.onCompleted: { + py.call('main.load_episodes', [podcast_id], function (episodes) { + Util.updateModelFrom(episodeListModel, episodes); + }); + } + + PullMenu { + PullMenuItem { + source: 'images/play.png' + onClicked: { + episodesPage.unPull(); + } + } + + PullMenuItem { + source: 'images/delete.png' + onClicked: { + py.call('main.unsubscribe', [episodesPage.podcast_id]); + episodesPage.closePage(); + } + } + } + + PListView { + id: episodeList + title: episodesPage.title + model: ListModel { id: episodeListModel } + + delegate: EpisodeItem { + onClicked: pgst.loadPage('EpisodeDetail.qml', {episode_id: id, title: title}); + } + } +} + diff --git a/qml/FreshEpisodes.qml b/qml/FreshEpisodes.qml new file mode 100644 index 0000000..f090fe8 --- /dev/null +++ b/qml/FreshEpisodes.qml @@ -0,0 +1,71 @@ + +/** + * + * gPodder QML UI Reference Implementation + * Copyright (c) 2013, Thomas Perl + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + * + */ + +import QtQuick 2.0 + +import 'util.js' as Util + +SlidePage { + id: freshEpisodes + property bool ready: false + + Component.onCompleted: { + py.call('main.get_fresh_episodes', [], function (episodes) { + Util.updateModelFrom(freshEpisodesListModel, episodes); + freshEpisodes.ready = true; + }); + } + + PBusyIndicator { + visible: !freshEpisodes.ready + anchors.centerIn: parent + } + + PListView { + id: freshEpisodesList + title: 'Fresh episodes' + + model: ListModel { id: freshEpisodesListModel } + + section.property: 'published' + section.delegate: SectionHeader { text: section } + + delegate: EpisodeItem { + onClicked: py.call('main.download_episode', [id]); + + Connections { + target: pgst + 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}); + } + } +} + diff --git a/qml/Main.qml b/qml/Main.qml new file mode 100644 index 0000000..f5c96df --- /dev/null +++ b/qml/Main.qml @@ -0,0 +1,109 @@ + +/** + * + * gPodder QML UI Reference Implementation + * Copyright (c) 2013, Thomas Perl + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + * + */ + +import QtQuick 2.0 +import io.thp.pyotherside 1.0 + +Rectangle { + id: pgst + property bool ready: false + + property real scalef: width / 480 + + anchors.fill: parent + color: '#336688' + + Image { + anchors.fill: parent + source: 'images/mask.png' + } + + Image { + anchors.fill: parent + source: 'images/noise.png' + fillMode: Image.Tile + } + + function update(page, x) { + var index = -1; + for (var i=0; i + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + * + */ + +import QtQuick 2.0 + +Item { + height: 64 * pgst.scalef + width: 64 * pgst.scalef + + Image { + anchors { + horizontalCenter: parent.horizontalCenter + bottom: parent.bottom + bottomMargin: 30*Math.abs(Math.sin(phase)) * pgst.scalef + } + + transform: Scale { + origin.x: 32 * pgst.scalef + origin.y: 32 * pgst.scalef + xScale: 1.0 - 0.3 * (1.0 - Math.abs(Math.sin(phase))) + } + + source: 'images/gpodder.png' + } + + property real phase: 0 + + PropertyAnimation on phase { + loops: Animation.Infinite + duration: 2000 + running: parent.visible + from: 0 + to: 2*Math.PI + } +} + diff --git a/qml/PLabel.qml b/qml/PLabel.qml new file mode 100644 index 0000000..580cfe7 --- /dev/null +++ b/qml/PLabel.qml @@ -0,0 +1,27 @@ + +/** + * + * gPodder QML UI Reference Implementation + * Copyright (c) 2013, Thomas Perl + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + * + */ + +import QtQuick 2.0 + +Text { + font.pixelSize: 30 * pgst.scalef + color: 'white' +} + diff --git a/qml/PListView.qml b/qml/PListView.qml new file mode 100644 index 0000000..b53e9c7 --- /dev/null +++ b/qml/PListView.qml @@ -0,0 +1,34 @@ + +/** + * + * gPodder QML UI Reference Implementation + * Copyright (c) 2013, Thomas Perl + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + * + */ + +import QtQuick 2.0 + +import 'constants.js' as Constants + +ListView { + id: pListView + + anchors.fill: parent + + property string title + + header: SlidePageHeader { title: pListView.title } +} + diff --git a/qml/PTextField.qml b/qml/PTextField.qml new file mode 100644 index 0000000..91a7a03 --- /dev/null +++ b/qml/PTextField.qml @@ -0,0 +1,51 @@ + +/** + * + * gPodder QML UI Reference Implementation + * Copyright (c) 2013, Thomas Perl + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + * + */ + +import QtQuick 2.0 + +Rectangle { + id: textField + + property alias text: textInput.text + property string placeholderText: '' + + radius: 10 * pgst.scalef + height: 50 * pgst.scalef + color: 'white' + + TextInput { + anchors { + fill: parent + margins: 5 * pgst.scalef + } + color: 'black' + id: textInput + font.pixelSize: height + } + + Text { + anchors.fill: textInput + visible: !textInput.focus && (textInput.text == '') + text: textField.placeholderText + color: '#aaa' + font.pixelSize: textInput.font.pixelSize + } +} + diff --git a/qml/PodcastItem.qml b/qml/PodcastItem.qml new file mode 100644 index 0000000..8b1442e --- /dev/null +++ b/qml/PodcastItem.qml @@ -0,0 +1,71 @@ + +/** + * + * gPodder QML UI Reference Implementation + * Copyright (c) 2013, Thomas Perl + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + * + */ + +import QtQuick 2.0 + +ButtonArea { + id: podcastItem + + transparent: true + + height: 100 * pgst.scalef + anchors { + left: parent.left + right: parent.right + } + + Image { + id: cover + visible: !updating + + anchors { + left: parent.left + leftMargin: 10 * pgst.scalef + verticalCenter: parent.verticalCenter + } + + sourceSize.width: width + sourceSize.height: height + + width: 80 * pgst.scalef + height: 80 * pgst.scalef + + source: coverart + } + + PBusyIndicator { + anchors.centerIn: cover + visible: updating + } + + PLabel { + anchors { + left: cover.right + leftMargin: 10 * pgst.scalef + rightMargin: 10 * pgst.scalef + right: parent.right + verticalCenter: parent.verticalCenter + } + + elide: Text.ElideRight + text: title + } +} + diff --git a/qml/PodcastsPage.qml b/qml/PodcastsPage.qml new file mode 100644 index 0000000..f1c625b --- /dev/null +++ b/qml/PodcastsPage.qml @@ -0,0 +1,110 @@ + +/** + * + * gPodder QML UI Reference Implementation + * Copyright (c) 2013, Thomas Perl + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + * + */ + +import QtQuick 2.0 + +import 'util.js' as Util + +SlidePage { + id: podcastsPage + hasPull: true + + function reload() { + loading.visible = true; + py.call('main.load_podcasts', [], function (podcasts) { + Util.updateModelFrom(podcastListModel, podcasts); + loading.visible = false; + }); + } + + Component.onCompleted: { + reload(); + + py.setHandler('podcast-list-changed', podcastsPage.reload); + + py.setHandler('updating-podcast', function (podcast_id) { + for (var i=0; i + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + * + */ + +import QtQuick 2.0 + +Column { + id: pullMenu + + width: parent.width / 4 + height: parent.height + anchors.top: parent.top + anchors.left: parent.right + + Item { width: 1; height: 1 } + + spacing: (width - 72 * pgst.scalef) / 2 +} + diff --git a/qml/PullMenuItem.qml b/qml/PullMenuItem.qml new file mode 100644 index 0000000..f63f76e --- /dev/null +++ b/qml/PullMenuItem.qml @@ -0,0 +1,38 @@ + +/** + * + * gPodder QML UI Reference Implementation + * Copyright (c) 2013, Thomas Perl + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + * + */ + +import QtQuick 2.0 + +Image { + id: pullMenuItem + + signal clicked + + width: 72 * pgst.scalef + height: 72 * pgst.scalef + + anchors.horizontalCenter: parent.horizontalCenter + + MouseArea { + anchors.fill: parent + onClicked: pullMenuItem.clicked(); + } +} + diff --git a/qml/SectionHeader.qml b/qml/SectionHeader.qml new file mode 100644 index 0000000..621f83e --- /dev/null +++ b/qml/SectionHeader.qml @@ -0,0 +1,39 @@ + +/** + * + * gPodder QML UI Reference Implementation + * Copyright (c) 2013, Thomas Perl + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + * + */ + +import QtQuick 2.0 + +Item { + property alias text: pLabel.text + + height: 70 * pgst.scalef + + PLabel { + id: pLabel + anchors { + left: parent.left + bottom: parent.bottom + margins: 10 * pgst.scalef + } + + color: '#aaa' + } +} + diff --git a/qml/Settings.qml b/qml/Settings.qml new file mode 100644 index 0000000..3b6a18f --- /dev/null +++ b/qml/Settings.qml @@ -0,0 +1,33 @@ + +/** + * + * gPodder QML UI Reference Implementation + * Copyright (c) 2013, Thomas Perl + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + * + */ + +import QtQuick 2.0 + +SlidePage { + SlidePageHeader { title: 'Settings' } + + Text { + anchors.centerIn: parent + color: 'white' + font.pixelSize: 50 * pgst.scalef + text: 'TODO' + } +} + diff --git a/qml/SlidePage.qml b/qml/SlidePage.qml new file mode 100644 index 0000000..2d1435d --- /dev/null +++ b/qml/SlidePage.qml @@ -0,0 +1,92 @@ + +/** + * + * gPodder QML UI Reference Implementation + * Copyright (c) 2013, Thomas Perl + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + * + */ + +import QtQuick 2.0 + +import 'constants.js' as Constants + +Rectangle { + id: page + color: '#88000000' + + default property alias children: dragging.children + property alias hasPull: dragging.hasPull + property alias canClose: dragging.canClose + property real pullPhase: (x >= 0) ? 0 : (-x / (width / 4)) + + function unPull() { + stacking.fadeInAgain(); + } + + function closePage() { + stacking.startFadeOut(); + } + + onXChanged: pgst.update(page, x) + + width: parent.width + height: parent.height + + Stacking { id: stacking } + + Dragging { + id: dragging + stacking: stacking + + onPulled: console.log('have pulled it!') + } + + Rectangle { + color: 'black' + anchors.fill: parent + + opacity: page.pullPhase * 0.8 + + MouseArea { + enabled: parent.opacity > 0 + anchors.fill: parent + onClicked: page.unPull(); + } + } + + Image { + anchors { + right: parent.left + top: parent.top + bottom: parent.bottom + } + width: 10 * pgst.scalef + source: 'images/pageshadow.png' + opacity: .2 + } + + Image { + anchors { + left: parent.right + top: parent.top + bottom: parent.bottom + } + mirror: true + width: 10 * pgst.scalef + source: 'images/pageshadow.png' + opacity: .2 + } +} + diff --git a/qml/SlidePageHeader.qml b/qml/SlidePageHeader.qml new file mode 100644 index 0000000..0435ba0 --- /dev/null +++ b/qml/SlidePageHeader.qml @@ -0,0 +1,60 @@ + +/** + * + * gPodder QML UI Reference Implementation + * Copyright (c) 2013, Thomas Perl + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + * + */ + +import QtQuick 2.0 + +import 'constants.js' as Constants + +Item { + id: slidePageHeader + property string title + + width: parent.width + height: Constants.layout.header.height * pgst.scalef + + Rectangle { + anchors { + left: parent.left + right: parent.right + top: parent.top + topMargin: slidePageHeader.height * 0.15 + } + + height: slidePageHeader.height * 0.7 + color: '#33000000' + } + + Text { + anchors { + left: parent.left + right: parent.right + rightMargin: 20 * pgst.scalef + leftMargin: 70 * pgst.scalef + verticalCenter: parent.verticalCenter + } + + color: 'white' + horizontalAlignment: Text.AlignRight + font.pixelSize: parent.height * .4 * pgst.scalef + elide: Text.ElideRight + text: parent.title + } +} + diff --git a/qml/Stacking.qml b/qml/Stacking.qml new file mode 100644 index 0000000..ce856c3 --- /dev/null +++ b/qml/Stacking.qml @@ -0,0 +1,63 @@ + +/** + * + * gPodder QML UI Reference Implementation + * Copyright (c) 2013, Thomas Perl + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + * + */ + +import QtQuick 2.0 + +Item { + id: stacking + property variant page: parent + + PropertyAnimation { + id: fadeIn + target: stacking.page + property: 'x' + to: 0 + duration: 500 + easing.type: Easing.OutCubic + } + + PropertyAnimation { + id: fadeOut + target: stacking.page + property: 'x' + to: stacking.page.width + duration: 500 + easing.type: Easing.OutCubic + } + + function startFadeOut() { + fadeOut.start(); + page.destroy(500); + } + + function fadeInAgain() { + fadeIn.start(); + } + + function stopAllAnimations() { + fadeIn.stop(); + } + + Component.onCompleted: { + page.x = page.width; + fadeIn.start(); + } +} + diff --git a/qml/StartPage.qml b/qml/StartPage.qml new file mode 100644 index 0000000..7dbbba7 --- /dev/null +++ b/qml/StartPage.qml @@ -0,0 +1,235 @@ + +/** + * + * gPodder QML UI Reference Implementation + * Copyright (c) 2013, Thomas Perl + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + * + */ + +import QtQuick 2.0 + +SlidePage { + id: startPage + canClose: false + + function update_stats() { + py.call('main.get_stats', [], function (result) { + stats.text = result; + }); + + py.call('main.get_fresh_episodes_summary', [3], function (episodes) { + freshEpisodesRepeater.model = episodes; + }); + } + + Component.onCompleted: { + py.setHandler('update-stats', startPage.update_stats); + } + + Component.onDestruction: { + py.setHandler('update-stats', undefined); + } + + Flickable { + Connections { + target: pgst + onReadyChanged: { + if (pgst.ready) { + startPage.update_stats(); + } + } + } + + anchors.fill: parent + + contentWidth: startPageColumn.width + contentHeight: startPageColumn.height + startPageColumn.spacing + + Column { + id: startPageColumn + + width: startPage.width + spacing: 20 * pgst.scalef + + SlidePageHeader { + title: 'gPodder' + } + + StartPageButton { + id: subscriptionsPane + + title: 'Subscriptions' + onClicked: pgst.loadPage('PodcastsPage.qml'); + + PLabel { + id: stats + + anchors { + verticalCenter: parent.verticalCenter + left: parent.left + margins: 20 * pgst.scalef + } + } + + ButtonArea { + anchors { + bottom: parent.bottom + right: parent.right + } + + transparent: true + onClicked: pgst.loadPage('Subscribe.qml'); + width: subscriptions.width + 2*subscriptions.anchors.margins + height: subscriptions.height + 2*subscriptions.anchors.margins + + Image { + id: subscriptions + source: 'images/subscriptions.png' + + anchors { + bottom: parent.bottom + right: parent.right + margins: 20 * pgst.scalef + } + } + } + } + + StartPageButton { + id: freshEpisodesPage + + title: 'Fresh episodes' + onClicked: pgst.loadPage('FreshEpisodes.qml'); + + Component.onCompleted: { + py.setHandler('refreshing', function (is_refreshing) { + refresherButtonArea.visible = !is_refreshing; + if (!is_refreshing) { + freshEpisodesPage.title = 'Fresh episodes'; + } + }); + + py.setHandler('refresh-progress', function (pos, total) { + freshEpisodesPage.title = 'Refreshing feeds (' + pos + '/' + total + ')'; + }); + } + + Row { + anchors.bottom: parent.bottom + anchors.bottomMargin: 50 * pgst.scalef + anchors.leftMargin: 20 * pgst.scalef + anchors.left: parent.left + spacing: 10 * pgst.scalef + + Repeater { + id: freshEpisodesRepeater + + Image { + source: modelData.coverart + sourceSize { width: 80 * pgst.scalef; height: 80 * pgst.scalef } + width: 80 * pgst.scalef + height: 80 * pgst.scalef + + PLabel { + anchors { + horizontalCenter: parent.horizontalCenter + top: parent.bottom + margins: 5 * pgst.scalef + } + + text: modelData.newEpisodes + } + } + } + } + + ButtonArea { + id: refresherButtonArea + + anchors { + bottom: parent.bottom + right: parent.right + } + + transparent: true + onClicked: py.call('main.check_for_episodes'); + width: refresher.width + 2*refresher.anchors.margins + height: refresher.height + 2*refresher.anchors.margins + + Image { + id: refresher + source: 'images/search.png' + + anchors { + bottom: parent.bottom + right: parent.right + margins: 20 * pgst.scalef + } + } + } + } + + ButtonArea { + onClicked: pgst.loadPage('Settings.qml'); + + anchors { + left: recommendationsPane.left + right: recommendationsPane.right + } + + height: 100 * pgst.scalef + + PLabel { + anchors.centerIn: parent + text: 'Settings' + } + } + + StartPageButton { + id: recommendationsPane + + title: 'Recommendations' + onClicked: pgst.loadPage('Settings.qml'); + + Row { + anchors { + horizontalCenter: parent.horizontalCenter + bottom: parent.bottom + margins: 40 * pgst.scalef + } + + spacing: 20 * pgst.scalef + + Connections { + target: pgst + onReadyChanged: { + if (pgst.ready) { + py.call('main.load_podcasts', [], function (podcasts) { + recommendationsRepeater.model = podcasts.splice(0, 4); + }); + } + } + } + + Repeater { + id: recommendationsRepeater + Image { source: modelData.coverart; sourceSize { width: 80 * pgst.scalef; height: 80 * pgst.scalef } } + } + } + } + } + } +} + diff --git a/qml/StartPageButton.qml b/qml/StartPageButton.qml new file mode 100644 index 0000000..7c4917e --- /dev/null +++ b/qml/StartPageButton.qml @@ -0,0 +1,42 @@ + +/** + * + * gPodder QML UI Reference Implementation + * Copyright (c) 2013, Thomas Perl + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + * + */ + +import QtQuick 2.0 + +ButtonArea { + id: startPageButton + + property string title + + anchors.horizontalCenter: parent.horizontalCenter + width: parent.width * .9 + height: 200 * pgst.scalef + + PLabel { + anchors { + right: parent.right + top: parent.top + margins: 20 * pgst.scalef + } + + text: startPageButton.title + } +} + diff --git a/qml/Subscribe.qml b/qml/Subscribe.qml new file mode 100644 index 0000000..1736ec9 --- /dev/null +++ b/qml/Subscribe.qml @@ -0,0 +1,64 @@ + +/** + * + * gPodder QML UI Reference Implementation + * Copyright (c) 2013, Thomas Perl + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + * + */ + +import QtQuick 2.0 + +SlidePage { + id: subscribe + + SlidePageHeader { title: 'Add subscription' } + + Column { + anchors.centerIn: parent + spacing: 30 * pgst.scalef + + PTextField { + id: input + width: subscribe.width *.8 + placeholderText: 'Feed URL' + } + + ButtonArea { + id: button + width: input.width + height: input.height + + PLabel { + anchors.centerIn: parent + text: 'Subscribe' + } + + onClicked: { + loading.visible = true; + button.visible = false; + input.visible = false; + py.call('main.subscribe', [input.text], function () { + subscribe.closePage(); + }); + } + } + + PBusyIndicator { + id: loading + visible: false + anchors.horizontalCenter: parent.horizontalCenter + } + } +} diff --git a/qml/constants.js b/qml/constants.js new file mode 100644 index 0000000..80e4adc --- /dev/null +++ b/qml/constants.js @@ -0,0 +1,33 @@ + +/** + * + * gPodder QML UI Reference Implementation + * Copyright (c) 2013, Thomas Perl + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + * + */ + +var layout = { + header: { + height: 100, /* page header height */ + }, +}; + +var colors = { + download: '#8ae234', /* download green */ + select: '#7f5785', /* gpodder dark purple */ + fresh: '#cf65de', /* gpodder purple */ + playback: '#729fcf', /* playback blue */ +}; + diff --git a/qml/images/delete.png b/qml/images/delete.png new file mode 100644 index 0000000..1b4e2e2 Binary files /dev/null and b/qml/images/delete.png differ diff --git a/qml/images/gpodder.png b/qml/images/gpodder.png new file mode 100644 index 0000000..8b03dde Binary files /dev/null and b/qml/images/gpodder.png differ diff --git a/qml/images/mask.png b/qml/images/mask.png new file mode 100644 index 0000000..d45f5c6 Binary files /dev/null and b/qml/images/mask.png differ diff --git a/qml/images/noise.png b/qml/images/noise.png new file mode 100644 index 0000000..fb8dfa9 Binary files /dev/null and b/qml/images/noise.png differ diff --git a/qml/images/pageshadow.png b/qml/images/pageshadow.png new file mode 100644 index 0000000..bb44406 Binary files /dev/null and b/qml/images/pageshadow.png differ diff --git a/qml/images/play.png b/qml/images/play.png new file mode 100644 index 0000000..b2a1a60 Binary files /dev/null and b/qml/images/play.png differ diff --git a/qml/images/search.png b/qml/images/search.png new file mode 100644 index 0000000..3d7e8e3 Binary files /dev/null and b/qml/images/search.png differ diff --git a/qml/images/subscriptions.png b/qml/images/subscriptions.png new file mode 100644 index 0000000..8f84af0 Binary files /dev/null and b/qml/images/subscriptions.png differ diff --git a/qml/util.js b/qml/util.js new file mode 100644 index 0000000..ec5acb4 --- /dev/null +++ b/qml/util.js @@ -0,0 +1,33 @@ + +/** + * + * gPodder QML UI Reference Implementation + * Copyright (c) 2013, Thomas Perl + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + * + */ + +function updateModelFrom(model, data) { + for (var i=0; i data.length) { + model.remove(model.count-1); + } +}