Various bugfixes, support for filtering episode lists
This commit is contained in:
parent
431ca519d3
commit
0da88e3577
17 changed files with 421 additions and 45 deletions
|
@ -26,24 +26,66 @@ import 'constants.js' as Constants
|
||||||
ListModel {
|
ListModel {
|
||||||
id: episodeListModel
|
id: episodeListModel
|
||||||
|
|
||||||
property var podcast_id
|
property var podcast_id: -1
|
||||||
|
|
||||||
function loadEpisodes(podcast_id) {
|
property var queries: ({
|
||||||
episodeListModel.podcast_id = podcast_id;
|
All: '',
|
||||||
reload();
|
Fresh: 'new or downloading',
|
||||||
|
Downloaded: 'downloaded or downloading',
|
||||||
|
HideDeleted: 'not deleted',
|
||||||
|
Deleted: 'deleted',
|
||||||
|
})
|
||||||
|
|
||||||
|
property var filters: ([
|
||||||
|
{ label: 'All', query: episodeListModel.queries.All },
|
||||||
|
{ label: 'Fresh', query: episodeListModel.queries.Fresh },
|
||||||
|
{ label: 'Downloaded', query: episodeListModel.queries.Downloaded },
|
||||||
|
{ label: 'Hide deleted', query: episodeListModel.queries.HideDeleted },
|
||||||
|
{ label: 'Deleted', query: episodeListModel.queries.Deleted },
|
||||||
|
])
|
||||||
|
|
||||||
|
property bool ready: false
|
||||||
|
property int currentFilterIndex: -1
|
||||||
|
property string currentCustomQuery: queries.All
|
||||||
|
|
||||||
|
function setQuery(query) {
|
||||||
|
for (var i=0; i<filters.length; i++) {
|
||||||
|
if (filters[i].query === query) {
|
||||||
|
currentFilterIndex = i;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
currentFilterIndex = -1;
|
||||||
|
currentCustomQuery = query;
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadFreshEpisodes(callback) {
|
function loadAllEpisodes(callback) {
|
||||||
episodeListModel.podcast_id = -1;
|
episodeListModel.podcast_id = -1;
|
||||||
py.call('main.get_fresh_episodes', [], function (episodes) {
|
reload(callback);
|
||||||
Util.updateModelFrom(episodeListModel, episodes);
|
|
||||||
callback();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function reload() {
|
function loadEpisodes(podcast_id, callback) {
|
||||||
py.call('main.load_episodes', [podcast_id], function (episodes) {
|
episodeListModel.podcast_id = podcast_id;
|
||||||
|
reload(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
function reload(callback) {
|
||||||
|
var query;
|
||||||
|
|
||||||
|
if (currentFilterIndex !== -1) {
|
||||||
|
query = filters[currentFilterIndex].query;
|
||||||
|
} else {
|
||||||
|
query = currentCustomQuery;
|
||||||
|
}
|
||||||
|
|
||||||
|
ready = false;
|
||||||
|
py.call('main.load_episodes', [podcast_id, query], function (episodes) {
|
||||||
Util.updateModelFrom(episodeListModel, episodes);
|
Util.updateModelFrom(episodeListModel, episodes);
|
||||||
|
episodeListModel.ready = true;
|
||||||
|
if (callback !== undefined) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,7 @@ var colors = {
|
||||||
destructive: '#cf424f', /* destructive actions */
|
destructive: '#cf424f', /* destructive actions */
|
||||||
|
|
||||||
page: '#dddddd',
|
page: '#dddddd',
|
||||||
|
dialogBackground: '#aa000000',
|
||||||
text: '#333333', /* text color */
|
text: '#333333', /* text color */
|
||||||
highlight: '#433b67',
|
highlight: '#433b67',
|
||||||
secondaryHighlight: '#605885',
|
secondaryHighlight: '#605885',
|
||||||
|
|
39
main.py
39
main.py
|
@ -31,6 +31,7 @@ import gpodder
|
||||||
|
|
||||||
from gpodder.api import core
|
from gpodder.api import core
|
||||||
from gpodder.api import util
|
from gpodder.api import util
|
||||||
|
from gpodder.api import query
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import functools
|
import functools
|
||||||
|
@ -55,6 +56,8 @@ def run_in_background_thread(f):
|
||||||
|
|
||||||
|
|
||||||
class gPotherSide:
|
class gPotherSide:
|
||||||
|
ALL_PODCASTS = -1
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.core = None
|
self.core = None
|
||||||
self._checking_for_new_episodes = False
|
self._checking_for_new_episodes = False
|
||||||
|
@ -149,9 +152,26 @@ class gPotherSide:
|
||||||
'hasShownotes': episode.description != '',
|
'hasShownotes': episode.description != '',
|
||||||
}
|
}
|
||||||
|
|
||||||
def load_episodes(self, id):
|
def load_episodes(self, id=ALL_PODCASTS, eql=None):
|
||||||
podcast = self._get_podcast_by_id(id)
|
if id is not None and id != self.ALL_PODCASTS:
|
||||||
return [self.convert_episode(episode) for episode in podcast.episodes]
|
podcasts = [self._get_podcast_by_id(id)]
|
||||||
|
else:
|
||||||
|
podcasts = self.core.model.get_podcasts()
|
||||||
|
|
||||||
|
if eql:
|
||||||
|
filter_func = query.EQL(eql).filter
|
||||||
|
else:
|
||||||
|
filter_func = lambda episodes: episodes
|
||||||
|
|
||||||
|
result = []
|
||||||
|
|
||||||
|
for podcast in podcasts:
|
||||||
|
result.extend(filter_func(podcast.episodes))
|
||||||
|
|
||||||
|
if id == self.ALL_PODCASTS:
|
||||||
|
result.sort(key=lambda e: e.published, reverse=True)
|
||||||
|
|
||||||
|
return [self.convert_episode(episode) for episode in result]
|
||||||
|
|
||||||
def get_fresh_episodes_summary(self, count):
|
def get_fresh_episodes_summary(self, count):
|
||||||
summary = []
|
summary = []
|
||||||
|
@ -167,19 +187,9 @@ class gPotherSide:
|
||||||
summary.sort(key=lambda e: e['newEpisodes'], reverse=True)
|
summary.sort(key=lambda e: e['newEpisodes'], reverse=True)
|
||||||
return summary[:int(count)]
|
return summary[:int(count)]
|
||||||
|
|
||||||
def 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_episode(e) for e in fresh_episodes]
|
|
||||||
|
|
||||||
@run_in_background_thread
|
@run_in_background_thread
|
||||||
def subscribe(self, url):
|
def subscribe(self, url):
|
||||||
url = util.normalize_feed_url(url)
|
url = self.core.model.normalize_feed_url(url)
|
||||||
# TODO: Check if subscription already exists
|
# TODO: Check if subscription already exists
|
||||||
self.core.model.load_podcast(url, create=True)
|
self.core.model.load_podcast(url, create=True)
|
||||||
self.core.save()
|
self.core.save()
|
||||||
|
@ -324,7 +334,6 @@ subscribe = gpotherside.subscribe
|
||||||
unsubscribe = gpotherside.unsubscribe
|
unsubscribe = gpotherside.unsubscribe
|
||||||
check_for_episodes = gpotherside.check_for_episodes
|
check_for_episodes = gpotherside.check_for_episodes
|
||||||
get_stats = gpotherside.get_stats
|
get_stats = gpotherside.get_stats
|
||||||
get_fresh_episodes = gpotherside.get_fresh_episodes
|
|
||||||
get_fresh_episodes_summary = gpotherside.get_fresh_episodes_summary
|
get_fresh_episodes_summary = gpotherside.get_fresh_episodes_summary
|
||||||
download_episode = gpotherside.download_episode
|
download_episode = gpotherside.download_episode
|
||||||
delete_episode = gpotherside.delete_episode
|
delete_episode = gpotherside.delete_episode
|
||||||
|
|
2
makefile
2
makefile
|
@ -13,7 +13,7 @@ dist/$(PROJECT)-$(VERSION).tar.gz:
|
||||||
git archive --format=tar --prefix=$(PROJECT)-$(VERSION)/ $(VERSION) | gzip >$@
|
git archive --format=tar --prefix=$(PROJECT)-$(VERSION)/ $(VERSION) | gzip >$@
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
find . -name '__pycache__' -exec rm {} +
|
find . -name '__pycache__' -exec rm -rf {} +
|
||||||
|
|
||||||
distclean: clean
|
distclean: clean
|
||||||
rm -rf dist
|
rm -rf dist
|
||||||
|
|
59
touch/Dialog.qml
Normal file
59
touch/Dialog.qml
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* gPodder QML UI Reference Implementation
|
||||||
|
* Copyright (c) 2014, 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 'common/constants.js' as Constants
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: page
|
||||||
|
color: Constants.colors.dialogBackground
|
||||||
|
|
||||||
|
default property alias children: contents.children
|
||||||
|
property bool isDialog: true
|
||||||
|
property int contentHeight: -1
|
||||||
|
|
||||||
|
function closePage() {
|
||||||
|
stacking.startFadeOut();
|
||||||
|
}
|
||||||
|
|
||||||
|
onXChanged: pgst.update(page, x)
|
||||||
|
|
||||||
|
width: parent.width
|
||||||
|
height: parent.height
|
||||||
|
|
||||||
|
DialogStacking { id: stacking }
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
onClicked: page.closePage();
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: contents
|
||||||
|
property int border: parent.width * 0.1
|
||||||
|
width: parent.width - 2 * border
|
||||||
|
property int maxHeight: parent.height - 4 * border
|
||||||
|
height: ((page.contentHeight > 0 && page.contentHeight < maxHeight) ? page.contentHeight : maxHeight) * parent.opacity
|
||||||
|
anchors.centerIn: parent
|
||||||
|
color: Constants.colors.page
|
||||||
|
clip: true
|
||||||
|
}
|
||||||
|
}
|
70
touch/DialogStacking.qml
Normal file
70
touch/DialogStacking.qml
Normal 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
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: stacking
|
||||||
|
property variant page: parent
|
||||||
|
|
||||||
|
PropertyAnimation {
|
||||||
|
id: fadeIn
|
||||||
|
target: stacking.page
|
||||||
|
property: 'opacity'
|
||||||
|
to: 1
|
||||||
|
duration: 500
|
||||||
|
easing.type: Easing.OutCubic
|
||||||
|
|
||||||
|
onStopped: {
|
||||||
|
pgst.loadPageInProgress = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PropertyAnimation {
|
||||||
|
id: fadeOut
|
||||||
|
target: stacking.page
|
||||||
|
property: 'opacity'
|
||||||
|
to: 0
|
||||||
|
duration: 500
|
||||||
|
easing.type: Easing.OutCubic
|
||||||
|
}
|
||||||
|
|
||||||
|
function startFadeOut() {
|
||||||
|
fadeOut.start();
|
||||||
|
page.destroy(500);
|
||||||
|
}
|
||||||
|
|
||||||
|
function fadeInAgain() {
|
||||||
|
fadeIn.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopAllAnimations() {
|
||||||
|
fadeIn.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
if (pgst.loadPageInProgress) {
|
||||||
|
page.x = 0;
|
||||||
|
page.opacity = 0;
|
||||||
|
fadeIn.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
46
touch/EpisodeQueryControl.qml
Normal file
46
touch/EpisodeQueryControl.qml
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* gPodder QML UI Reference Implementation
|
||||||
|
* Copyright (c) 2014, 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: episodeQueryControl
|
||||||
|
|
||||||
|
property var model
|
||||||
|
property string title
|
||||||
|
|
||||||
|
function showSelectionDialog() {
|
||||||
|
pgst.loadPage('SelectionDialog.qml', {
|
||||||
|
title: episodeQueryControl.title,
|
||||||
|
callback: function (index, result) {
|
||||||
|
episodeQueryControl.model.currentFilterIndex = index;
|
||||||
|
episodeQueryControl.model.reload();
|
||||||
|
},
|
||||||
|
items: function () {
|
||||||
|
var labels = [];
|
||||||
|
for (var i in episodeQueryControl.model.filters) {
|
||||||
|
labels.push(episodeQueryControl.model.filters[i].label);
|
||||||
|
}
|
||||||
|
return labels;
|
||||||
|
}(),
|
||||||
|
selectedIndex: episodeQueryControl.model.currentFilterIndex,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,28 +22,42 @@ import QtQuick 2.0
|
||||||
|
|
||||||
import 'common'
|
import 'common'
|
||||||
import 'common/util.js' as Util
|
import 'common/util.js' as Util
|
||||||
|
import 'common/constants.js' as Constants
|
||||||
|
import 'icons/icons.js' as Icons
|
||||||
|
|
||||||
SlidePage {
|
SlidePage {
|
||||||
id: freshEpisodes
|
id: allEpisodesPage
|
||||||
property bool ready: false
|
|
||||||
|
EpisodeQueryControl {
|
||||||
|
id: queryControl
|
||||||
|
model: episodesListModel
|
||||||
|
title: 'Select filter'
|
||||||
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
freshEpisodesListModel.loadFreshEpisodes(function () {
|
episodesListModel.setQuery(episodesListModel.queries.Fresh);
|
||||||
freshEpisodes.ready = true;
|
episodesListModel.reload();
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PBusyIndicator {
|
PBusyIndicator {
|
||||||
visible: !freshEpisodes.ready
|
visible: !episodesListModel.ready
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
}
|
}
|
||||||
|
|
||||||
PListView {
|
PListView {
|
||||||
id: episodeList
|
id: episodeList
|
||||||
property int selectedIndex: -1
|
property int selectedIndex: -1
|
||||||
title: 'Fresh episodes'
|
title: 'Episodes'
|
||||||
|
headerHasIcon: true
|
||||||
|
headerIconText: 'Filter'
|
||||||
|
onHeaderIconClicked: queryControl.showSelectionDialog();
|
||||||
|
|
||||||
model: GPodderEpisodeListModel { id: freshEpisodesListModel }
|
PPlaceholder {
|
||||||
|
text: 'No episodes found'
|
||||||
|
visible: episodeList.count === 0 && episodesListModel.ready
|
||||||
|
}
|
||||||
|
|
||||||
|
model: GPodderEpisodeListModel { id: episodesListModel }
|
||||||
|
|
||||||
section.property: 'published'
|
section.property: 'published'
|
||||||
section.delegate: SectionHeader { text: section }
|
section.delegate: SectionHeader { text: section }
|
||||||
|
@ -51,4 +65,3 @@ SlidePage {
|
||||||
delegate: EpisodeItem { }
|
delegate: EpisodeItem { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,17 @@ SlidePage {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: parent.height
|
height: parent.height
|
||||||
|
|
||||||
Component.onCompleted: episodeListModel.loadEpisodes(podcast_id);
|
Component.onCompleted: {
|
||||||
|
episodeListModel.podcast_id = podcast_id;
|
||||||
|
episodeListModel.setQuery(episodeListModel.queries.All);
|
||||||
|
episodeListModel.reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
EpisodeQueryControl {
|
||||||
|
id: queryControl
|
||||||
|
model: episodeListModel
|
||||||
|
title: 'Select filter'
|
||||||
|
}
|
||||||
|
|
||||||
PullMenu {
|
PullMenu {
|
||||||
PullMenuItem {
|
PullMenuItem {
|
||||||
|
@ -81,6 +91,10 @@ SlidePage {
|
||||||
title: episodesPage.title
|
title: episodesPage.title
|
||||||
model: GPodderEpisodeListModel { id: episodeListModel }
|
model: GPodderEpisodeListModel { id: episodeListModel }
|
||||||
|
|
||||||
|
headerHasIcon: true
|
||||||
|
headerIconText: 'Filter'
|
||||||
|
onHeaderIconClicked: queryControl.showSelectionDialog();
|
||||||
|
|
||||||
PPlaceholder {
|
PPlaceholder {
|
||||||
text: 'No episodes'
|
text: 'No episodes'
|
||||||
visible: episodeList.count === 0
|
visible: episodeList.count === 0
|
||||||
|
|
|
@ -42,7 +42,11 @@ Item {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
children[index-1].opacity = x / width;
|
if (page.isDialog) {
|
||||||
|
children[index-1].opacity = 1;
|
||||||
|
} else {
|
||||||
|
children[index-1].opacity = x / width;
|
||||||
|
}
|
||||||
//children[index-1].pushPhase = x / width;
|
//children[index-1].pushPhase = x / width;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,9 +27,19 @@ ListView {
|
||||||
|
|
||||||
property string title
|
property string title
|
||||||
property real pushPhase: 0
|
property real pushPhase: 0
|
||||||
|
property bool headerHasIcon: false
|
||||||
|
property string headerIconText
|
||||||
|
|
||||||
|
signal headerIconClicked()
|
||||||
|
|
||||||
boundsBehavior: Flickable.StopAtBounds
|
boundsBehavior: Flickable.StopAtBounds
|
||||||
header: SlidePageHeader { title: pListView.title }
|
|
||||||
|
header: SlidePageHeader {
|
||||||
|
title: pListView.title
|
||||||
|
hasIcon: pListView.headerHasIcon
|
||||||
|
iconText: pListView.headerIconText
|
||||||
|
onIconClicked: pListView.headerIconClicked()
|
||||||
|
}
|
||||||
|
|
||||||
PScrollDecorator { flickable: pListView }
|
PScrollDecorator { flickable: pListView }
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,5 +26,5 @@ import 'common/constants.js' as Constants
|
||||||
PLabel {
|
PLabel {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
font.pixelSize: 40 * pgst.scalef
|
font.pixelSize: 40 * pgst.scalef
|
||||||
color: Constants.colors.text
|
color: Constants.colors.placeholder
|
||||||
}
|
}
|
||||||
|
|
84
touch/SelectionDialog.qml
Normal file
84
touch/SelectionDialog.qml
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* gPodder QML UI Reference Implementation
|
||||||
|
* Copyright (c) 2014, 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 'common/constants.js' as Constants
|
||||||
|
import 'icons/icons.js' as Icons
|
||||||
|
|
||||||
|
Dialog {
|
||||||
|
id: selectionDialog
|
||||||
|
|
||||||
|
property string title: 'Dialog'
|
||||||
|
property var callback: undefined
|
||||||
|
property var items: ([])
|
||||||
|
property var selectedIndex: -1
|
||||||
|
|
||||||
|
contentHeight: selectionDialogFlickable.contentHeight
|
||||||
|
|
||||||
|
Flickable {
|
||||||
|
id: selectionDialogFlickable
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
contentHeight: contentColumn.height
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: contentColumn
|
||||||
|
width: parent.width
|
||||||
|
|
||||||
|
SlidePageHeader {
|
||||||
|
id: header
|
||||||
|
color: Constants.colors.highlight
|
||||||
|
title: selectionDialog.title
|
||||||
|
}
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: selectionDialog.items
|
||||||
|
|
||||||
|
delegate: ButtonArea {
|
||||||
|
width: parent.width
|
||||||
|
height: 60 * pgst.scalef
|
||||||
|
|
||||||
|
transparent: (index != selectionDialog.selectedIndex)
|
||||||
|
|
||||||
|
PLabel {
|
||||||
|
anchors {
|
||||||
|
left: parent.left
|
||||||
|
verticalCenter: parent.verticalCenter
|
||||||
|
margins: 20 * pgst.scalef
|
||||||
|
}
|
||||||
|
|
||||||
|
text: modelData
|
||||||
|
color: (index == selectionDialog.selectedIndex) ? Constants.colors.highlight : Constants.colors.text
|
||||||
|
}
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
if (selectionDialog.callback !== undefined) {
|
||||||
|
selectionDialog.callback(index, modelData);
|
||||||
|
}
|
||||||
|
selectionDialog.closePage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PScrollDecorator { flickable: selectionDialogFlickable }
|
||||||
|
}
|
|
@ -30,6 +30,7 @@ Rectangle {
|
||||||
property alias hasPull: dragging.hasPull
|
property alias hasPull: dragging.hasPull
|
||||||
property alias canClose: dragging.canClose
|
property alias canClose: dragging.canClose
|
||||||
property real pullPhase: (x >= 0) ? 0 : (-x / (width / 4))
|
property real pullPhase: (x >= 0) ? 0 : (-x / (width / 4))
|
||||||
|
property bool isDialog: false
|
||||||
|
|
||||||
function unPull() {
|
function unPull() {
|
||||||
stacking.fadeInAgain();
|
stacking.fadeInAgain();
|
||||||
|
|
|
@ -21,22 +21,45 @@
|
||||||
import QtQuick 2.0
|
import QtQuick 2.0
|
||||||
|
|
||||||
import 'common/constants.js' as Constants
|
import 'common/constants.js' as Constants
|
||||||
|
import 'icons/icons.js' as Icons
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: slidePageHeader
|
id: slidePageHeader
|
||||||
property alias title: label.text
|
property alias title: label.text
|
||||||
property alias color: label.color
|
property alias color: label.color
|
||||||
|
|
||||||
|
property alias hasIcon: icon.visible
|
||||||
|
property alias iconText: icon.text
|
||||||
|
signal iconClicked()
|
||||||
|
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: Constants.layout.header.height * pgst.scalef
|
height: Constants.layout.header.height * pgst.scalef
|
||||||
|
|
||||||
|
IconMenuItem {
|
||||||
|
id: icon
|
||||||
|
|
||||||
|
visible: false
|
||||||
|
enabled: visible
|
||||||
|
|
||||||
|
text: 'Search'
|
||||||
|
icon: Icons.magnifying_glass
|
||||||
|
color: label.color
|
||||||
|
|
||||||
|
anchors {
|
||||||
|
left: parent.left
|
||||||
|
verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
onClicked: slidePageHeader.iconClicked()
|
||||||
|
}
|
||||||
|
|
||||||
PLabel {
|
PLabel {
|
||||||
id: label
|
id: label
|
||||||
anchors {
|
anchors {
|
||||||
left: parent.left
|
left: icon.right
|
||||||
right: parent.right
|
right: parent.right
|
||||||
rightMargin: 20 * pgst.scalef
|
rightMargin: 20 * pgst.scalef
|
||||||
leftMargin: 70 * pgst.scalef
|
leftMargin: 20 * pgst.scalef
|
||||||
verticalCenter: parent.verticalCenter
|
verticalCenter: parent.verticalCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -134,10 +134,9 @@ SlidePage {
|
||||||
|
|
||||||
StartPageButton {
|
StartPageButton {
|
||||||
id: freshEpisodes
|
id: freshEpisodes
|
||||||
enabled: freshEpisodesRepeater.count > 0
|
|
||||||
|
|
||||||
title: py.refreshing ? 'Refreshing feeds' : 'Fresh episodes'
|
title: py.refreshing ? 'Refreshing feeds' : 'Episodes'
|
||||||
onClicked: pgst.loadPage('FreshEpisodes.qml');
|
onClicked: pgst.loadPage('EpisodeQueryPage.qml');
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
id: freshEpisodesRow
|
id: freshEpisodesRow
|
||||||
|
|
|
@ -16,3 +16,4 @@ var aperture = '\ue026';
|
||||||
var eye = '\ue025';
|
var eye = '\ue025';
|
||||||
var loop_alt2 = '\ue033';
|
var loop_alt2 = '\ue033';
|
||||||
var folder = '\ue065';
|
var folder = '\ue065';
|
||||||
|
var magnifying_glass = '\ue074';
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue