Initial commit

Based on private pyotherside devel branch (gPodder Cuatro, May 2013).
This commit is contained in:
Thomas Perl 2013-08-23 17:30:04 +02:00
commit a333def9c1
36 changed files with 1852 additions and 0 deletions

15
README Normal file
View file

@ -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/

29
index.qml Normal file
View file

@ -0,0 +1,29 @@
/**
*
* gPodder QML UI Reference Implementation
* Copyright (c) 2013, Thomas Perl <m@thp.io>
*
* 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 {}
}

227
main.py Normal file
View file

@ -0,0 +1,227 @@
#
# gPodder QML UI Reference Implementation
# Copyright (c) 2013, Thomas Perl <m@thp.io>
#
# 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

37
qml/ButtonArea.qml Normal file
View file

@ -0,0 +1,37 @@
/**
*
* gPodder QML UI Reference Implementation
* Copyright (c) 2013, Thomas Perl <m@thp.io>
*
* 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();
}
}

57
qml/Dragging.qml Normal file
View file

@ -0,0 +1,57 @@
/**
*
* gPodder QML UI Reference Implementation
* Copyright (c) 2013, Thomas Perl <m@thp.io>
*
* 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();
}
}
}
}

65
qml/EpisodeDetail.qml Normal file
View file

@ -0,0 +1,65 @@
/**
*
* gPodder QML UI Reference Implementation
* Copyright (c) 2013, Thomas Perl <m@thp.io>
*
* 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
}
}
}
}

59
qml/EpisodeItem.qml Normal file
View file

@ -0,0 +1,59 @@
/**
*
* gPodder QML UI Reference Implementation
* Copyright (c) 2013, Thomas Perl <m@thp.io>
*
* 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
}
}

70
qml/EpisodesPage.qml Normal file
View file

@ -0,0 +1,70 @@
/**
*
* gPodder QML UI Reference Implementation
* Copyright (c) 2013, Thomas Perl <m@thp.io>
*
* 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});
}
}
}

71
qml/FreshEpisodes.qml Normal file
View file

@ -0,0 +1,71 @@
/**
*
* gPodder QML UI Reference Implementation
* Copyright (c) 2013, Thomas Perl <m@thp.io>
*
* 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});
}
}
}

109
qml/Main.qml Normal file
View file

@ -0,0 +1,109 @@
/**
*
* gPodder QML UI Reference Implementation
* Copyright (c) 2013, Thomas Perl <m@thp.io>
*
* 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<children.length; i++) {
if (children[i] === page) {
index = i;
break;
}
}
children[index-1].opacity = x / width;
}
signal downloading(int episode_id)
signal downloadProgress(int episode_id, real progress)
signal downloaded(int episode_id)
function loadPage(filename, properties) {
var component = Qt.createComponent(filename);
console.log('error: ' + component.errorString());
if (properties === undefined) {
component.createObject(pgst);
} else {
component.createObject(pgst, properties);
}
}
Python {
id: py
Component.onCompleted: {
addImportPath('.');
setHandler('hello', function (version, copyright) {
console.log('gPodder version ' + version + ' starting up');
console.log('Copyright: ' + copyright);
});
setHandler('downloading', pgst.downloading);
setHandler('download-progress', pgst.downloadProgress);
setHandler('downloaded', pgst.downloaded);
// Load the Python side of things
importModule('main', function() {
pgst.ready = true;
});
}
onReceived: {
console.log('unhandled message: ' + data);
}
onError: {
console.log('Python failure: ' + traceback);
}
}
PBusyIndicator {
anchors.centerIn: parent
visible: !pgst.ready
}
StartPage {
id: startPage
visible: pgst.ready
}
}

53
qml/PBusyIndicator.qml Normal file
View file

@ -0,0 +1,53 @@
/**
*
* gPodder QML UI Reference Implementation
* Copyright (c) 2013, Thomas Perl <m@thp.io>
*
* 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
}
}

27
qml/PLabel.qml Normal file
View file

@ -0,0 +1,27 @@
/**
*
* gPodder QML UI Reference Implementation
* Copyright (c) 2013, Thomas Perl <m@thp.io>
*
* 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'
}

34
qml/PListView.qml Normal file
View file

@ -0,0 +1,34 @@
/**
*
* gPodder QML UI Reference Implementation
* Copyright (c) 2013, Thomas Perl <m@thp.io>
*
* 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 }
}

51
qml/PTextField.qml Normal file
View file

@ -0,0 +1,51 @@
/**
*
* gPodder QML UI Reference Implementation
* Copyright (c) 2013, Thomas Perl <m@thp.io>
*
* 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
}
}

71
qml/PodcastItem.qml Normal file
View file

@ -0,0 +1,71 @@
/**
*
* gPodder QML UI Reference Implementation
* Copyright (c) 2013, Thomas Perl <m@thp.io>
*
* 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
}
}

110
qml/PodcastsPage.qml Normal file
View file

@ -0,0 +1,110 @@
/**
*
* gPodder QML UI Reference Implementation
* Copyright (c) 2013, Thomas Perl <m@thp.io>
*
* 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<podcastListModel.count; i++) {
var podcast = podcastListModel.get(i);
if (podcast.id == podcast_id) {
podcastListModel.setProperty(i, 'updating', true);
break;
}
}
});
py.setHandler('updated-podcast', function (podcast) {
for (var i=0; i<podcastListModel.count; i++) {
if (podcastListModel.get(i).id == podcast.id) {
podcastListModel.set(i, podcast);
break;
}
}
});
}
Component.onDestruction: {
py.setHandler('podcast-list-changed', undefined);
py.setHandler('updating-podcast', undefined);
py.setHandler('updated-podcast', undefined);
}
PullMenu {
PullMenuItem {
source: 'images/play.png'
}
PullMenuItem {
source: 'images/search.png'
onClicked: {
podcastsPage.unPull();
py.call('main.check_for_episodes');
}
}
PullMenuItem {
source: 'images/subscriptions.png'
onClicked: {
pgst.loadPage('Subscribe.qml');
podcastsPage.unPull();
}
}
}
PLabel {
id: loading
anchors.centerIn: parent
text: 'Loading'
}
PListView {
id: podcastList
title: 'Subscriptions'
section.property: 'section'
section.delegate: SectionHeader { text: section }
model: ListModel { id: podcastListModel }
delegate: PodcastItem {
onClicked: pgst.loadPage('EpisodesPage.qml', {'podcast_id': id, 'title': title});
}
}
}

35
qml/PullMenu.qml Normal file
View file

@ -0,0 +1,35 @@
/**
*
* gPodder QML UI Reference Implementation
* Copyright (c) 2013, Thomas Perl <m@thp.io>
*
* 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
}

38
qml/PullMenuItem.qml Normal file
View file

@ -0,0 +1,38 @@
/**
*
* gPodder QML UI Reference Implementation
* Copyright (c) 2013, Thomas Perl <m@thp.io>
*
* 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();
}
}

39
qml/SectionHeader.qml Normal file
View file

@ -0,0 +1,39 @@
/**
*
* gPodder QML UI Reference Implementation
* Copyright (c) 2013, Thomas Perl <m@thp.io>
*
* 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'
}
}

33
qml/Settings.qml Normal file
View file

@ -0,0 +1,33 @@
/**
*
* gPodder QML UI Reference Implementation
* Copyright (c) 2013, Thomas Perl <m@thp.io>
*
* 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'
}
}

92
qml/SlidePage.qml Normal file
View file

@ -0,0 +1,92 @@
/**
*
* gPodder QML UI Reference Implementation
* Copyright (c) 2013, Thomas Perl <m@thp.io>
*
* 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
}
}

60
qml/SlidePageHeader.qml Normal file
View file

@ -0,0 +1,60 @@
/**
*
* gPodder QML UI Reference Implementation
* Copyright (c) 2013, Thomas Perl <m@thp.io>
*
* 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
}
}

63
qml/Stacking.qml Normal file
View file

@ -0,0 +1,63 @@
/**
*
* gPodder QML UI Reference Implementation
* Copyright (c) 2013, Thomas Perl <m@thp.io>
*
* 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();
}
}

235
qml/StartPage.qml Normal file
View file

@ -0,0 +1,235 @@
/**
*
* gPodder QML UI Reference Implementation
* Copyright (c) 2013, Thomas Perl <m@thp.io>
*
* 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 } }
}
}
}
}
}
}

42
qml/StartPageButton.qml Normal file
View file

@ -0,0 +1,42 @@
/**
*
* gPodder QML UI Reference Implementation
* Copyright (c) 2013, Thomas Perl <m@thp.io>
*
* 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
}
}

64
qml/Subscribe.qml Normal file
View file

@ -0,0 +1,64 @@
/**
*
* gPodder QML UI Reference Implementation
* Copyright (c) 2013, Thomas Perl <m@thp.io>
*
* 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
}
}
}

33
qml/constants.js Normal file
View file

@ -0,0 +1,33 @@
/**
*
* gPodder QML UI Reference Implementation
* Copyright (c) 2013, Thomas Perl <m@thp.io>
*
* 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 */
};

BIN
qml/images/delete.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 394 B

BIN
qml/images/gpodder.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

BIN
qml/images/mask.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

BIN
qml/images/noise.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

BIN
qml/images/pageshadow.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 B

BIN
qml/images/play.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
qml/images/search.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

33
qml/util.js Normal file
View file

@ -0,0 +1,33 @@
/**
*
* gPodder QML UI Reference Implementation
* Copyright (c) 2013, Thomas Perl <m@thp.io>
*
* 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; i++) {
if (model.count < i) {
model.append(data[i]);
} else {
model.set(i, data[i]);
}
}
while (model.count > data.length) {
model.remove(model.count-1);
}
}