From 0da88e35771d53efc501c4c6ea6e250668bf5111 Mon Sep 17 00:00:00 2001 From: Thomas Perl Date: Sat, 15 Mar 2014 15:44:00 +0100 Subject: [PATCH] Various bugfixes, support for filtering episode lists --- common/GPodderEpisodeListModel.qml | 64 +++++++++++--- common/constants.js | 1 + main.py | 39 +++++---- makefile | 2 +- touch/Dialog.qml | 59 +++++++++++++ touch/DialogStacking.qml | 70 ++++++++++++++++ touch/EpisodeQueryControl.qml | 46 ++++++++++ ...FreshEpisodes.qml => EpisodeQueryPage.qml} | 31 +++++-- touch/EpisodesPage.qml | 16 +++- touch/Main.qml | 6 +- touch/PListView.qml | 12 ++- touch/PPlaceholder.qml | 2 +- touch/SelectionDialog.qml | 84 +++++++++++++++++++ touch/SlidePage.qml | 1 + touch/SlidePageHeader.qml | 27 +++++- touch/StartPage.qml | 5 +- touch/icons/icons.js | 1 + 17 files changed, 421 insertions(+), 45 deletions(-) create mode 100644 touch/Dialog.qml create mode 100644 touch/DialogStacking.qml create mode 100644 touch/EpisodeQueryControl.qml rename touch/{FreshEpisodes.qml => EpisodeQueryPage.qml} (62%) create mode 100644 touch/SelectionDialog.qml diff --git a/common/GPodderEpisodeListModel.qml b/common/GPodderEpisodeListModel.qml index 06a351a..bc467e0 100644 --- a/common/GPodderEpisodeListModel.qml +++ b/common/GPodderEpisodeListModel.qml @@ -26,24 +26,66 @@ import 'constants.js' as Constants ListModel { id: episodeListModel - property var podcast_id + property var podcast_id: -1 - function loadEpisodes(podcast_id) { - episodeListModel.podcast_id = podcast_id; - reload(); + property var queries: ({ + All: '', + 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$@ clean: - find . -name '__pycache__' -exec rm {} + + find . -name '__pycache__' -exec rm -rf {} + distclean: clean rm -rf dist diff --git a/touch/Dialog.qml b/touch/Dialog.qml new file mode 100644 index 0000000..e5208e6 --- /dev/null +++ b/touch/Dialog.qml @@ -0,0 +1,59 @@ + +/** + * + * gPodder QML UI Reference Implementation + * Copyright (c) 2014, 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 '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 + } +} diff --git a/touch/DialogStacking.qml b/touch/DialogStacking.qml new file mode 100644 index 0000000..0752071 --- /dev/null +++ b/touch/DialogStacking.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 + +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(); + } + } +} + diff --git a/touch/EpisodeQueryControl.qml b/touch/EpisodeQueryControl.qml new file mode 100644 index 0000000..63a2399 --- /dev/null +++ b/touch/EpisodeQueryControl.qml @@ -0,0 +1,46 @@ + +/** + * + * gPodder QML UI Reference Implementation + * Copyright (c) 2014, 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: 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, + }); + } +} diff --git a/touch/FreshEpisodes.qml b/touch/EpisodeQueryPage.qml similarity index 62% rename from touch/FreshEpisodes.qml rename to touch/EpisodeQueryPage.qml index 611a0ba..841fc49 100644 --- a/touch/FreshEpisodes.qml +++ b/touch/EpisodeQueryPage.qml @@ -22,28 +22,42 @@ import QtQuick 2.0 import 'common' import 'common/util.js' as Util +import 'common/constants.js' as Constants +import 'icons/icons.js' as Icons SlidePage { - id: freshEpisodes - property bool ready: false + id: allEpisodesPage + + EpisodeQueryControl { + id: queryControl + model: episodesListModel + title: 'Select filter' + } Component.onCompleted: { - freshEpisodesListModel.loadFreshEpisodes(function () { - freshEpisodes.ready = true; - }); + episodesListModel.setQuery(episodesListModel.queries.Fresh); + episodesListModel.reload(); } PBusyIndicator { - visible: !freshEpisodes.ready + visible: !episodesListModel.ready anchors.centerIn: parent } PListView { id: episodeList 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.delegate: SectionHeader { text: section } @@ -51,4 +65,3 @@ SlidePage { delegate: EpisodeItem { } } } - diff --git a/touch/EpisodesPage.qml b/touch/EpisodesPage.qml index e4f879a..32524d7 100644 --- a/touch/EpisodesPage.qml +++ b/touch/EpisodesPage.qml @@ -36,7 +36,17 @@ SlidePage { width: parent.width 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 { PullMenuItem { @@ -81,6 +91,10 @@ SlidePage { title: episodesPage.title model: GPodderEpisodeListModel { id: episodeListModel } + headerHasIcon: true + headerIconText: 'Filter' + onHeaderIconClicked: queryControl.showSelectionDialog(); + PPlaceholder { text: 'No episodes' visible: episodeList.count === 0 diff --git a/touch/Main.qml b/touch/Main.qml index 3d75ff0..7135804 100644 --- a/touch/Main.qml +++ b/touch/Main.qml @@ -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; } diff --git a/touch/PListView.qml b/touch/PListView.qml index 670b2ce..0947f19 100644 --- a/touch/PListView.qml +++ b/touch/PListView.qml @@ -27,9 +27,19 @@ ListView { property string title property real pushPhase: 0 + property bool headerHasIcon: false + property string headerIconText + + signal headerIconClicked() 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 } } diff --git a/touch/PPlaceholder.qml b/touch/PPlaceholder.qml index 960c59c..e05cba9 100644 --- a/touch/PPlaceholder.qml +++ b/touch/PPlaceholder.qml @@ -26,5 +26,5 @@ import 'common/constants.js' as Constants PLabel { anchors.centerIn: parent font.pixelSize: 40 * pgst.scalef - color: Constants.colors.text + color: Constants.colors.placeholder } diff --git a/touch/SelectionDialog.qml b/touch/SelectionDialog.qml new file mode 100644 index 0000000..3f66119 --- /dev/null +++ b/touch/SelectionDialog.qml @@ -0,0 +1,84 @@ + +/** + * + * gPodder QML UI Reference Implementation + * Copyright (c) 2014, 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 '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 } +} diff --git a/touch/SlidePage.qml b/touch/SlidePage.qml index ef873d0..c16dc5f 100644 --- a/touch/SlidePage.qml +++ b/touch/SlidePage.qml @@ -30,6 +30,7 @@ Rectangle { property alias hasPull: dragging.hasPull property alias canClose: dragging.canClose property real pullPhase: (x >= 0) ? 0 : (-x / (width / 4)) + property bool isDialog: false function unPull() { stacking.fadeInAgain(); diff --git a/touch/SlidePageHeader.qml b/touch/SlidePageHeader.qml index 45a8379..a3d6c92 100644 --- a/touch/SlidePageHeader.qml +++ b/touch/SlidePageHeader.qml @@ -21,22 +21,45 @@ import QtQuick 2.0 import 'common/constants.js' as Constants +import 'icons/icons.js' as Icons Item { id: slidePageHeader property alias title: label.text property alias color: label.color + property alias hasIcon: icon.visible + property alias iconText: icon.text + signal iconClicked() + width: parent.width 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 { id: label anchors { - left: parent.left + left: icon.right right: parent.right rightMargin: 20 * pgst.scalef - leftMargin: 70 * pgst.scalef + leftMargin: 20 * pgst.scalef verticalCenter: parent.verticalCenter } diff --git a/touch/StartPage.qml b/touch/StartPage.qml index a3f3d21..5b22f0f 100644 --- a/touch/StartPage.qml +++ b/touch/StartPage.qml @@ -134,10 +134,9 @@ SlidePage { StartPageButton { id: freshEpisodes - enabled: freshEpisodesRepeater.count > 0 - title: py.refreshing ? 'Refreshing feeds' : 'Fresh episodes' - onClicked: pgst.loadPage('FreshEpisodes.qml'); + title: py.refreshing ? 'Refreshing feeds' : 'Episodes' + onClicked: pgst.loadPage('EpisodeQueryPage.qml'); Row { id: freshEpisodesRow diff --git a/touch/icons/icons.js b/touch/icons/icons.js index e7605e9..8548881 100644 --- a/touch/icons/icons.js +++ b/touch/icons/icons.js @@ -16,3 +16,4 @@ var aperture = '\ue026'; var eye = '\ue025'; var loop_alt2 = '\ue033'; var folder = '\ue065'; +var magnifying_glass = '\ue074';