From f7a28cace78ffab9fd775c210d836f21c6e1b33c Mon Sep 17 00:00:00 2001 From: Thilo Kogge Date: Sun, 11 Apr 2021 10:52:39 +0200 Subject: [PATCH 01/20] basic search implementation in python --- python/podcast/podpost.py | 2 +- python/podcast/search/Filter.py | 58 ++++++++++++++++++++++++++++ python/podcast/search/Order.py | 51 ++++++++++++++++++++++++ python/podcast/search/SearchBase.py | 60 +++++++++++++++++++++++++++++ python/podcast/search/__init__.py | 0 test/test_searchbase.py | 29 ++++++++++++++ 6 files changed, 199 insertions(+), 1 deletion(-) create mode 100644 python/podcast/search/Filter.py create mode 100644 python/podcast/search/Order.py create mode 100644 python/podcast/search/SearchBase.py create mode 100644 python/podcast/search/__init__.py create mode 100644 test/test_searchbase.py diff --git a/python/podcast/podpost.py b/python/podcast/podpost.py index 6c5708a..579f811 100644 --- a/python/podcast/podpost.py +++ b/python/podcast/podpost.py @@ -68,7 +68,7 @@ class Podpost(BaseModel): published = DateTimeField() # play_state state: int = IntegerField(default=STOP) - title: str = TextField() + title: TextField = TextField() # mimetype type: str = TextField(null=True) diff --git a/python/podcast/search/Filter.py b/python/podcast/search/Filter.py new file mode 100644 index 0000000..76b1bd8 --- /dev/null +++ b/python/podcast/search/Filter.py @@ -0,0 +1,58 @@ +from abc import abstractmethod, ABC +from typing import Dict + +from peewee import ModelSelect, Expression +from podcast.podcast import Podcast +from podcast.podpost import Podpost + + +class Filter(ABC): + def apply_filter(self, query: ModelSelect, search_env: Dict) -> ModelSelect: + return query.where(self.get_where_expression(search_env)) + + @abstractmethod + def get_where_expression(self, search_env: Dict) -> Expression: + raise ValueError("Do not call abstract method") + + +class NoFilter(Filter): + def apply_filter(self, query: ModelSelect, _) -> ModelSelect: + return query + + +class PodcastFilter(Filter): + def __init__(self, podcast: Podcast) -> None: + super().__init__() + self.podcast = podcast + + def get_where_expression(self, search_env: Dict) -> Expression: + return Podpost.podcast == self.podcast + + +class PodpostTitleFilter(Filter): + + def __init__(self, searchstring: str) -> None: + super().__init__() + self.searchstring = searchstring + + def get_where_expression(self, search_env: Dict) -> Expression: + return Podpost.title.contains(self.searchstring) + + +class BiFilter(Filter): + def __init__(self, left: Filter, right: Filter) -> None: + super().__init__() + self.left = left + self.right = right + + @abstractmethod + def get_where_expression(self, search_env: Dict) -> Expression: + raise ValueError("Do not call abstract method") + +class AndFilter(BiFilter): + def get_where_expression(self, search_env: Dict) -> Expression: + return self.left.get_where_expression(search_env) & self.right.get_where_expression(search_env) + +class OrFilter(BiFilter): + def get_where_expression(self, search_env: Dict) -> Expression: + return self.left.get_where_expression(search_env) | self.right.get_where_expression(search_env) diff --git a/python/podcast/search/Order.py b/python/podcast/search/Order.py new file mode 100644 index 0000000..de55621 --- /dev/null +++ b/python/podcast/search/Order.py @@ -0,0 +1,51 @@ +from abc import abstractmethod, ABC +from enum import Enum, auto +from typing import Dict + +from peewee import ModelSelect +from podcast.podpost import Podpost + + +class Direction(Enum): + ASC = auto() + DESC = auto() + + +def handle_order(expression, order: Direction): + if order == Direction.ASC: + return expression.asc() + elif order == Direction.DESC: + return expression.desc() + else: + raise ValueError("Invalid Direction: " + str(order)) + + +class Order(ABC): + dir: Direction + + def __init__(self, dir: Direction) -> None: + self.dir = dir + + @abstractmethod + def apply_order(self, query: ModelSelect, search_env: Dict) -> ModelSelect: + raise ValueError("do not call abstract method") + + +class NoOrder(Order): + def __init__(self) -> None: + super().__init__(Direction.ASC) + + def apply_order(self, query: ModelSelect, _) -> ModelSelect: + return query + + +class PodpostListInsertDateOrder(Order): + def apply_order(self, query: ModelSelect, search_env: Dict) -> ModelSelect: + if not "list_type" in search_env: + raise ValueError("listtype must be set when ordering by it") + return query.order_by(handle_order(search_env["list_type"].insert_date, self.dir)) + + +class PodpostPublishedDateOrder(Order): + def apply_order(self, query: ModelSelect, search_env: Dict) -> ModelSelect: + return query.order_by(handle_order(Podpost.published, self.dir)) diff --git a/python/podcast/search/SearchBase.py b/python/podcast/search/SearchBase.py new file mode 100644 index 0000000..b3dd2c3 --- /dev/null +++ b/python/podcast/search/SearchBase.py @@ -0,0 +1,60 @@ +import logging +from abc import abstractmethod, ABC +from collections import Iterator + +from peewee import ModelSelect +from podcast.inbox import InboxEntry +from podcast.podpost import Podpost +from podcast.search.Order import Order +from podcast.search.Filter import Filter + +logger = logging.getLogger(__name__) + + +class SearchBase(ABC): + + def __init__(self) -> None: + self.search_env = {} + + @abstractmethod + def get_search_base(self) -> ModelSelect: + raise ValueError("do not call abstract method") + + @abstractmethod + def unpack_result_to_podpost(self, result) -> Podpost: + raise ValueError("do not call abstract method") + + def iter_search(self, filter: Filter, order: Order) -> Iterator[Podpost]: + query = self.get_search_base() + query = filter.apply_filter(query, self.search_env) + query = order.apply_order(query, self.search_env) + for result in query.iterator(): + yield self.unpack_result_to_podpost(result) + + +class AllSearchBase(SearchBase): + def get_search_base(self) -> ModelSelect: + return Podpost.select() + + def unpack_result_to_podpost(self, result) -> Podpost: + return result + + +class FavoriteSearchBase(SearchBase): + def get_search_base(self) -> ModelSelect: + return Podpost.select().where(Podpost.favorite == True) + + def unpack_result_to_podpost(self, result) -> Podpost: + return result + + +class InboxSearchBase(SearchBase): + def __init__(self) -> None: + super().__init__() + self.search_env['list_type'] = InboxEntry + + def get_search_base(self) -> ModelSelect: + return InboxEntry.select(Podpost).join(Podpost) + + def unpack_result_to_podpost(self, result) -> Podpost: + return result.podpost diff --git a/python/podcast/search/__init__.py b/python/podcast/search/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/test_searchbase.py b/test/test_searchbase.py new file mode 100644 index 0000000..8ffae85 --- /dev/null +++ b/test/test_searchbase.py @@ -0,0 +1,29 @@ +from podcast.search.Filter import * +from podcast.search.Order import * +from podcast.search.SearchBase import * +from test import setup_inbox_with_2_posts + + +def test_inbox_searchbase(): + entry1, entry2 = setup_inbox_with_2_posts() + assert [entry2.id, entry1.id] == [post.id for post in list(InboxSearchBase().iter_search(NoFilter(),NoOrder()))] + assert [entry2.id, entry1.id] == [post.id for post in list(InboxSearchBase().iter_search(PodcastFilter(entry1.podcast),NoOrder()))] + assert [] == [post.id for post in list(InboxSearchBase().iter_search(PodcastFilter(None),NoOrder()))] + assert [entry1.id, entry2.id] == [post.id for post in list(InboxSearchBase().iter_search(NoFilter(),PodpostListInsertDateOrder(Direction.ASC)))] + assert [entry1.id, entry2.id] == [post.id for post in list(InboxSearchBase().iter_search(PodcastFilter(entry1.podcast),PodpostListInsertDateOrder(Direction.ASC)))] + assert [entry2.id, entry1.id] == [post.id for post in list(InboxSearchBase().iter_search(NoFilter(),PodpostListInsertDateOrder(Direction.DESC)))] + assert [entry2.id, entry1.id] == [post.id for post in list(InboxSearchBase().iter_search(NoFilter(),PodpostPublishedDateOrder(Direction.DESC)))] + assert [entry1.id, entry2.id] == [post.id for post in list(InboxSearchBase().iter_search(NoFilter(),PodpostPublishedDateOrder(Direction.ASC)))] + +def test_title_filter(): + entry1, entry2 = setup_inbox_with_2_posts() + assert [entry1.id] == [post.id for post in list(AllSearchBase().iter_search(PodpostTitleFilter("Milch"),NoOrder()))] + assert [entry2.id] == [post.id for post in list(AllSearchBase().iter_search(PodpostTitleFilter("Alien"),NoOrder()))] + assert [entry2.id] == [post.id for post in list(AllSearchBase().iter_search(PodpostTitleFilter("alien"),NoOrder()))] + assert [entry2.id] == [post.id for post in list(AllSearchBase().iter_search(PodpostTitleFilter("Alien Wave"),NoOrder()))] + assert [] == [post.id for post in list(AllSearchBase().iter_search(PodpostTitleFilter("Alien not Wave"),NoOrder()))] + assert [] == [post.id for post in list(AllSearchBase().iter_search(PodpostTitleFilter("Wave Alien"),NoOrder()))] + assert [] == [post.id for post in list(AllSearchBase().iter_search(PodpostTitleFilter("Krautsalat"),NoOrder()))] + assert [entry2.id] == [post.id for post in list(AllSearchBase().iter_search(AndFilter(PodpostTitleFilter("Alien"),PodpostTitleFilter("Wave")),NoOrder()))] + assert [entry1.id, entry2.id] == [post.id for post in list(AllSearchBase().iter_search(OrFilter(PodpostTitleFilter("Alien"),PodpostTitleFilter("Milch")),PodpostPublishedDateOrder(Direction.ASC)))] + assert [] == [post.id for post in list(AllSearchBase().iter_search(AndFilter(PodpostTitleFilter("Alien"),PodpostTitleFilter("Milch")),NoOrder()))] -- GitLab From 033bcac02e00274424984f0ee492b52b87284f43 Mon Sep 17 00:00:00 2001 From: Thilo Kogge Date: Fri, 23 Jul 2021 19:39:02 +0200 Subject: [PATCH 02/20] search basic gui --- harbour-podqast.pro | 1 + python/podcast/data_migration.py | 12 +- qml/components/EpisodeSearchComponent.qml | 76 +++++++++++ qml/components/FeedParser.py | 2 +- qml/components/FeedParserPython.qml | 5 +- qml/components/RoundButton.qml | 18 +++ qml/pages/PodpostList.qml | 155 ++++++++++++++++------ translations/harbour-podqast-de.ts | 4 +- translations/harbour-podqast-es.ts | 4 +- translations/harbour-podqast-fr.ts | 4 +- translations/harbour-podqast-sv.ts | 4 +- translations/harbour-podqast-zh_CN.ts | 4 +- translations/harbour-podqast.ts | 4 +- 13 files changed, 235 insertions(+), 58 deletions(-) create mode 100644 qml/components/EpisodeSearchComponent.qml create mode 100644 qml/components/RoundButton.qml diff --git a/harbour-podqast.pro b/harbour-podqast.pro index ee9b0d5..9a7cfe9 100644 --- a/harbour-podqast.pro +++ b/harbour-podqast.pro @@ -20,6 +20,7 @@ DISTFILES += qml/podqast.qml \ qml/components/FyydDePython.qml \ qml/components/LogHandler.qml \ qml/components/MigrationHandler.qml \ + qml/components/RoundButton.qml \ qml/components/timeutil.js \ qml/cover/CoverPage.qml \ qml/pages/DataMigration.qml \ diff --git a/python/podcast/data_migration.py b/python/podcast/data_migration.py index ef96e04..07862c0 100644 --- a/python/podcast/data_migration.py +++ b/python/podcast/data_migration.py @@ -29,12 +29,14 @@ target_data_version = 3 def needs_migration(): def app_is_pristine(): if get_versionnumber() != 0: + logger.debug("app version %s", get_versionnumber()) return False - if os.path.exists(Constants().sqlitepath): - return False - if not os.path.exists(Constants().storepath): - return True - return len(os.listdir(Constants().storepath)) == 0 + if os.path.exists(Constants().storepath): + i = len(os.listdir(Constants().storepath)) + logger.debug("store exists with %d items", i) + return i == 0 + + return True if get_versionnumber() >= target_data_version: logger.info("Dataversion is fine.") diff --git a/qml/components/EpisodeSearchComponent.qml b/qml/components/EpisodeSearchComponent.qml new file mode 100644 index 0000000..85aadda --- /dev/null +++ b/qml/components/EpisodeSearchComponent.qml @@ -0,0 +1,76 @@ +import QtQuick 2.0 +import Sailfish.Silica 1.0 +import "../components" + +Column { + + SearchField { + id: episodeSearchField + width: parent.width + height: Theme.itemSizeMedium + visible: false + } + + Row { + id: episodeSortField + width: parent.width + visible: false + height: Theme.itemSizeMedium + LayoutMirroring.enabled: true + + IconButton { + icon.source: "image://theme/icon-s-duration" + } + + IconButton { + icon.source: "image://theme/icon-m-moon" + } + + IconButton { + icon.source: "image://theme/icon-m-mic-mute" + } + } + + Row { + id: settingsrow + height: Theme.itemSizeMedium + width: parent.width + visible: false + LayoutMirroring.enabled: true + + IconButton { + icon.source: "image://theme/icon-m-cancel" + onClicked: { + settingsrow.visible = false + } + } + + IconButton { + id: prefsicon + icon.source: "image://theme/icon-m-developer-mode" + onClicked: { + pageStack.push(Qt.resolvedUrl( + "PodcastSettings.qml"), { + "podtitle": title, + "url": url + }) + } + } + + IconButton { + id: searchicon + icon.source: "image://theme/icon-m-search" + onClicked: { + episodeSearchField.visible = !episodeSearchField.visible + } + } + + IconButton { + id: sorticon + icon.source: "image://theme/icon-m-transfer" + onClicked: { + episodeSortField.visible = !episodeSortField.visible + } + } + } +} diff --git a/qml/components/FeedParser.py b/qml/components/FeedParser.py index c38dc75..cf5da05 100644 --- a/qml/components/FeedParser.py +++ b/qml/components/FeedParser.py @@ -65,7 +65,7 @@ class FeedParser: async def get_podcast_preview(self, theurl, num_preview_episodes): await self.get_feedinfo(theurl, True, num_preview_episodes=num_preview_episodes) - async def get_entries(self, url): + async def get_entries(self, url, searchoptions): """ Get all podposts """ diff --git a/qml/components/FeedParserPython.qml b/qml/components/FeedParserPython.qml index f0ab84c..39757e0 100644 --- a/qml/components/FeedParserPython.qml +++ b/qml/components/FeedParserPython.qml @@ -61,9 +61,10 @@ Python { call("FeedParser.instance.get_podcasts", function () {}) } - function getEntries(url) { + function getEntries(url, search_options) { console.log("url: " + url) - call("FeedParser.instance.get_entries", [url], function () {}) + call("FeedParser.instance.get_entries", [url, searchoptions], + function () {}) } function subscribePodcast(url) { diff --git a/qml/components/RoundButton.qml b/qml/components/RoundButton.qml new file mode 100644 index 0000000..597066e --- /dev/null +++ b/qml/components/RoundButton.qml @@ -0,0 +1,18 @@ +import QtQuick 2.0 + +import Sailfish.Silica 1.0 + +Item { + property alias button: button + Rectangle { + color: Theme.primaryColor + border.width: 1 + anchors.fill: parent + radius: height + } + + IconButton { + id: button + icon.color: Theme.highlightColor + } +} diff --git a/qml/pages/PodpostList.qml b/qml/pages/PodpostList.qml index a3c469e..61223e0 100644 --- a/qml/pages/PodpostList.qml +++ b/qml/pages/PodpostList.qml @@ -16,6 +16,21 @@ Page { feedparserhandler.getEntries(url) } + RoundButton { + x: parent.width - Theme.iconSizeLarge - Theme.paddingLarge + y: parent.height - Theme.iconSizeLarge - Theme.paddingLarge + z: 1 + button.icon.width: Theme.iconSizeMedium + button.icon.height: Theme.iconSizeMedium + button.icon.source: "image://theme/icon-m-search" + + visible: !searchControls.visible + width: Theme.iconSizeLarge + height: Theme.iconSizeLarge + + button.onClicked: searchControls.visible = !searchControls.visible + } + Connections { target: feedparserhandler ignoreUnknownSignals: true @@ -46,24 +61,98 @@ Page { // Tell SilicaFlickable the height of its content. contentHeight: page.height - Column { - id: archivetitle + Column { + id: archivetitle + + width: page.width - width: page.width + spacing: Theme.paddingLarge + PageHeader { + title: page.title + Column { + id: placeholder + width: Theme.itemSizeExtraSmall / 2 + } + + Column { + id: prefscol + anchors.verticalCenter: parent.verticalCenter + anchors.left: placeholder.right + } + } + } + + SilicaListView { + id: archivepostlist + clip: true + width: parent.width + height: page.height - pdp.height - archivetitle.height - searchControls.height + section.property: 'section' + section.delegate: SectionHeader { + text: section + horizontalAlignment: Text.AlignRight + } + + ViewPlaceholder { + enabled: podpostsModel.count == 0 + text: qsTr("Rendering") + hintText: qsTr("Creating items") + verticalOffset: -archivetitle.height + } + + model: ListModel { + id: podpostsModel + } + delegate: PodpostPostListItem {} + } + + Column { + id: searchControls + width: parent.width + visible: false + + SearchField { + id: episodeSearchField + width: parent.width + height: Theme.itemSizeMedium + visible: false + } + + Row { + id: episodeSortField + width: parent.width + visible: false + height: Theme.itemSizeMedium + LayoutMirroring.enabled: true + + IconButton { + icon.source: "image://theme/icon-s-duration" + } + + IconButton { + icon.source: "image://theme/icon-m-moon" + } - spacing: Theme.paddingLarge - PageHeader { - title: page.title - Column { - id: placeholder - width: Theme.itemSizeExtraSmall / 2 + IconButton { + icon.source: "image://theme/icon-m-mic-mute" + } } - Column { - id: prefscol - anchors.verticalCenter: parent.verticalCenter - anchors.left: placeholder.right + Row { + id: settingsrow + height: Theme.itemSizeMedium + width: parent.width + visible: true + LayoutMirroring.enabled: true + + IconButton { + icon.source: "image://theme/icon-m-cancel" + onClicked: { + searchControls.visible = false + } + } + IconButton { id: prefsicon icon.source: "image://theme/icon-m-developer-mode" @@ -75,35 +164,25 @@ Page { }) } } - } - } - } - SilicaListView { - id: archivepostlist - clip: true - anchors.top: archivetitle.bottom - width: parent.width - height: page.height - pdp.height - archivetitle.height - section.property: 'section' - section.delegate: SectionHeader { - text: section - horizontalAlignment: Text.AlignRight - } - - ViewPlaceholder { - enabled: podpostsModel.count == 0 - text: qsTr("Rendering") - hintText: qsTr("Creating items") - verticalOffset: -archivetitle.height - } + IconButton { + id: searchicon + icon.source: "image://theme/icon-m-search" + onClicked: { + episodeSearchField.visible = !episodeSearchField.visible + } + } - model: ListModel { - id: podpostsModel + IconButton { + id: sorticon + icon.source: "image://theme/icon-m-transfer" + onClicked: { + episodeSortField.visible = !episodeSortField.visible + } + } + } } - delegate: PodpostPostListItem {} } - PlayDockedPanel { id: pdp } diff --git a/translations/harbour-podqast-de.ts b/translations/harbour-podqast-de.ts index 8bd2818..66c09c0 100644 --- a/translations/harbour-podqast-de.ts +++ b/translations/harbour-podqast-de.ts @@ -582,12 +582,12 @@ PodpostList - + Rendering Zeichne - + Creating items Erzeuge Elemente diff --git a/translations/harbour-podqast-es.ts b/translations/harbour-podqast-es.ts index a9d4bc1..ce19877 100644 --- a/translations/harbour-podqast-es.ts +++ b/translations/harbour-podqast-es.ts @@ -582,12 +582,12 @@ PodpostList - + Rendering Generando - + Creating items Creando elementos diff --git a/translations/harbour-podqast-fr.ts b/translations/harbour-podqast-fr.ts index cfa670d..a7117ec 100644 --- a/translations/harbour-podqast-fr.ts +++ b/translations/harbour-podqast-fr.ts @@ -583,12 +583,12 @@ PodpostList - + Rendering Chargement - + Creating items Génération des éléments diff --git a/translations/harbour-podqast-sv.ts b/translations/harbour-podqast-sv.ts index 87914dc..4ce3f45 100644 --- a/translations/harbour-podqast-sv.ts +++ b/translations/harbour-podqast-sv.ts @@ -582,12 +582,12 @@ PodpostList - + Rendering Rendering - + Creating items Skapar objekt diff --git a/translations/harbour-podqast-zh_CN.ts b/translations/harbour-podqast-zh_CN.ts index d73fc4c..7e24fa6 100644 --- a/translations/harbour-podqast-zh_CN.ts +++ b/translations/harbour-podqast-zh_CN.ts @@ -582,12 +582,12 @@ PodpostList - + Rendering 更新 - + Creating items 创建新项目 diff --git a/translations/harbour-podqast.ts b/translations/harbour-podqast.ts index 1274bf8..6e571ad 100644 --- a/translations/harbour-podqast.ts +++ b/translations/harbour-podqast.ts @@ -582,12 +582,12 @@ PodpostList - + Rendering Rendering - + Creating items Creating items -- GitLab From 1c27deb5cf2a3bf63d166df7af66bea5ca725a1c Mon Sep 17 00:00:00 2001 From: Thilo Kogge Date: Fri, 23 Jul 2021 23:12:49 +0200 Subject: [PATCH 03/20] basic search filter implemented --- python/podcast/podpost.py | 4 +- python/podcast/search/Filter.py | 32 +++++++++++-- python/podcast/search/SearchBase.py | 4 +- qml/components/FeedParser.py | 55 +++++++++++++++++---- qml/components/FeedParserPython.qml | 5 +- qml/components/RoundButton.qml | 13 +++-- qml/pages/PodpostList.qml | 69 ++++++++++++++++++++------- test/test_searchbase.py | 20 ++++---- translations/harbour-podqast-de.ts | 4 +- translations/harbour-podqast-es.ts | 4 +- translations/harbour-podqast-fr.ts | 4 +- translations/harbour-podqast-sv.ts | 4 +- translations/harbour-podqast-zh_CN.ts | 4 +- translations/harbour-podqast.ts | 4 +- 14 files changed, 165 insertions(+), 61 deletions(-) diff --git a/python/podcast/podpost.py b/python/podcast/podpost.py index 3e2f2dc..02cec5f 100644 --- a/python/podcast/podpost.py +++ b/python/podcast/podpost.py @@ -57,13 +57,13 @@ class Podpost(BaseModel): isaudio: bool = BooleanField(default=False, help_text="Is episode from external folder source") # filesize length: int = IntegerField(default=0) - listened: bool = BooleanField(default=False, help_text="Has been played already") + listened: bool = BooleanField(default=False, null=False, help_text="Has been played already") link: str = TextField(default="") logo_path: str = TextField(null=True) logo_url: str = TextField(null=True) # download percentage percentage: float = FloatField(default=0) - plainpart: str = TextField(default="") + plainpart: TextField = TextField(default="") # the feed's url podurl: str = TextField(null=True) position: int = IntegerField(default=0) diff --git a/python/podcast/search/Filter.py b/python/podcast/search/Filter.py index 76b1bd8..e86f6d8 100644 --- a/python/podcast/search/Filter.py +++ b/python/podcast/search/Filter.py @@ -16,9 +16,16 @@ class Filter(ABC): class NoFilter(Filter): + """ + Warning: do not use as part of other filters + """ + def apply_filter(self, query: ModelSelect, _) -> ModelSelect: return query + def get_where_expression(self, search_env: Dict) -> Expression: + raise NotImplementedError() + class PodcastFilter(Filter): def __init__(self, podcast: Podcast) -> None: @@ -29,14 +36,31 @@ class PodcastFilter(Filter): return Podpost.podcast == self.podcast -class PodpostTitleFilter(Filter): +class PodpostTextFilter(Filter): def __init__(self, searchstring: str) -> None: super().__init__() self.searchstring = searchstring def get_where_expression(self, search_env: Dict) -> Expression: - return Podpost.title.contains(self.searchstring) + return Podpost.title.contains(self.searchstring) or Podpost.plainpart.contains(self.searchstring) + + +class PodpostListenedFilter(Filter): + def __init__(self, inverse) -> None: + super().__init__() + self.inverse = inverse + + def get_where_expression(self, search_env: Dict) -> Expression: + return Podpost.listened != self.inverse + + +class FavoriteFilter(Filter): + def __init__(self) -> None: + super().__init__() + + def get_where_expression(self, search_env: Dict) -> Expression: + return Podpost.favorite == True class BiFilter(Filter): @@ -49,10 +73,12 @@ class BiFilter(Filter): def get_where_expression(self, search_env: Dict) -> Expression: raise ValueError("Do not call abstract method") + class AndFilter(BiFilter): def get_where_expression(self, search_env: Dict) -> Expression: return self.left.get_where_expression(search_env) & self.right.get_where_expression(search_env) - + + class OrFilter(BiFilter): def get_where_expression(self, search_env: Dict) -> Expression: return self.left.get_where_expression(search_env) | self.right.get_where_expression(search_env) diff --git a/python/podcast/search/SearchBase.py b/python/podcast/search/SearchBase.py index b3dd2c3..d8929ed 100644 --- a/python/podcast/search/SearchBase.py +++ b/python/podcast/search/SearchBase.py @@ -24,10 +24,12 @@ class SearchBase(ABC): def unpack_result_to_podpost(self, result) -> Podpost: raise ValueError("do not call abstract method") - def iter_search(self, filter: Filter, order: Order) -> Iterator[Podpost]: + # -> Iterator[Podpost] + def iter_search(self, filter: Filter, order: Order): query = self.get_search_base() query = filter.apply_filter(query, self.search_env) query = order.apply_order(query, self.search_env) + logger.debug(query) for result in query.iterator(): yield self.unpack_result_to_podpost(result) diff --git a/qml/components/FeedParser.py b/qml/components/FeedParser.py index cf5da05..f2c67c6 100644 --- a/qml/components/FeedParser.py +++ b/qml/components/FeedParser.py @@ -16,6 +16,10 @@ from podcast.persistent_log import LogType from podcast.podcast import PodcastFactory, Podcast from podcast.podcastlist import PodcastListFactory from podcast.archive import ArchiveFactory +from podcast.search.Filter import PodcastFilter, AndFilter, PodpostTextFilter, Filter, NoFilter, FavoriteFilter, \ + PodpostListenedFilter +from podcast.search.Order import PodpostPublishedDateOrder, Direction, Order +from podcast.search.SearchBase import AllSearchBase from podcast.util import create_opml, movePost, chunks logger = logging.getLogger(__name__) @@ -65,17 +69,48 @@ class FeedParser: async def get_podcast_preview(self, theurl, num_preview_episodes): await self.get_feedinfo(theurl, True, num_preview_episodes=num_preview_episodes) - async def get_entries(self, url, searchoptions): + async def get_entries(self, searchoptions): """ Get all podposts """ + def and_add_filter(old,new): + if old is None: + return new + if new is None: + return old + return AndFilter(old,new) + + logger.info("searching with: %s", searchoptions) + filter = None + if "searchtext" in searchoptions: + searchtext = searchoptions["searchtext"] + filter = and_add_filter(filter, PodpostTextFilter(searchtext)) + if "fav_filter" in searchoptions and searchoptions["fav_filter"]: + filter = and_add_filter(filter, FavoriteFilter()) + if "listened_filter" in searchoptions: + logger.info("lis: %s" ,searchoptions["listened_filter"]) + filter = and_add_filter(filter, PodpostListenedFilter(not searchoptions["listened_filter"])) + + order: Order = PodpostPublishedDateOrder(Direction.DESC) + if "order" in searchoptions: + if searchoptions["order"] == "published": + order = PodpostPublishedDateOrder(Direction.DESC) + + if "podcast_url" in searchoptions: + url = searchoptions["podcast_url"] + podcast = PodcastFactory().get_podcast(url) - podcast = PodcastFactory().get_podcast(url) - - link = podcast.link - - for offset, max, chunk in chunks(podcast.get_entries(), 32): - pyotherside.send("episodeListData", link, podcast.title, chunk, offset, max) + filter: Filter = and_add_filter(filter, PodcastFilter(podcast)) + triggered = False + for offset, max, chunk in chunks( + (post.get_data() for post in AllSearchBase().iter_search(filter, order)), + 32): + triggered = True + pyotherside.send("episodeListData", url, podcast.title, chunk, offset, max) + if not triggered: + pyotherside.send("episodeListData", url, podcast.title, [], 0, 0) + return + raise NotImplementedError("can only get episodes of a single podcast right now") async def subscribe_podcast(self, url): """ @@ -146,14 +181,16 @@ class FeedParser: podcast_list = PodcastListFactory().get_podcast_list() - for postid, podcasttitle, posttitle, move in podcast_list.refwresh(moveto, limit, full_refresh=full_refresh): + for postid, podcasttitle, posttitle, move in podcast_list.refwresh(moveto, limit, + full_refresh=full_refresh): try: page = movePost(move, postid) pyotherside.send( "updatesNotification", podcasttitle, posttitle, page ) except Exception as e: - persistent_log.persist_log(LogType.Exception,msg="Could not move episode to according list",exception=e) + persistent_log.persist_log(LogType.Exception, msg="Could not move episode to according list", + exception=e) pyotherside.send("refreshFinished") diff --git a/qml/components/FeedParserPython.qml b/qml/components/FeedParserPython.qml index 39757e0..5daf2da 100644 --- a/qml/components/FeedParserPython.qml +++ b/qml/components/FeedParserPython.qml @@ -61,9 +61,8 @@ Python { call("FeedParser.instance.get_podcasts", function () {}) } - function getEntries(url, search_options) { - console.log("url: " + url) - call("FeedParser.instance.get_entries", [url, searchoptions], + function getEntries(search_options) { + call("FeedParser.instance.get_entries", [search_options], function () {}) } diff --git a/qml/components/RoundButton.qml b/qml/components/RoundButton.qml index 597066e..62a51d6 100644 --- a/qml/components/RoundButton.qml +++ b/qml/components/RoundButton.qml @@ -4,15 +4,22 @@ import Sailfish.Silica 1.0 Item { property alias button: button + property alias mouse: mouse Rectangle { - color: Theme.primaryColor + color: Theme.highlightDimmerColor border.width: 1 + border.color: Theme.highlightColor anchors.fill: parent radius: height } - IconButton { + Icon { id: button - icon.color: Theme.highlightColor + color: Theme.primaryColor + } + + MouseArea{ + id: mouse + anchors.fill: parent } } diff --git a/qml/pages/PodpostList.qml b/qml/pages/PodpostList.qml index 61223e0..368ab2c 100644 --- a/qml/pages/PodpostList.qml +++ b/qml/pages/PodpostList.qml @@ -13,22 +13,38 @@ Page { allowedOrientations: Orientation.All Component.onCompleted: { - feedparserhandler.getEntries(url) + get_data() + } + + property string searchtext: "" + property string order: "published" + property bool fav_filter: false + // 2 do not filter, 0 not listened, 1 listened + property int listened_filter: 2 + + function get_data() { + var get_options = { + "podcast_url": url, + "searchtext": searchtext, + "fav_filter": fav_filter, + "order": order + } + if (listened_filter < 2) + get_options["listened_filter"] = listened_filter === 1 + feedparserhandler.getEntries(get_options) } RoundButton { x: parent.width - Theme.iconSizeLarge - Theme.paddingLarge - y: parent.height - Theme.iconSizeLarge - Theme.paddingLarge + y: parent.height - Theme.iconSizeLarge - Theme.paddingLarge - pdp.height z: 1 - button.icon.width: Theme.iconSizeMedium - button.icon.height: Theme.iconSizeMedium - button.icon.source: "image://theme/icon-m-search" + button.source: "image://theme/icon-m-search" visible: !searchControls.visible - width: Theme.iconSizeLarge - height: Theme.iconSizeLarge + width: Theme.iconSizeMedium + height: Theme.iconSizeMedium - button.onClicked: searchControls.visible = !searchControls.visible + mouse.onClicked: searchControls.visible = !searchControls.visible } Connections { @@ -38,6 +54,7 @@ Page { title = podtitle link = podlink if (offset == 0) { + console.log("reloading data") podpostsModel.clear() } for (var i = 0; i < episodes.length; i++) { @@ -115,35 +132,51 @@ Page { SearchField { id: episodeSearchField width: parent.width - height: Theme.itemSizeMedium + height: visible ? Theme.itemSizeMedium : 0 visible: false + onTextChanged: { + searchtext = episodeSearchField.text + get_data() + } } Row { id: episodeSortField width: parent.width visible: false - height: Theme.itemSizeMedium + height: visible ? Theme.itemSizeMedium : 0 LayoutMirroring.enabled: true IconButton { - icon.source: "image://theme/icon-s-duration" - } - - IconButton { - icon.source: "image://theme/icon-m-moon" + icon.source: fav_filter ? "image://theme/icon-m-favorite-selected" : "image://theme/icon-m-favorite" + icon.color: fav_filter ? Theme.highlightColor : Theme.primaryColor + onClicked: { + fav_filter = !fav_filter + get_data() + } } IconButton { - icon.source: "image://theme/icon-m-mic-mute" + icon.source: "image://theme/icon-m-headphone" + icon.color: listened_filter < 2 ? Theme.highlightColor : Theme.primaryColor + onClicked: { + listened_filter++ + listened_filter = listened_filter % 3 + get_data() + } + Icon { + source: "image://theme/icon-s-clear-opaque-cross" + visible: listened_filter === 0 + color: Theme.highlightColor + } } } Row { id: settingsrow - height: Theme.itemSizeMedium + height: visible ? Theme.itemSizeMedium : 0 width: parent.width - visible: true + visible: parent.visible LayoutMirroring.enabled: true IconButton { diff --git a/test/test_searchbase.py b/test/test_searchbase.py index 8ffae85..faa9ad8 100644 --- a/test/test_searchbase.py +++ b/test/test_searchbase.py @@ -17,13 +17,13 @@ def test_inbox_searchbase(): def test_title_filter(): entry1, entry2 = setup_inbox_with_2_posts() - assert [entry1.id] == [post.id for post in list(AllSearchBase().iter_search(PodpostTitleFilter("Milch"),NoOrder()))] - assert [entry2.id] == [post.id for post in list(AllSearchBase().iter_search(PodpostTitleFilter("Alien"),NoOrder()))] - assert [entry2.id] == [post.id for post in list(AllSearchBase().iter_search(PodpostTitleFilter("alien"),NoOrder()))] - assert [entry2.id] == [post.id for post in list(AllSearchBase().iter_search(PodpostTitleFilter("Alien Wave"),NoOrder()))] - assert [] == [post.id for post in list(AllSearchBase().iter_search(PodpostTitleFilter("Alien not Wave"),NoOrder()))] - assert [] == [post.id for post in list(AllSearchBase().iter_search(PodpostTitleFilter("Wave Alien"),NoOrder()))] - assert [] == [post.id for post in list(AllSearchBase().iter_search(PodpostTitleFilter("Krautsalat"),NoOrder()))] - assert [entry2.id] == [post.id for post in list(AllSearchBase().iter_search(AndFilter(PodpostTitleFilter("Alien"),PodpostTitleFilter("Wave")),NoOrder()))] - assert [entry1.id, entry2.id] == [post.id for post in list(AllSearchBase().iter_search(OrFilter(PodpostTitleFilter("Alien"),PodpostTitleFilter("Milch")),PodpostPublishedDateOrder(Direction.ASC)))] - assert [] == [post.id for post in list(AllSearchBase().iter_search(AndFilter(PodpostTitleFilter("Alien"),PodpostTitleFilter("Milch")),NoOrder()))] + assert [entry1.id] == [post.id for post in list(AllSearchBase().iter_search(PodpostTextFilter("Milch"), NoOrder()))] + assert [entry2.id] == [post.id for post in list(AllSearchBase().iter_search(PodpostTextFilter("Alien"), NoOrder()))] + assert [entry2.id] == [post.id for post in list(AllSearchBase().iter_search(PodpostTextFilter("alien"), NoOrder()))] + assert [entry2.id] == [post.id for post in list(AllSearchBase().iter_search(PodpostTextFilter("Alien Wave"), NoOrder()))] + assert [] == [post.id for post in list(AllSearchBase().iter_search(PodpostTextFilter("Alien not Wave"), NoOrder()))] + assert [] == [post.id for post in list(AllSearchBase().iter_search(PodpostTextFilter("Wave Alien"), NoOrder()))] + assert [] == [post.id for post in list(AllSearchBase().iter_search(PodpostTextFilter("Krautsalat"), NoOrder()))] + assert [entry2.id] == [post.id for post in list(AllSearchBase().iter_search(AndFilter(PodpostTextFilter("Alien"), PodpostTextFilter("Wave")), NoOrder()))] + assert [entry1.id, entry2.id] == [post.id for post in list(AllSearchBase().iter_search(OrFilter(PodpostTextFilter("Alien"), PodpostTextFilter("Milch")), PodpostPublishedDateOrder(Direction.ASC)))] + assert [] == [post.id for post in list(AllSearchBase().iter_search(AndFilter(PodpostTextFilter("Alien"), PodpostTextFilter("Milch")), NoOrder()))] diff --git a/translations/harbour-podqast-de.ts b/translations/harbour-podqast-de.ts index 66c09c0..1c8598e 100644 --- a/translations/harbour-podqast-de.ts +++ b/translations/harbour-podqast-de.ts @@ -582,12 +582,12 @@ PodpostList - + Rendering Zeichne - + Creating items Erzeuge Elemente diff --git a/translations/harbour-podqast-es.ts b/translations/harbour-podqast-es.ts index ce19877..0b57136 100644 --- a/translations/harbour-podqast-es.ts +++ b/translations/harbour-podqast-es.ts @@ -582,12 +582,12 @@ PodpostList - + Rendering Generando - + Creating items Creando elementos diff --git a/translations/harbour-podqast-fr.ts b/translations/harbour-podqast-fr.ts index a7117ec..13a16f4 100644 --- a/translations/harbour-podqast-fr.ts +++ b/translations/harbour-podqast-fr.ts @@ -583,12 +583,12 @@ PodpostList - + Rendering Chargement - + Creating items Génération des éléments diff --git a/translations/harbour-podqast-sv.ts b/translations/harbour-podqast-sv.ts index 4ce3f45..5499dc5 100644 --- a/translations/harbour-podqast-sv.ts +++ b/translations/harbour-podqast-sv.ts @@ -582,12 +582,12 @@ PodpostList - + Rendering Rendering - + Creating items Skapar objekt diff --git a/translations/harbour-podqast-zh_CN.ts b/translations/harbour-podqast-zh_CN.ts index 7e24fa6..04498c7 100644 --- a/translations/harbour-podqast-zh_CN.ts +++ b/translations/harbour-podqast-zh_CN.ts @@ -582,12 +582,12 @@ PodpostList - + Rendering 更新 - + Creating items 创建新项目 diff --git a/translations/harbour-podqast.ts b/translations/harbour-podqast.ts index 6e571ad..588e458 100644 --- a/translations/harbour-podqast.ts +++ b/translations/harbour-podqast.ts @@ -582,12 +582,12 @@ PodpostList - + Rendering Rendering - + Creating items Creating items -- GitLab From 15aa31965f78389248e941bd68818472404381c9 Mon Sep 17 00:00:00 2001 From: Thilo Kogge Date: Sat, 24 Jul 2021 17:50:46 +0200 Subject: [PATCH 04/20] moved search button to the player dock --- harbour-podqast.pro | 1 - qml/components/PlayDockedPanel.qml | 130 +++++++++++++++----------- qml/pages/PodpostList.qml | 18 +--- translations/harbour-podqast-de.ts | 4 +- translations/harbour-podqast-es.ts | 4 +- translations/harbour-podqast-fr.ts | 4 +- translations/harbour-podqast-sv.ts | 4 +- translations/harbour-podqast-zh_CN.ts | 4 +- translations/harbour-podqast.ts | 4 +- 9 files changed, 94 insertions(+), 79 deletions(-) diff --git a/harbour-podqast.pro b/harbour-podqast.pro index 9a7cfe9..ee9b0d5 100644 --- a/harbour-podqast.pro +++ b/harbour-podqast.pro @@ -20,7 +20,6 @@ DISTFILES += qml/podqast.qml \ qml/components/FyydDePython.qml \ qml/components/LogHandler.qml \ qml/components/MigrationHandler.qml \ - qml/components/RoundButton.qml \ qml/components/timeutil.js \ qml/cover/CoverPage.qml \ qml/pages/DataMigration.qml \ diff --git a/qml/components/PlayDockedPanel.qml b/qml/components/PlayDockedPanel.qml index 142e1d1..55b2bd4 100644 --- a/qml/components/PlayDockedPanel.qml +++ b/qml/components/PlayDockedPanel.qml @@ -6,9 +6,10 @@ DockedPanel { width: parent.width height: Theme.itemSizeSmall - open: playeropen + open: true dock: Dock.Bottom + default property alias contents: additions.children Rectangle { anchors.fill: parent @@ -17,69 +18,92 @@ DockedPanel { } Row { - id: row anchors.centerIn: parent + Row { + id: row - IconButton { - icon.source: "image://theme/icon-m-previous" - onClicked: { - podqast.fast_backward() + //anchors.centerIn: parent + IconButton { + icon.source: "image://theme/icon-m-previous" + onClicked: { + podqast.fast_backward() + } } - } - IconButton { - icon.source: "image://theme/icon-m-" + (playerHandler.isPlaying ? "pause" : "play") - onClicked: { - playerHandler.playpause() + IconButton { + icon.source: "image://theme/icon-m-" + (playerHandler.isPlaying ? "pause" : "play") + onClicked: { + playerHandler.playpause() + } } - } - IconButton { - icon.source: "image://theme/icon-m-next" - onClicked: { - podqast.fast_forward() + IconButton { + icon.source: "image://theme/icon-m-next" + onClicked: { + podqast.fast_forward() + } } - } - IconButton { - id: playpanelicon - icon.source: playerHandler.playicon - === "" ? "../../images/podcast.png" : playerHandler.playicon - icon.width: Theme.iconSizeMedium - icon.height: Theme.iconSizeMedium - icon.color: undefined - onClicked: { - queuehandler.getFirstEntry() + IconButton { + id: playpanelicon + icon.source: playerHandler.playicon + === "" ? "../../images/podcast.png" : playerHandler.playicon + icon.width: Theme.iconSizeMedium + icon.height: Theme.iconSizeMedium + icon.color: undefined + onClicked: { + queuehandler.getFirstEntry() + } + Connections { + target: queuehandler + onFirstEntry: { + pageStack.push(Qt.resolvedUrl( + "../pages/PostDescription.qml"), { + "title": data.title, + "detail": data.detail, + "length": data.length, + "date": data.date, + "duration": data.duration, + "href": data.link + }) + } + } } - Connections { - target: queuehandler - onFirstEntry: { - pageStack.push(Qt.resolvedUrl( - "../pages/PostDescription.qml"), { - "title": data.title, - "detail": data.detail, - "length": data.length, - "date": data.date, - "duration": data.duration, - "href": data.link - }) + IconButton { + id: podimage + icon.source: "image://theme/icon-m-right" + onClicked: pageStack.push(Qt.resolvedUrl("../pages/Player.qml")) + } + Column { + id: statusicons + Icon { + source: "image://theme/icon-s-cloud-download" + visible: playerHandler.streaming + } + Icon { + source: "image://theme/icon-s-timer" + visible: podqast.dosleep + } + Icon { + source: "image://theme/icon-s-duration" + visible: playerHandler.playrate !== 1.0 } } } - IconButton { - id: podimage - icon.source: "image://theme/icon-m-right" - onClicked: pageStack.push(Qt.resolvedUrl("../pages/Player.qml")) - } - Image { - source: "image://theme/icon-s-timer" - visible: podqast.dosleep - } - Image { - source: "image://theme/icon-s-duration" - visible: playerHandler.playrate !== 1.0 + + Item { + id: placeholder + height: parent.height + width: Theme.paddingMedium * 2 + 3 + Rectangle { + height: parent.height - Theme.paddingMedium * 2 + width: 3 + color: Theme.highlightBackgroundColor + visible: additions.visibleChildren.length > 0 + anchors.centerIn: parent + } } - Image { - source: "image://theme/icon-s-cloud-download" - visible: playerHandler.streaming + Row { + id: additions + LayoutMirroring.enabled: true } } } diff --git a/qml/pages/PodpostList.qml b/qml/pages/PodpostList.qml index 368ab2c..0cfcf54 100644 --- a/qml/pages/PodpostList.qml +++ b/qml/pages/PodpostList.qml @@ -34,19 +34,6 @@ Page { feedparserhandler.getEntries(get_options) } - RoundButton { - x: parent.width - Theme.iconSizeLarge - Theme.paddingLarge - y: parent.height - Theme.iconSizeLarge - Theme.paddingLarge - pdp.height - z: 1 - button.source: "image://theme/icon-m-search" - - visible: !searchControls.visible - width: Theme.iconSizeMedium - height: Theme.iconSizeMedium - - mouse.onClicked: searchControls.visible = !searchControls.visible - } - Connections { target: feedparserhandler ignoreUnknownSignals: true @@ -218,6 +205,11 @@ Page { } PlayDockedPanel { id: pdp + + IconButton{ + icon.source: "image://theme/icon-m-search" + onClicked: searchControls.visible = !searchControls.visible + } } } } diff --git a/translations/harbour-podqast-de.ts b/translations/harbour-podqast-de.ts index 1c8598e..517ab0e 100644 --- a/translations/harbour-podqast-de.ts +++ b/translations/harbour-podqast-de.ts @@ -582,12 +582,12 @@ PodpostList - + Rendering Zeichne - + Creating items Erzeuge Elemente diff --git a/translations/harbour-podqast-es.ts b/translations/harbour-podqast-es.ts index 0b57136..a37291a 100644 --- a/translations/harbour-podqast-es.ts +++ b/translations/harbour-podqast-es.ts @@ -582,12 +582,12 @@ PodpostList - + Rendering Generando - + Creating items Creando elementos diff --git a/translations/harbour-podqast-fr.ts b/translations/harbour-podqast-fr.ts index 13a16f4..7a73ca4 100644 --- a/translations/harbour-podqast-fr.ts +++ b/translations/harbour-podqast-fr.ts @@ -583,12 +583,12 @@ PodpostList - + Rendering Chargement - + Creating items Génération des éléments diff --git a/translations/harbour-podqast-sv.ts b/translations/harbour-podqast-sv.ts index 5499dc5..5a1ea72 100644 --- a/translations/harbour-podqast-sv.ts +++ b/translations/harbour-podqast-sv.ts @@ -582,12 +582,12 @@ PodpostList - + Rendering Rendering - + Creating items Skapar objekt diff --git a/translations/harbour-podqast-zh_CN.ts b/translations/harbour-podqast-zh_CN.ts index 04498c7..a41101a 100644 --- a/translations/harbour-podqast-zh_CN.ts +++ b/translations/harbour-podqast-zh_CN.ts @@ -582,12 +582,12 @@ PodpostList - + Rendering 更新 - + Creating items 创建新项目 diff --git a/translations/harbour-podqast.ts b/translations/harbour-podqast.ts index 588e458..004746b 100644 --- a/translations/harbour-podqast.ts +++ b/translations/harbour-podqast.ts @@ -582,12 +582,12 @@ PodpostList - + Rendering Rendering - + Creating items Creating items -- GitLab From 8abf7db4f110472767b57e07411f9d82c32be12f Mon Sep 17 00:00:00 2001 From: Thilo Kogge Date: Sat, 24 Jul 2021 17:51:04 +0200 Subject: [PATCH 05/20] renoved obsolete component --- qml/components/RoundButton.qml | 25 ------------------------- 1 file changed, 25 deletions(-) delete mode 100644 qml/components/RoundButton.qml diff --git a/qml/components/RoundButton.qml b/qml/components/RoundButton.qml deleted file mode 100644 index 62a51d6..0000000 --- a/qml/components/RoundButton.qml +++ /dev/null @@ -1,25 +0,0 @@ -import QtQuick 2.0 - -import Sailfish.Silica 1.0 - -Item { - property alias button: button - property alias mouse: mouse - Rectangle { - color: Theme.highlightDimmerColor - border.width: 1 - border.color: Theme.highlightColor - anchors.fill: parent - radius: height - } - - Icon { - id: button - color: Theme.primaryColor - } - - MouseArea{ - id: mouse - anchors.fill: parent - } -} -- GitLab From 741b7c5b24123a2d1074352906be88add0b07a1a Mon Sep 17 00:00:00 2001 From: Thilo Kogge Date: Sat, 24 Jul 2021 19:32:13 +0200 Subject: [PATCH 06/20] added tests for searchoptions made episodeData signal more generic to port it to other lists --- python/podcast/podcast.py | 3 +- python/podcast/search/DictParser.py | 63 ++++++++++++++++++++++++++ python/podcast/search/Filter.py | 20 ++++++-- python/podcast/search/Order.py | 11 +++++ python/podcast/search/SearchBase.py | 10 +++- qml/components/FeedParser.py | 50 ++++---------------- qml/components/FeedParserPython.qml | 2 +- qml/components/PlayerHandler.qml | 1 + qml/pages/PodpostList.qml | 29 ++++++++++-- test/test_search_dictparser_filters.py | 24 ++++++++++ test/test_search_dictparser_orders.py | 31 +++++++++++++ translations/harbour-podqast-de.ts | 4 +- translations/harbour-podqast-es.ts | 4 +- translations/harbour-podqast-fr.ts | 4 +- translations/harbour-podqast-sv.ts | 4 +- translations/harbour-podqast-zh_CN.ts | 4 +- translations/harbour-podqast.ts | 4 +- 17 files changed, 200 insertions(+), 68 deletions(-) create mode 100644 python/podcast/search/DictParser.py create mode 100644 test/test_search_dictparser_filters.py create mode 100644 test/test_search_dictparser_orders.py diff --git a/python/podcast/podcast.py b/python/podcast/podcast.py index 78da8d2..32785ab 100644 --- a/python/podcast/podcast.py +++ b/python/podcast/podcast.py @@ -179,8 +179,6 @@ class Podcast(BaseModel): if not do_not_store: from podcast.podpost import PodpostFactory PodpostFactory().persist_bulk(new_posts) - new_post_ids = [post.id for post in new_posts] - new_post_ids.reverse() return new_posts @property @@ -224,6 +222,7 @@ class Podcast(BaseModel): return { "url": self.url, + "link": self.link, "title": title, "description": description, "descriptionfull": descriptionfull, diff --git a/python/podcast/search/DictParser.py b/python/podcast/search/DictParser.py new file mode 100644 index 0000000..e4697e9 --- /dev/null +++ b/python/podcast/search/DictParser.py @@ -0,0 +1,63 @@ +from typing import Dict, Iterator + +from podcast.podcast import PodcastFactory +from podcast.search.Filter import AndFilter, PodpostTextFilter, FavoriteFilter, PodpostListenedFilter, Filter, \ + PodcastFilter +from podcast.search.Order import Order, PodpostPublishedDateOrder, Direction, PodpostListInsertDateOrder +from podcast.search.SearchBase import AllSearchBase + + +def iterator_from_searchoptions(searchoptions: Dict) -> Iterator[Dict]: + podcast_cache = {} + if "podcast_url" in searchoptions: + podcast_cache[searchoptions["podcast_url"]] = PodcastFactory().get_podcast(searchoptions["podcast_url"]) + + filter = create_filter_from_options(searchoptions, podcast_cache) + order = create_order_from_options(searchoptions) + + if "podcast_url" in searchoptions: + return (post.get_data() for post in AllSearchBase().iter_search(filter, order)) + else: + raise NotImplementedError("can only get episodes of a single podcast right now") + + +def create_filter_from_options(searchoptions: Dict, podcast_cache: Dict = None) -> Filter: + if podcast_cache is None: + podcast_cache = {} + + def and_add_filter(old, new): + if old is None: + return new + if new is None: + return old + return AndFilter(old, new) + + filter = None + if "searchtext" in searchoptions: + searchtext = searchoptions["searchtext"] + filter = and_add_filter(filter, PodpostTextFilter(searchtext)) + if "fav_filter" in searchoptions: + filter = and_add_filter(filter, FavoriteFilter(searchoptions["fav_filter"])) + if "listened_filter" in searchoptions: + filter = and_add_filter(filter, PodpostListenedFilter(searchoptions["listened_filter"])) + if "podcast_url" in searchoptions: + url = searchoptions["podcast_url"] + if url not in podcast_cache: + raise AttributeError("podcast filter defined but podcast not cached") + filter: Filter = and_add_filter(filter, PodcastFilter(podcast_cache[url])) + return filter + + +def create_order_from_options(searchoptions: Dict) -> Order: + dir = Direction.DESC + if "order_dir" in searchoptions: + if searchoptions["order_dir"] == "asc": + dir = Direction.ASC + order: Order = PodpostPublishedDateOrder(dir) + + if "order" in searchoptions: + if searchoptions["order"] == "published": + order = PodpostPublishedDateOrder(dir) + if searchoptions["order"] == "inserted": + order = PodpostListInsertDateOrder(dir) + return order diff --git a/python/podcast/search/Filter.py b/python/podcast/search/Filter.py index e86f6d8..5cee562 100644 --- a/python/podcast/search/Filter.py +++ b/python/podcast/search/Filter.py @@ -15,6 +15,15 @@ class Filter(ABC): raise ValueError("Do not call abstract method") + def __eq__(self, other): + if other is None: + return False + if other.__class__ == self.__class__: + return self.__dict__ == other.__dict__ + else: + return False + + class NoFilter(Filter): """ Warning: do not use as part of other filters @@ -47,20 +56,21 @@ class PodpostTextFilter(Filter): class PodpostListenedFilter(Filter): - def __init__(self, inverse) -> None: + def __init__(self, filterval) -> None: super().__init__() - self.inverse = inverse + self.filterval = filterval def get_where_expression(self, search_env: Dict) -> Expression: - return Podpost.listened != self.inverse + return Podpost.listened == self.filterval class FavoriteFilter(Filter): - def __init__(self) -> None: + def __init__(self,filterval) -> None: super().__init__() + self.filterval = filterval def get_where_expression(self, search_env: Dict) -> Expression: - return Podpost.favorite == True + return Podpost.favorite == self.filterval class BiFilter(Filter): diff --git a/python/podcast/search/Order.py b/python/podcast/search/Order.py index de55621..b21e7d0 100644 --- a/python/podcast/search/Order.py +++ b/python/podcast/search/Order.py @@ -30,6 +30,17 @@ class Order(ABC): def apply_order(self, query: ModelSelect, search_env: Dict) -> ModelSelect: raise ValueError("do not call abstract method") + def __eq__(self, other): + if other is None: + return False + if other.__class__ == self.__class__: + return self.__dict__ == other.__dict__ + else: + return False + + def __str__(self) -> str: + return self.__class__.__name__ + "("+str(self.__dict__)+")" + class NoOrder(Order): def __init__(self) -> None: diff --git a/python/podcast/search/SearchBase.py b/python/podcast/search/SearchBase.py index d8929ed..09f6f19 100644 --- a/python/podcast/search/SearchBase.py +++ b/python/podcast/search/SearchBase.py @@ -27,8 +27,14 @@ class SearchBase(ABC): # -> Iterator[Podpost] def iter_search(self, filter: Filter, order: Order): query = self.get_search_base() - query = filter.apply_filter(query, self.search_env) - query = order.apply_order(query, self.search_env) + if filter is not None: + query = filter.apply_filter(query, self.search_env) + else: + logger.warning("no filter set") + if order is not None: + query = order.apply_order(query, self.search_env) + else: + logger.warning("no order set") logger.debug(query) for result in query.iterator(): yield self.unpack_result_to_podpost(result) diff --git a/qml/components/FeedParser.py b/qml/components/FeedParser.py index f2c67c6..b4cfe31 100644 --- a/qml/components/FeedParser.py +++ b/qml/components/FeedParser.py @@ -16,9 +16,7 @@ from podcast.persistent_log import LogType from podcast.podcast import PodcastFactory, Podcast from podcast.podcastlist import PodcastListFactory from podcast.archive import ArchiveFactory -from podcast.search.Filter import PodcastFilter, AndFilter, PodpostTextFilter, Filter, NoFilter, FavoriteFilter, \ - PodpostListenedFilter -from podcast.search.Order import PodpostPublishedDateOrder, Direction, Order +from podcast.search import DictParser from podcast.search.SearchBase import AllSearchBase from podcast.util import create_opml, movePost, chunks @@ -49,7 +47,8 @@ class FeedParser: if preview: podcast, episodes = Podcast.create_from_url(url, preview=True, num_preview_items=num_preview_episodes) else: - podcast, episodes = PodcastFactory().get_podcast(url) + podcast = PodcastFactory().get_podcast(url) + episodes = list(podcast.episodes.limit(num_preview_episodes)) if num_preview_episodes > 0 else [] except URLError: pyotherside.send("feedFetchError", url) return @@ -73,44 +72,13 @@ class FeedParser: """ Get all podposts """ - def and_add_filter(old,new): - if old is None: - return new - if new is None: - return old - return AndFilter(old,new) - logger.info("searching with: %s", searchoptions) - filter = None - if "searchtext" in searchoptions: - searchtext = searchoptions["searchtext"] - filter = and_add_filter(filter, PodpostTextFilter(searchtext)) - if "fav_filter" in searchoptions and searchoptions["fav_filter"]: - filter = and_add_filter(filter, FavoriteFilter()) - if "listened_filter" in searchoptions: - logger.info("lis: %s" ,searchoptions["listened_filter"]) - filter = and_add_filter(filter, PodpostListenedFilter(not searchoptions["listened_filter"])) - - order: Order = PodpostPublishedDateOrder(Direction.DESC) - if "order" in searchoptions: - if searchoptions["order"] == "published": - order = PodpostPublishedDateOrder(Direction.DESC) - - if "podcast_url" in searchoptions: - url = searchoptions["podcast_url"] - podcast = PodcastFactory().get_podcast(url) - - filter: Filter = and_add_filter(filter, PodcastFilter(podcast)) - triggered = False - for offset, max, chunk in chunks( - (post.get_data() for post in AllSearchBase().iter_search(filter, order)), - 32): - triggered = True - pyotherside.send("episodeListData", url, podcast.title, chunk, offset, max) - if not triggered: - pyotherside.send("episodeListData", url, podcast.title, [], 0, 0) - return - raise NotImplementedError("can only get episodes of a single podcast right now") + triggered = False + for offset, max, chunk in chunks(DictParser.iterator_from_searchoptions(searchoptions), 32): + triggered = True + pyotherside.send("episodeListData",chunk, offset, max) + if not triggered: + pyotherside.send("episodeListData", [], 0, 0) async def subscribe_podcast(self, url): """ diff --git a/qml/components/FeedParserPython.qml b/qml/components/FeedParserPython.qml index 5daf2da..d98a9cb 100644 --- a/qml/components/FeedParserPython.qml +++ b/qml/components/FeedParserPython.qml @@ -19,7 +19,7 @@ Python { signal refreshlimit(string podcasttitle) signal backupDone(string tarpath) signal opmlSaveDone(string opmlpath) - signal episodeListData(string podlink, string podtitle, var episodes, string offset, string totalcount) + signal episodeListData(var episodes, string offset, string totalcount) signal feedFetchError(string pod_url) Component.onCompleted: { diff --git a/qml/components/PlayerHandler.qml b/qml/components/PlayerHandler.qml index 1689a73..16b4e5f 100644 --- a/qml/components/PlayerHandler.qml +++ b/qml/components/PlayerHandler.qml @@ -86,6 +86,7 @@ Python { } function setEpisode(data, chapterlist) { + console.log("setting episode: " + data) firstid = data.id firsttitle = data.title chapters = chapterlist diff --git a/qml/pages/PodpostList.qml b/qml/pages/PodpostList.qml index 0cfcf54..39e815e 100644 --- a/qml/pages/PodpostList.qml +++ b/qml/pages/PodpostList.qml @@ -14,10 +14,12 @@ Page { Component.onCompleted: { get_data() + feedparserhandler.getPodcast(url) } property string searchtext: "" property string order: "published" + property string order_dir: "desc" property bool fav_filter: false // 2 do not filter, 0 not listened, 1 listened property int listened_filter: 2 @@ -27,7 +29,8 @@ Page { "podcast_url": url, "searchtext": searchtext, "fav_filter": fav_filter, - "order": order + "order": order, + "order_dir": order_dir } if (listened_filter < 2) get_options["listened_filter"] = listened_filter === 1 @@ -37,9 +40,13 @@ Page { Connections { target: feedparserhandler ignoreUnknownSignals: true + + onFeedinfo: { + title = pcdata["title"] + link = pcdata["link"] + } + onEpisodeListData: { - title = podtitle - link = podlink if (offset == 0) { console.log("reloading data") podpostsModel.clear() @@ -157,6 +164,18 @@ Page { color: Theme.highlightColor } } + + IconButton { + icon.source: order_dir == "asc" ? "image://theme/icon-m-page-up" : "image://theme/icon-m-page-down" + icon.color: Theme.highlightColor + onClicked: { + if (order_dir == "desc") + order_dir = "asc" + else + order_dir = "desc" + get_data() + } + } } Row { @@ -206,9 +225,9 @@ Page { PlayDockedPanel { id: pdp - IconButton{ + IconButton { icon.source: "image://theme/icon-m-search" - onClicked: searchControls.visible = !searchControls.visible + onClicked: searchControls.visible = !searchControls.visible } } } diff --git a/test/test_search_dictparser_filters.py b/test/test_search_dictparser_filters.py new file mode 100644 index 0000000..c30e819 --- /dev/null +++ b/test/test_search_dictparser_filters.py @@ -0,0 +1,24 @@ +from podcast.podcast import Podcast +from podcast.search.DictParser import * + + +def test_empty_filter(): + assert create_filter_from_options({}) == None + + +def test_single_filters(): + dummy_pod = Podcast() + assert create_filter_from_options({"searchtext": "abc"}) == PodpostTextFilter("abc") + + assert create_filter_from_options({"fav_filter": True}) == FavoriteFilter(True) + assert create_filter_from_options({"fav_filter": False}) == FavoriteFilter(False) + + assert create_filter_from_options({"listened_filter": True}) == PodpostListenedFilter(True) + assert create_filter_from_options({"listened_filter": False}) == PodpostListenedFilter(False) + + assert create_filter_from_options({"podcast_url": "abc"}, {"abc": dummy_pod}) == PodcastFilter(dummy_pod) + + +def test_combining_filters(): + assert create_filter_from_options({"searchtext": "abc", "fav_filter": True}) == AndFilter(PodpostTextFilter("abc"), + FavoriteFilter(True)) diff --git a/test/test_search_dictparser_orders.py b/test/test_search_dictparser_orders.py new file mode 100644 index 0000000..2f0b032 --- /dev/null +++ b/test/test_search_dictparser_orders.py @@ -0,0 +1,31 @@ +from podcast.search.DictParser import * + + +def test_empty_options(): + assert create_filter_from_options({}) == None + assert create_order_from_options({}) == PodpostPublishedDateOrder(Direction.DESC) + + +def test_order_only(): + assert create_order_from_options({"order_dir": "desc"}) == PodpostPublishedDateOrder(Direction.DESC) + assert create_order_from_options({"order_dir": "asc"}) == PodpostPublishedDateOrder(Direction.ASC) + + +def test_order_published(): + assert create_order_from_options({"order": "published", "order_dir": "desc"}) == PodpostPublishedDateOrder( + Direction.DESC) + assert create_order_from_options({"order": "published", "order_dir": "asc"}) == PodpostPublishedDateOrder( + Direction.ASC) + + +def test_order_inserted(): + assert create_order_from_options({"order": "inserted", "order_dir": "desc"}) == PodpostListInsertDateOrder( + Direction.DESC) + assert create_order_from_options({"order": "inserted", "order_dir": "asc"}) == PodpostListInsertDateOrder( + Direction.ASC) + + +def test_real_case(): + options = {'fav_filter': False, 'order': 'published', 'order_dir': 'asc', + 'podcast_url': 'https://augenzu.podigee.io/feed/ogg', 'searchtext': ''} + assert create_order_from_options(options) == PodpostPublishedDateOrder(Direction.ASC) diff --git a/translations/harbour-podqast-de.ts b/translations/harbour-podqast-de.ts index 517ab0e..167a4e0 100644 --- a/translations/harbour-podqast-de.ts +++ b/translations/harbour-podqast-de.ts @@ -582,12 +582,12 @@ PodpostList - + Rendering Zeichne - + Creating items Erzeuge Elemente diff --git a/translations/harbour-podqast-es.ts b/translations/harbour-podqast-es.ts index a37291a..0212856 100644 --- a/translations/harbour-podqast-es.ts +++ b/translations/harbour-podqast-es.ts @@ -582,12 +582,12 @@ PodpostList - + Rendering Generando - + Creating items Creando elementos diff --git a/translations/harbour-podqast-fr.ts b/translations/harbour-podqast-fr.ts index 7a73ca4..6e32ba7 100644 --- a/translations/harbour-podqast-fr.ts +++ b/translations/harbour-podqast-fr.ts @@ -583,12 +583,12 @@ PodpostList - + Rendering Chargement - + Creating items Génération des éléments diff --git a/translations/harbour-podqast-sv.ts b/translations/harbour-podqast-sv.ts index 5a1ea72..5c075ba 100644 --- a/translations/harbour-podqast-sv.ts +++ b/translations/harbour-podqast-sv.ts @@ -582,12 +582,12 @@ PodpostList - + Rendering Rendering - + Creating items Skapar objekt diff --git a/translations/harbour-podqast-zh_CN.ts b/translations/harbour-podqast-zh_CN.ts index a41101a..13ce2bb 100644 --- a/translations/harbour-podqast-zh_CN.ts +++ b/translations/harbour-podqast-zh_CN.ts @@ -582,12 +582,12 @@ PodpostList - + Rendering 更新 - + Creating items 创建新项目 diff --git a/translations/harbour-podqast.ts b/translations/harbour-podqast.ts index 004746b..fcc0f0a 100644 --- a/translations/harbour-podqast.ts +++ b/translations/harbour-podqast.ts @@ -582,12 +582,12 @@ PodpostList - + Rendering Rendering - + Creating items Creating items -- GitLab From 03dd21e291dd9cc8dd26195817a51319465928ac Mon Sep 17 00:00:00 2001 From: Thilo Kogge Date: Sat, 24 Jul 2021 19:42:08 +0200 Subject: [PATCH 07/20] fixed linter errors --- python/podcast/search/SearchBase.py | 1 - qml/components/FeedParser.py | 1 - 2 files changed, 2 deletions(-) diff --git a/python/podcast/search/SearchBase.py b/python/podcast/search/SearchBase.py index 09f6f19..27d96a3 100644 --- a/python/podcast/search/SearchBase.py +++ b/python/podcast/search/SearchBase.py @@ -1,6 +1,5 @@ import logging from abc import abstractmethod, ABC -from collections import Iterator from peewee import ModelSelect from podcast.inbox import InboxEntry diff --git a/qml/components/FeedParser.py b/qml/components/FeedParser.py index b4cfe31..ec274b9 100644 --- a/qml/components/FeedParser.py +++ b/qml/components/FeedParser.py @@ -17,7 +17,6 @@ from podcast.podcast import PodcastFactory, Podcast from podcast.podcastlist import PodcastListFactory from podcast.archive import ArchiveFactory from podcast.search import DictParser -from podcast.search.SearchBase import AllSearchBase from podcast.util import create_opml, movePost, chunks logger = logging.getLogger(__name__) -- GitLab From 094fd4af36dbb377d8af050ba7b183ceca251857 Mon Sep 17 00:00:00 2001 From: Thilo Kogge Date: Sat, 24 Jul 2021 19:49:38 +0200 Subject: [PATCH 08/20] updating listened flag when duration close to end --- python/podcast/podpost.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/python/podcast/podpost.py b/python/podcast/podpost.py index 02cec5f..93e3a68 100644 --- a/python/podcast/podpost.py +++ b/python/podcast/podpost.py @@ -385,8 +385,12 @@ class Podpost(BaseModel): """ self.position = position + if self.position > 0 and ( + self.duration * 1000 - self.position < Constants().markListenedBeforeEndThreshold * 1000): + self.listened = True PodpostFactory().persist(self) + @property def get_position(self): """ -- GitLab From ceee554e3d962a42a21358c51c0f3535687671f2 Mon Sep 17 00:00:00 2001 From: Thilo Kogge Date: Thu, 12 Aug 2021 07:47:09 +0200 Subject: [PATCH 09/20] handle itunes new feed url by changing the url of the podcast --- python/podcast/feedutils.py | 9 ++++++++- python/podcast/persistent_log.py | 1 + python/podcast/podcast.py | 18 +++++++++++++++--- test/conftest.py | 8 ++++++++ test/test_feedutils.py | 16 ++++++++++++++++ test/test_podcast.py | 19 +++++++++++++++++++ test/testdata/new-feed-url-feed.rss | 6 ++++++ 7 files changed, 73 insertions(+), 4 deletions(-) create mode 100644 test/test_feedutils.py create mode 100644 test/testdata/new-feed-url-feed.rss diff --git a/python/podcast/feedutils.py b/python/podcast/feedutils.py index 2f5485a..599a122 100644 --- a/python/podcast/feedutils.py +++ b/python/podcast/feedutils.py @@ -34,12 +34,14 @@ def fetch_feed(published, url) -> FeedParserDict: if feed.status == 304: raise NotModified() if feed.bozo != 0: - exc : Exception = feed.bozo_exception + exc: Exception = feed.bozo_exception if type(exc) != feedparser.CharacterEncodingOverride: logger.exception( "Podcast init: error in parsing feed %s", str(type(exc)), exc_info=exc ) raise FeedFetchingError(exc.message if hasattr(exc, 'message') else "message_missing") + if "itunes_new-feed-url" in feed.feed and feed.feed["itunes_new-feed-url"] != url: + raise NewFeedUrlError(feed.feed["itunes_new-feed-url"]) logger.info( "podcast init size of entries: %d", len(feed.entries) ) @@ -67,3 +69,8 @@ class FeedFetchingError(BaseException): class NotModified(Exception): pass + + +class NewFeedUrlError(BaseException): + def __init__(self, url): + self.url = url diff --git a/python/podcast/persistent_log.py b/python/podcast/persistent_log.py index 60508d5..ace1590 100644 --- a/python/podcast/persistent_log.py +++ b/python/podcast/persistent_log.py @@ -18,6 +18,7 @@ class LogType(Enum): AddPodcastError = "AddPodcastError" SuccessfulRefresh = "SuccessfulRefresh" Refresh304 = "Refresh304" + FeedRedirect = "FeedRedirect" class LogMessage(BaseModel): diff --git a/python/podcast/podcast.py b/python/podcast/podcast.py index 32785ab..83d6945 100644 --- a/python/podcast/podcast.py +++ b/python/podcast/podcast.py @@ -19,7 +19,7 @@ if TYPE_CHECKING: from podcast.constants import Constants, BaseModel from podcast.factory import BaseFactory -from podcast.feedutils import fetch_feed, FeedFetchingError, NotModified +from podcast.feedutils import fetch_feed, FeedFetchingError, NotModified, NewFeedUrlError from podcast.cache import Cache from podcast import util, feedutils @@ -83,6 +83,10 @@ class Podcast(BaseModel): feedCache.store(url, feed) except NotModified: raise FeedFetchingError("Should not get 304 on initial fetching") + except NewFeedUrlError as e: + persist_log(LogType.FeedRedirect, msg="permanently redirected feed", + oldurl=url, newurl=e.url) + return cls.create_from_url(e.url, preview, num_preview_items) episodes = cls.podcast_from_feed(feed, podcast, preview, url, num_preview_items) return podcast, episodes except ValueError: @@ -147,7 +151,7 @@ class Podcast(BaseModel): href_known = "href" in entry and entry.href in known_hrefs has_guid = "guid" in entry guid_known = has_guid and entry["guid"] in known_guids - #logger.debug("entry knowncheck: %s %s %s", has_guid, guid_known, href_known) + # logger.debug("entry knowncheck: %s %s %s", has_guid, guid_known, href_known) return guid_known or href_known if self.autolimit: @@ -226,7 +230,8 @@ class Podcast(BaseModel): "title": title, "description": description, "descriptionfull": descriptionfull, - "logo_url": self.logo() + "logo_url": self.logo(), + "episodecount": self.episodes.count() } def _set_alt_feeds(self, feed): @@ -320,6 +325,7 @@ class Podcast(BaseModel): pyotherside.send("refreshPost", self.title) try: feed = fetch_feed(self.published if not full_refresh else 0, self.url) + self.__set_data_from_feed(feed) PodcastFactory().persist(self) @@ -339,6 +345,12 @@ class Podcast(BaseModel): except FeedFetchingError as ffe: logger.exception("Could not fetch feed") persist_log(LogType.FeedParseError, title=self.title, errormsg=ffe) + except NewFeedUrlError as e: + self.url = e.url + PodcastFactory().persist(self) + persist_log(LogType.FeedRedirect, title=self.title, msg="permanently redirected feed", oldurl=self.url, + newurl=e.url) + yield from self.refresh(moveto, limit, full_refresh) except Exception as e: persist_log(LogType.Exception, msg="during refresh", podcasttitle=self.title, exception=e) diff --git a/test/conftest.py b/test/conftest.py index 26f81ae..6d76373 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -2,14 +2,22 @@ import logging import pytest + + @pytest.fixture(autouse=True, scope="function") def cleanup(): cleanup_archive() cleanup_queue() cleanup_inbox() cleanup_podcast() + cleanup_log() yield +def cleanup_log(): + from podcast.persistent_log import LogMessage + LogMessage.delete().execute() + + def cleanup_inbox(): from podcast.inbox import InboxEntry InboxEntry.delete().execute() diff --git a/test/test_feedutils.py b/test/test_feedutils.py new file mode 100644 index 0000000..ac4293d --- /dev/null +++ b/test/test_feedutils.py @@ -0,0 +1,16 @@ +import httpretty + +from podcast.feedutils import fetch_feed, NewFeedUrlError +from test import read_testdata, xml_headers + + +@httpretty.activate +def test_new_feed_url(): + url_f = "https://fakefeed.com/" + httpretty.HTTPretty.register_uri(httpretty.HTTPretty.GET, url_f, body=read_testdata('testdata/new-feed-url-feed.rss'), + adding_headers=xml_headers) + try: + fetch_feed(None,url_f) + assert False + except NewFeedUrlError: + pass diff --git a/test/test_podcast.py b/test/test_podcast.py index 78b843d..a7866e4 100644 --- a/test/test_podcast.py +++ b/test/test_podcast.py @@ -7,9 +7,11 @@ from typing import Tuple, List import httpretty import pytest from httpretty import HTTPretty +from more_itertools import first from peewee import DoesNotExist from podcast import POST_ID_TYPE +from podcast.persistent_log import get_log_messages, LogType from podcast.podcastlist import PodcastList from podcast.util import ilen from test import xml_headers, read_testdata @@ -27,6 +29,9 @@ def test_feed_entry(): Get a feed entry """ url_sn = "http://feeds.twit.tv/sn.xml" + HTTPretty.register_uri(HTTPretty.GET, url_sn, + body=read_testdata('testdata/twittv.xml'), adding_headers=xml_headers) + url_sn = "https://feeds.twit.tv/sn.xml" HTTPretty.register_uri(HTTPretty.GET, url_sn, body=read_testdata('testdata/twittv.xml'), adding_headers=xml_headers) url_fs = 'https://freakshow.fm/feed/opus/' @@ -267,3 +272,17 @@ def test_pagination(): assert 1 == podcast.count_episodes() list(podcast.refresh(0,0,True)) assert 2 == podcast.count_episodes() + + +@httpretty.activate +def test_new_feed_url(): + url_f = "https://fakefeed.com/" + HTTPretty.register_uri(HTTPretty.GET, url_f, body=read_testdata('testdata/new-feed-url-feed.rss'), + adding_headers=xml_headers) + url_f1 = "https://newhoster.com/feed/ogg" + HTTPretty.register_uri(HTTPretty.GET, url_f1, body=read_testdata('testdata/fakefeed.xml'), + adding_headers=xml_headers) + podcast, episodes = Podcast.create_from_url(url_f) + assert podcast.url == "https://newhoster.com/feed/ogg" + assert 1 == ilen(get_log_messages()) + assert list(get_log_messages())[0].messagetype == LogType.FeedRedirect.name \ No newline at end of file diff --git a/test/testdata/new-feed-url-feed.rss b/test/testdata/new-feed-url-feed.rss new file mode 100644 index 0000000..461e400 --- /dev/null +++ b/test/testdata/new-feed-url-feed.rss @@ -0,0 +1,6 @@ + + + + https://newhoster.com/feed/ogg + + \ No newline at end of file -- GitLab From 243f1227846ab3f6ca7dbfd09c879439e1ea8bde Mon Sep 17 00:00:00 2001 From: Thilo Kogge Date: Thu, 12 Aug 2021 08:04:07 +0200 Subject: [PATCH 10/20] removed podpost.podurl, removed PodSelectGrid.qml and dependent functions --- harbour-podqast.pro | 1 - python/podcast/archive.py | 3 +- python/podcast/data_migration.py | 3 - python/podcast/favorite.py | 44 ----------- python/podcast/inbox.py | 3 +- python/podcast/podpost.py | 8 +- python/podcast/queue.py | 11 +-- python/podcast/search/DictParser.py | 7 +- qml/components/ArchiveHandler.py | 24 ------ qml/components/ExternalHandler.py | 26 ------- qml/components/ExternalHandlerPython.qml | 23 ++---- qml/components/FavoriteHandler.py | 10 --- qml/components/FavoriteHandlerPython.qml | 3 - qml/components/FeedParser.py | 2 +- qml/components/InboxHandler.py | 21 ------ qml/components/PodSelectGrid.qml | 93 ------------------------ qml/pages/History.qml | 1 - qml/pages/Inbox.qml | 22 +----- test/test_favorites.py | 3 - test/test_queue.py | 5 -- translations/harbour-podqast-de.ts | 35 ++++++--- translations/harbour-podqast-es.ts | 35 ++++++--- translations/harbour-podqast-fr.ts | 35 ++++++--- translations/harbour-podqast-sv.ts | 35 ++++++--- translations/harbour-podqast-zh_CN.ts | 35 ++++++--- translations/harbour-podqast.ts | 35 ++++++--- 26 files changed, 171 insertions(+), 352 deletions(-) delete mode 100644 qml/components/PodSelectGrid.qml diff --git a/harbour-podqast.pro b/harbour-podqast.pro index ee9b0d5..c7679a5 100644 --- a/harbour-podqast.pro +++ b/harbour-podqast.pro @@ -63,7 +63,6 @@ DISTFILES += qml/podqast.qml \ qml/components/ArchivePostListItem.qml \ qml/pages/History.qml \ qml/pages/Favorites.qml \ - qml/components/PodSelectGrid.qml \ qml/components/PodcastItem.qml \ qml/pages/PodpostList.qml \ qml/pages/Settings.qml \ diff --git a/python/podcast/archive.py b/python/podcast/archive.py index 98e850e..2a46784 100644 --- a/python/podcast/archive.py +++ b/python/podcast/archive.py @@ -9,6 +9,7 @@ from typing import List, Iterator from peewee import ModelSelect, IntegrityError from podcast import POST_ID_TYPE from podcast.AbstractPodpostListEntry import AbstractPodpostListEntry +from podcast.podcast import PodcastFactory sys.path.append("../") @@ -64,7 +65,7 @@ class Archive: Iterator[Podpost]: query: ModelSelect = ArchiveEntry.select(Podpost).join(Podpost) if url_filter: - query = query.where(Podpost.podurl == url_filter) + query = query.where(Podpost.podcast == PodcastFactory().get_podcast(url_filter)) if filter_favorite: query = query.where(Podpost.favorite == True) if sort_by_insertdate: diff --git a/python/podcast/data_migration.py b/python/podcast/data_migration.py index 07862c0..f428b23 100644 --- a/python/podcast/data_migration.py +++ b/python/podcast/data_migration.py @@ -254,11 +254,8 @@ def create_post_from_store_dict(post_dict, podcast): def migrate_podpost_v0_v1(post_dict: dict, podpost: Podpost, do_persist=True): if not podpost: raise ValueError("podpost must not be none") - if hasattr(podpost, "entry") and not podpost.isaudio: - podpost.init(podpost.entry, podpost.logo_url, podpost.podurl) for attr in ["favorite", "file_path", "percentage", "position", "state"]: transfer_attribute(attr, post_dict, podpost) - if do_persist: PodpostFactory().persist(podpost) diff --git a/python/podcast/favorite.py b/python/podcast/favorite.py index c529d23..2cd99c8 100644 --- a/python/podcast/favorite.py +++ b/python/podcast/favorite.py @@ -1,30 +1,6 @@ from podcast.podcast import PodcastFactory from podcast.podpost import Podpost - -def get_queue_favorites(podurl=None): - """ - Return a list of all favorites posts from queue - """ - - from podcast.queue import QueueFactory - - return list(QueueFactory().get_favorites(podurl)) - - - -def append_archive_favorites(posts, podurl=None): - """ - append archive favorites to posts list - """ - - from podcast.archive import ArchiveFactory - - archive = ArchiveFactory().get_archive() - return list(archive.get_podpost_objects(url_filter=podurl,filter_favorite=True)) - - - def get_favorites(podurl=None): query = Podpost.select() if podurl: @@ -33,23 +9,3 @@ def get_favorites(podurl=None): else: query = query.where(Podpost.favorite) return query.order_by(Podpost.insert_date.desc()).iterator() - - -def get_favorite_podcast_stats(): - """ - """ - - entries = {} - - posts = get_queue_favorites() - posts = append_archive_favorites(posts) - for p in posts: - if p.podurl in entries: - entries[p.podurl]["count"] += 1 - else: - if p.logo_url: - logo_url = p.logo_url - else: - logo_url = "../../images/podcast.png" - entries[p.podurl] = {"count": 1, "logo_url": logo_url} - return entries diff --git a/python/podcast/inbox.py b/python/podcast/inbox.py index fbd3996..5f73a11 100644 --- a/python/podcast/inbox.py +++ b/python/podcast/inbox.py @@ -8,6 +8,7 @@ from typing import List, Iterator from peewee import ModelSelect from podcast import POST_ID_TYPE from podcast.AbstractPodpostListEntry import AbstractPodpostListEntry +from podcast.podcast import Podcast from podcast.podpost import Podpost sys.path.append("../") @@ -58,7 +59,7 @@ class Inbox: def get_podposts_objects(self, podurl=None) -> Iterator[Podpost]: query: ModelSelect = self.__select_inbox_entries(Podpost) if podurl: - query = query.where(Podpost.podurl == podurl) + query = query.join(Podcast).where(Podpost.podcast.url == podurl) for e in query: yield e.podpost diff --git a/python/podcast/podpost.py b/python/podcast/podpost.py index 93e3a68..62a196f 100644 --- a/python/podcast/podpost.py +++ b/python/podcast/podpost.py @@ -64,8 +64,6 @@ class Podpost(BaseModel): # download percentage percentage: float = FloatField(default=0) plainpart: TextField = TextField(default="") - # the feed's url - podurl: str = TextField(null=True) position: int = IntegerField(default=0) podcast = ForeignKeyField(Podcast, null=True, backref='episodes', lazy_load=True, on_delete='CASCADE') # when the post was published according to feed @@ -97,7 +95,6 @@ class Podpost(BaseModel): post = cls() post.podcast = podcast post.logo_url = logo_url - post.podurl = podurl if logo_url and len(logo_url) > 0 and logo_url[0] == "/": post.logo_path = logo_url else: @@ -203,7 +200,6 @@ class Podpost(BaseModel): post.length = int(filesize) post.published = 0 post.type = "audio/" + tech["filetype"] - post.podurl = afile post.isaudio = True return post @@ -237,7 +233,7 @@ class Podpost(BaseModel): return { "id": self.id, "title": self.title, - "url": self.podurl, + "url": self.podcast.url, "logo_url": image, "link": self.link, "description": self.plainpart, @@ -599,7 +595,7 @@ class PodpostFactory(BaseFactory): from podcast.podcast import Podcast if (type(podcast) != Podcast): raise ValueError("supplied argument must be a podcast") - query: ModelSelect = Podpost.select().where(Podpost.podurl == podcast.url).order_by(Podpost.published.desc()) + query: ModelSelect = Podpost.select().where(Podpost.podcast == podcast).order_by(Podpost.published.desc()) if limit > 0: query = query.limit(limit) for post in query.iterator(): diff --git a/python/podcast/queue.py b/python/podcast/queue.py index bafec02..4fc1d06 100644 --- a/python/podcast/queue.py +++ b/python/podcast/queue.py @@ -373,13 +373,4 @@ class QueueFactory(metaclass=Singleton): logger.warning("Podpost %s is in queue but not found", postid) def get_first_podpost_object(self): - return next(self.get_podposts_objects(), None) - - def get_favorites(self, podurl) -> Iterator[Podpost]: - for post in self.get_podposts_objects(): - if post.favorite: - if podurl: - if post.podurl == podurl: - yield post - else: - yield post + return next(self.get_podposts_objects(), None) \ No newline at end of file diff --git a/python/podcast/search/DictParser.py b/python/podcast/search/DictParser.py index e4697e9..5e1a5f4 100644 --- a/python/podcast/search/DictParser.py +++ b/python/podcast/search/DictParser.py @@ -14,11 +14,8 @@ def iterator_from_searchoptions(searchoptions: Dict) -> Iterator[Dict]: filter = create_filter_from_options(searchoptions, podcast_cache) order = create_order_from_options(searchoptions) + return (post.get_data() for post in AllSearchBase().iter_search(filter, order)) - if "podcast_url" in searchoptions: - return (post.get_data() for post in AllSearchBase().iter_search(filter, order)) - else: - raise NotImplementedError("can only get episodes of a single podcast right now") def create_filter_from_options(searchoptions: Dict, podcast_cache: Dict = None) -> Filter: @@ -45,6 +42,8 @@ def create_filter_from_options(searchoptions: Dict, podcast_cache: Dict = None) if url not in podcast_cache: raise AttributeError("podcast filter defined but podcast not cached") filter: Filter = and_add_filter(filter, PodcastFilter(podcast_cache[url])) + if "podcast_id" in searchoptions: + filter: Filter = and_add_filter(filter, PodcastFilter(searchoptions["podcast_id"])) return filter diff --git a/qml/components/ArchiveHandler.py b/qml/components/ArchiveHandler.py index 4b0f2a0..f66bb15 100644 --- a/qml/components/ArchiveHandler.py +++ b/qml/components/ArchiveHandler.py @@ -22,34 +22,10 @@ async def get_archive_posts(podurl=None): pyotherside.send("historyData", offset, [post.get_data() for post in chunk]) -async def get_archive_pod_data(): - """ - """ - - entries = {} - - archive = ArchiveFactory().get_archive() - - for entry in archive.get_podpost_objects(): - if entry.podurl in entries: - entries[entry.podurl]["count"] += 1 - else: - if entry.logo_url: - logo_url = entry.logo_url - else: - logo_url = "../../images/podcast.png" - entries[entry.podurl] = {"count": 1, "logo_url": logo_url} - - pyotherside.send("archivePodList", entries) - - class ArchiveHandler: def getarchiveposts(self, podurl=None): asyncio.run(get_archive_posts(podurl)) - def getarchivepoddata(self): - asyncio.run(get_archive_pod_data()) - archivehandler = ArchiveHandler() diff --git a/qml/components/ExternalHandler.py b/qml/components/ExternalHandler.py index 7ba3c1e..fefafee 100644 --- a/qml/components/ExternalHandler.py +++ b/qml/components/ExternalHandler.py @@ -139,26 +139,6 @@ def get_external_posts(): pyotherside.send("createExternalList", entries) -def get_external_pod_data(): - """ - """ - - entries = {} - - external = ExternalFactory().get_external() - - for filename, entry in external.get_podposts_objects(): - if entry.podurl in entries: - entries[entry.podurl]["count"] += 1 - else: - if entry.logo_url: - logo_url = entry.logo_url - else: - logo_url = "../../images/podcast.png" - entries[entry.podurl] = {"count": 1, "logo_url": logo_url} - - pyotherside.send("archivePodList", entries) - def get_audio_data(afile): """ @@ -189,12 +169,6 @@ class ExternalHandler: self.bgthread2 = threading.Thread(target=get_external_posts) self.bgthread2.start() - def getexternalpoddata(self): - if self.bgthread.is_alive(): - return - self.bgthread = threading.Thread(target=get_external_pod_data) - self.bgthread.start() - def waitnew(self): if self.inothread.is_alive(): return diff --git a/qml/components/ExternalHandlerPython.qml b/qml/components/ExternalHandlerPython.qml index 6dda009..eb73ae7 100644 --- a/qml/components/ExternalHandlerPython.qml +++ b/qml/components/ExternalHandlerPython.qml @@ -6,19 +6,17 @@ Python { id: externalhandler signal createExternalList(var data) - signal externalPodList(var data) - signal externalUpdated() - signal objectLoaded() + signal externalUpdated + signal objectLoaded signal audioInfo(string info) Component.onCompleted: { setHandler("createExternalList", createExternalList) - setHandler("externalPodList", externalPodList) setHandler("externalUpdated", externalUpdated) setHandler("objectLoaded", objectLoaded) setHandler("audioInfo", audioInfo) - addImportPath(Qt.resolvedUrl('.')); + addImportPath(Qt.resolvedUrl('.')) importModule('ExternalHandler', function () { console.log('ExternalHandler is now imported') }) @@ -26,26 +24,21 @@ Python { function getExternalEntries() { // call("ExternalHandler.externalhandler.getexternalposts", function() {}); - call("ExternalHandler.get_external_posts", function() {}); + call("ExternalHandler.get_external_posts", function () {}) } -// function getExternalPodData() { -// call("ExternalHandler.externalhandler.getexternalpoddata", function() {}); -// // call("ExternalHandler.get_external_pod_data", function() {}); -// } function waitNew() { - call("ExternalHandler.externalhandler.waitnew", function() {}); + call("ExternalHandler.externalhandler.waitnew", function () {}) // call("ExternalHandler.wait_new", function() {}); } function checkNew() { - call("ExternalHandler.check_new", function() {}); + call("ExternalHandler.check_new", function () {}) } function getAudioData(audio_file) { - call("ExternalHandler.get_audio_data", [audio_file], function() {}) + call("ExternalHandler.get_audio_data", [audio_file], function () {}) } onError: { - console.log('python error: ' + traceback); + console.log('python error: ' + traceback) } - } diff --git a/qml/components/FavoriteHandler.py b/qml/components/FavoriteHandler.py index 34cab37..1cde4fd 100644 --- a/qml/components/FavoriteHandler.py +++ b/qml/components/FavoriteHandler.py @@ -32,10 +32,6 @@ def send_favorites(podurl=None): for offset, max, chunk in chunks(posts, 32): pyotherside.send("favoriteListData", offset, [post.get_data() for post in chunk]) -def get_fav_pod_data(): - pyotherside.send("archivePodList", favorite.get_favorite_podcast_stats()) - - class FavoriteHandler: def __init__(self): self.bgthread = threading.Thread() @@ -62,11 +58,5 @@ class FavoriteHandler: self.bgthread2 = threading.Thread(target=send_favorites) self.bgthread2.start() - def getfavpoddata(self): - if self.bgthread.is_alive(): - return - self.bgthread = threading.Thread(target=get_fav_pod_data) - self.bgthread.start() - favoritehandler = FavoriteHandler() diff --git a/qml/components/FavoriteHandlerPython.qml b/qml/components/FavoriteHandlerPython.qml index 98f0738..ec8902b 100644 --- a/qml/components/FavoriteHandlerPython.qml +++ b/qml/components/FavoriteHandlerPython.qml @@ -29,7 +29,4 @@ Python { } } - function getFavPodData() { - call("FavoriteHandler.favoritehandler.getfavpoddata", function() {}); - } } diff --git a/qml/components/FeedParser.py b/qml/components/FeedParser.py index ec274b9..14f4dc4 100644 --- a/qml/components/FeedParser.py +++ b/qml/components/FeedParser.py @@ -148,7 +148,7 @@ class FeedParser: podcast_list = PodcastListFactory().get_podcast_list() - for postid, podcasttitle, posttitle, move in podcast_list.refwresh(moveto, limit, + for postid, podcasttitle, posttitle, move in podcast_list.refresh(moveto, limit, full_refresh=full_refresh): try: page = movePost(move, postid) diff --git a/qml/components/InboxHandler.py b/qml/components/InboxHandler.py index c9ee361..ecb1d2b 100644 --- a/qml/components/InboxHandler.py +++ b/qml/components/InboxHandler.py @@ -23,27 +23,6 @@ def get_inbox_posts(podurl=None): pyotherside.send("inboxData", offset, [post.get_data() for post in chunk]) -def get_inbox_pod_data(): - """ - """ - - entries = {} - - inbox = InboxFactory().get_inbox() - - for entry in inbox.get_podposts_objects(): - if entry.podurl in entries: - entries[entry.podurl]["count"] += 1 - else: - if entry.logo_url: - logo_url = entry.logo_url - else: - logo_url = "../../images/podcast.png" - entries[entry.podurl] = {"count": 1, "logo_url": logo_url} - - pyotherside.send("archivePodList", entries) - - def move_queue_top(podpost): """ Move element to top of queue diff --git a/qml/components/PodSelectGrid.qml b/qml/components/PodSelectGrid.qml deleted file mode 100644 index 603ceaa..0000000 --- a/qml/components/PodSelectGrid.qml +++ /dev/null @@ -1,93 +0,0 @@ -import QtQuick 2.0 -import Sailfish.Silica 1.0 -import Nemo.Thumbnailer 1.0 - -SilicaListView { - id: gridview - clip: true - width: parent.width - height: Theme.itemSizeLarge - orientation: ListView.Horizontal - - property string homeicon: "image://theme/icon-m-backup" - property var filter: podqast.hfilter - - // HorizontalScrollDecorator {} - - Connections { - target: archivehandler - onArchivePodList: { - console.log("onArchivePodList") - podselectmodel.clear() - - var keys = Object.keys(data) - - console.log("keys length " + keys.length) - console.log(data) - // gridview.width = keys.length * Theme.itemSizeMedium - gridview.contentWidth = keys.length * Theme.itemSizeMedium - - var thecount = 0 - - for (var i = 0; i < keys.length; i++ ) { - console.log(keys[i]) - console.log(data[keys[i]].count) - console.log(data[keys[i]].logo_url) - thecount += data[keys[i]].count - data[keys[i]].podurl = keys[i] - data[keys[i]].is_enabled = (keys[i] === filter) - - podselectmodel.append(data[keys[i]]) - } - podselectmodel.insert(0, { - podurl: "home", - logo_url : homeicon, - count: thecount, - is_enabled: filter === "home" - }) -// podselectmodel.insert(0, { -// podurl: "none", -// logo_url: "image://theme/icon-m-search", -// count: 0, -// is_enabled: false -// }) - } - } - - model: ListModel { - id: podselectmodel - } - - delegate: ListItem { - width: Theme.iconSizeLarge - height: Theme.iconSizeLarge - highlighted: is_enabled - Image { - asynchronous:true - source: logo_url - anchors.top: parent.top - anchors.horizontalCenter: parent.horizontalCenter - width: Theme.iconSizeMedium - height: Theme.iconSizeMedium - // opacity: is_enabled ? 1.0 : 0.8 - Label { - anchors.horizontalCenter: parent.horizontalCenter - anchors.top: parent.bottom - text: count - font.pixelSize: Theme.fontSizeExtraSmall - font.bold: true - color: Theme.highlightColor - z: 2 - } - MouseArea { - anchors.fill: parent - onClicked: { - console.log(podurl) - if (podurl != "none") { - filter = podurl - } - } - } - } - } -} diff --git a/qml/pages/History.qml b/qml/pages/History.qml index 16c1f7d..f62ca10 100644 --- a/qml/pages/History.qml +++ b/qml/pages/History.qml @@ -50,7 +50,6 @@ Page { SilicaListView { id: archivepostlist clip: true - // anchors.top: podselectgrid.bottom anchors.top: archivetitle.bottom width: parent.width height: page.height - pdp.height - archivetitle.height diff --git a/qml/pages/Inbox.qml b/qml/pages/Inbox.qml index daf11e5..e1c266d 100644 --- a/qml/pages/Inbox.qml +++ b/qml/pages/Inbox.qml @@ -77,30 +77,12 @@ Page { } } - // PodSelectGrid { - // id: podselectgrid - // homeicon: "image://theme/icon-m-media-playlists" - // anchors.top: inboxtitle.bottom - // height: Theme.iconSizeLarge - // z: 3 - // filter: podqast.ifilter - // Component.onCompleted: { - // // inboxhandler.getInboxPodData() - // } - // onFilterChanged: { - // podqast.ifilter = filter - // inboxPostModel.clear() - // inboxhandler.getInboxPodData() - // inboxhandler.getInboxEntries(podqast.ifilter) - // } - // } SilicaListView { id: inboxpostlist clip: true - // anchors.top: podselectgrid.bottom anchors.top: inboxtitle.bottom width: parent.width - height: page.height - pdp.height - inboxtitle.height // - podselectgrid.height + height: page.height - pdp.height - inboxtitle.height section.property: 'section' section.delegate: SectionHeader { text: section @@ -110,7 +92,7 @@ Page { enabled: inboxPostModel.count == 0 text: qsTr("No new posts") hintText: qsTr("Pull down to Discover new podcasts, get posts from Library, or play the Playlist") - verticalOffset: -inboxtitle.height // - podselectgrid.height + verticalOffset: -inboxtitle.height } model: ListModel { diff --git a/test/test_favorites.py b/test/test_favorites.py index b8cb9b8..c20e399 100644 --- a/test/test_favorites.py +++ b/test/test_favorites.py @@ -27,7 +27,4 @@ def test_queue_favorites(): post.favorite = True PodpostFactory().persist(post) archive.insert(post.id) - assert len(favorite.get_queue_favorites()) == 0 assert ilen(favorite.get_favorites()) == 5 - assert len(favorite.get_favorite_podcast_stats()) == 1 - assert favorite.get_favorite_podcast_stats()[url]['count'] == 5 diff --git a/test/test_queue.py b/test/test_queue.py index 44b6301..3682ea9 100644 --- a/test/test_queue.py +++ b/test/test_queue.py @@ -116,11 +116,6 @@ def test_queue_favorites(): for id in podcast.entry_ids_old_to_new: queue.insert_bottom(id) assert len(queue.podposts) == 20 - assert len(favorite.get_queue_favorites('https://wrong.url')) == 0 - assert [fav.id for fav in favorite.get_queue_favorites()] == [podcast.entry_ids_old_to_new[i] for i in - range(0, 10, 2)] - assert len(favorite.get_queue_favorites(url)) == 5 for id in podcast.entry_ids_old_to_new: queue.remove(id) assert len(queue.podposts) == 0 - assert len(favorite.get_queue_favorites()) == 0 diff --git a/translations/harbour-podqast-de.ts b/translations/harbour-podqast-de.ts index 167a4e0..86a3361 100644 --- a/translations/harbour-podqast-de.ts +++ b/translations/harbour-podqast-de.ts @@ -531,42 +531,57 @@ PodcastSettings - - Posts - Beiträge + + Settings + Einstellungen - + + URL + + + + + Link + + + + + %1 Posts + + + + Move new post to Neuen Beitrag - + Inbox Eingang - + Top of Playlist an den Anfang der Playlist - + Bottom of Playlist ans Ende der Playlist - + Archive in das Archiv - + Automatic post limit Höchstgrenze automatisierter Beiträge - + Audio playrate Abspielgeschwindigkeit diff --git a/translations/harbour-podqast-es.ts b/translations/harbour-podqast-es.ts index 0212856..19356c8 100644 --- a/translations/harbour-podqast-es.ts +++ b/translations/harbour-podqast-es.ts @@ -531,42 +531,57 @@ PodcastSettings - - Posts - Publicaciones + + Settings + Ajustes + + + + URL + + + + + Link + + + + + %1 Posts + - + Move new post to Mover nueva publicación a - + Inbox Entradas - + Top of Playlist Al principio de la lista - + Bottom of Playlist Al final de la lista - + Archive Almacén - + Automatic post limit Límite de publicaciones automáticas - + Audio playrate Velocidad de reproducción diff --git a/translations/harbour-podqast-fr.ts b/translations/harbour-podqast-fr.ts index 6e32ba7..427fd91 100644 --- a/translations/harbour-podqast-fr.ts +++ b/translations/harbour-podqast-fr.ts @@ -532,42 +532,57 @@ PodcastSettings - - Posts - Articles + + Settings + Paramètres + + + + URL + + + + + Link + + + + + %1 Posts + - + Move new post to Transférer les nouveaux articles - + Inbox Dans la Boîte aux lettres - + Top of Playlist En début de Playlist - + Bottom of Playlist En fin de playlist - + Archive Aux Archives - + Automatic post limit Limite automatique du nombre d'articles - + Audio playrate Vitesse de lecture diff --git a/translations/harbour-podqast-sv.ts b/translations/harbour-podqast-sv.ts index 5c075ba..c275354 100644 --- a/translations/harbour-podqast-sv.ts +++ b/translations/harbour-podqast-sv.ts @@ -531,42 +531,57 @@ PodcastSettings - - Posts - Poster + + Settings + Inställningar + + + + URL + + + + + Link + + + + + %1 Posts + - + Move new post to Flytta ny post till - + Inbox Inkorg - + Top of Playlist Först i spelningslistan - + Bottom of Playlist Sist i spelningslistan - + Archive Arkiv - + Automatic post limit Automatisk postbegränsning - + Audio playrate Spelningsfrekvens diff --git a/translations/harbour-podqast-zh_CN.ts b/translations/harbour-podqast-zh_CN.ts index 13ce2bb..d34b112 100644 --- a/translations/harbour-podqast-zh_CN.ts +++ b/translations/harbour-podqast-zh_CN.ts @@ -531,42 +531,57 @@ PodcastSettings - - Posts - 内容 + + Settings + 设置 + + + + URL + + + + + Link + + + + + %1 Posts + - + Move new post to 移动新内容到 - + Inbox 收件箱 - + Top of Playlist 播放列表顶部 - + Bottom of Playlist 播放列表底部 - + Archive 存档 - + Automatic post limit 自动限制内容 - + Audio playrate 音频播放率 diff --git a/translations/harbour-podqast.ts b/translations/harbour-podqast.ts index fcc0f0a..0d4bec2 100644 --- a/translations/harbour-podqast.ts +++ b/translations/harbour-podqast.ts @@ -531,42 +531,57 @@ PodcastSettings - - Posts - Posts + + Settings + Settings + + + + URL + + + + + Link + + + + + %1 Posts + - + Move new post to Move new post to - + Inbox Inbox - + Top of Playlist Top of Playlist - + Bottom of Playlist Bottom of Playlist - + Archive Archive - + Automatic post limit Automatic post limit - + Audio playrate Audio playrate -- GitLab From c1a998c1333d5960febea8d8138f6c7b4b2d9c61 Mon Sep 17 00:00:00 2001 From: Thilo Kogge Date: Thu, 12 Aug 2021 08:04:27 +0200 Subject: [PATCH 11/20] showing url and more on settings page --- qml/pages/PodcastSettings.qml | 70 +++++++++++++++++++++++++++++++---- 1 file changed, 62 insertions(+), 8 deletions(-) diff --git a/qml/pages/PodcastSettings.qml b/qml/pages/PodcastSettings.qml index 8c2a047..adba58b 100644 --- a/qml/pages/PodcastSettings.qml +++ b/qml/pages/PodcastSettings.qml @@ -4,10 +4,22 @@ import Sailfish.Silica 1.0 Dialog { property var url property var podtitle + property var feedinfo + + DialogHeader { + id: header + title: qsTr("Settings") + } SilicaFlickable { - anchors.fill: parent - contentWidth: parent.width + anchors { + top: header.bottom + left: parent.left + right: parent.right + bottom: parent.bottom + margins: Theme.paddingMedium + } + contentWidth: parent.width - 2 * Theme.paddingMedium VerticalScrollDecorator {} @@ -15,13 +27,54 @@ Dialog { id: downloadConf width: parent.width - DialogHeader { - title: podtitle + Label { + text: podtitle + font.pixelSize: Theme.fontSizeLarge + color: Theme.highlightColor + wrapMode: Text.WordWrap + width: parent.width + } + + Label { + text: qsTr("%1 Posts").arg(feedinfo["episodecount"]) + font.pixelSize: Theme.fontSizeMedium + color: Theme.highlightColor + } + + Label { + text: qsTr("URL") + font.pixelSize: Theme.fontSizeMedium + color: Theme.highlightColor + } + + LinkedLabel { + plainText: url + wrapMode: Text.WrapAnywhere + width: parent.width } + Label { - text: qsTr("Posts") + text: qsTr("Link") font.pixelSize: Theme.fontSizeMedium color: Theme.highlightColor + visible: linklabel.visible + } + + LinkedLabel { + id: linklabel + plainText: feedinfo["link"] + wrapMode: Text.WrapAnywhere + visible: plainText.length > 0 + width: parent.width + } + Item { + height: Theme.itemSizeLarge + width: parent.width + Separator { + anchors.centerIn: parent + color: Theme.highlightColor + width: parent.width + } } ComboBox { @@ -75,6 +128,7 @@ Dialog { onOpened: { console.log("Title: " + podtitle) feedparserhandler.getPodcastParams(url) + feedparserhandler.getPodcast(url) } onAccepted: { @@ -89,7 +143,6 @@ Dialog { Connections { target: feedparserhandler onPodcastParams: { - console.log("podcast moveto: " + pcdata.move) if (pcdata.move !== -1) { moveTo.currentIndex = pcdata.move } else { @@ -106,8 +159,9 @@ Dialog { } else { playrate.value = globalPlayrateConf.value } - - console.log("moveTo:" + moveTo.currentIndex) + } + onFeedInfo: { + feedinfo = pcdata } } } -- GitLab From 8a796fb6823a007829aba1d5b9111df39df2da0c Mon Sep 17 00:00:00 2001 From: Thilo Kogge Date: Thu, 12 Aug 2021 09:10:32 +0200 Subject: [PATCH 12/20] fixes #66 sorting feedentries by published date when iterating --- python/podcast/feedutils.py | 21 ++--- test/test_podcast.py | 44 +++++++--- test/testdata/feed_entries_not_ordered.xml | 70 ++++++++++++++++ test/testdata/feed_entries_not_ordered2.xml | 93 +++++++++++++++++++++ translations/harbour-podqast-de.ts | 8 +- translations/harbour-podqast-es.ts | 8 +- translations/harbour-podqast-fr.ts | 8 +- translations/harbour-podqast-sv.ts | 8 +- translations/harbour-podqast-zh_CN.ts | 8 +- translations/harbour-podqast.ts | 8 +- 10 files changed, 230 insertions(+), 46 deletions(-) create mode 100644 test/testdata/feed_entries_not_ordered.xml create mode 100644 test/testdata/feed_entries_not_ordered2.xml diff --git a/python/podcast/feedutils.py b/python/podcast/feedutils.py index 599a122..8962442 100644 --- a/python/podcast/feedutils.py +++ b/python/podcast/feedutils.py @@ -1,6 +1,6 @@ import time import logging -from typing import Iterator +from typing import Iterator, Callable import feedparser from feedparser import FeedParserDict @@ -48,18 +48,19 @@ def fetch_feed(published, url) -> FeedParserDict: return feed -def iterate_feed_entries(feed) -> Iterator: +def iterate_feed_entries(feed, should_fetch_next_page: Callable[[], bool] = lambda: True) -> Iterator: while True: - for entry in feed.entries: + for entry in sorted(feed.entries, key=lambda e: e.published_parsed, reverse=True): yield entry - next_page_href = get_next_page(feed) - if next_page_href != None: - logger.info("Found next page: %s", next_page_href) - feed = fetch_feed(0, next_page_href) - if feed is None: + if should_fetch_next_page(): + next_page_href = get_next_page(feed) + if next_page_href != None: + logger.info("Found next page: %s", next_page_href) + feed = fetch_feed(0, next_page_href) + if feed is None: + break + else: break - else: - break class FeedFetchingError(BaseException): diff --git a/test/test_podcast.py b/test/test_podcast.py index a7866e4..3eda9cb 100644 --- a/test/test_podcast.py +++ b/test/test_podcast.py @@ -47,7 +47,6 @@ def test_feed_entry(): 'https://elroy.twit.tv/sites/default/files/styles/twit_album_art_2048x2048/public/images/shows/security_now/album_art/audio/sn_albumart_mask.jpg?itok=scC8c-TL', body=image) - seen_postids = {} pclist = PodcastList() @@ -190,8 +189,9 @@ def test_feed_no_changes(): assert invoked == 2 -def test_feed_refresh(refreshable_podcast): - p, episodes = refreshable_podcast +@pytest.mark.parametrize("refreshable_podcast_fixture", ["testdata/fakefeed"], indirect=True) +def test_feed_refresh(refreshable_podcast_fixture): + p, episodes = refreshable_podcast_fixture assert len(episodes) == 1 new_episodes = list(p.refresh(0)) assert len(new_episodes) == 1 @@ -200,24 +200,30 @@ def test_feed_refresh(refreshable_podcast): 'Hello, I am a fake episode for testing!!öäü'] -def test_podcastlist_refresh(refreshable_podcast): - p, episodes = refreshable_podcast +@pytest.mark.parametrize("refreshable_podcast_fixture", ["testdata/fakefeed"], indirect=True) +def test_podcastlist_refresh(refreshable_podcast_fixture): + p, episodes = refreshable_podcast_fixture plist = PodcastList() assert ilen(p.get_entries()) == 1 assert plist.get_podcast_count() == 1 assert list(plist.refresh(0)) == [(2, p.title, 'Hello, I am a new fake episode for testing!!öäü', 0)] -@pytest.fixture() -def refreshable_podcast() -> Tuple[Podcast, List[Podpost]]: +@pytest.fixture +def refreshable_podcast_fixture(request) -> Tuple[Podcast, List[Podpost]]: + """ + A pytest fixture to setup a feed that uses a different file when refreshed. + @param request: the first parameter should be the filename without xml. Thus loads .xml and then 2.xml + """ httpretty.enable() invoked = 0 + filename = request.param def request_callback(request, uri, response_headers): nonlocal invoked invoked += 1 logger.info("Returning normal response file") - testdata = read_testdata('testdata/fakefeed.xml') if invoked == 1 else read_testdata('testdata/fakefeed2.xml') + testdata = read_testdata(filename + ".xml") if invoked == 1 else read_testdata(filename + "2.xml") return [200, response_headers, testdata] feed_url = 'http://fakefeed.com/feed' @@ -226,6 +232,7 @@ def refreshable_podcast() -> Tuple[Podcast, List[Podpost]]: yield Podcast.create_from_url(feed_url) assert invoked > 0 + @httpretty.activate def test_pagination(): url_f = "https://fakefeed.com/page" @@ -235,20 +242,22 @@ def test_pagination(): assert 3 == podcast.count_episodes() assert 3 == len(episodes) + @httpretty.activate def test_pagination_stops(): url_f1 = "https://fakefeed.com/" url_f2 = "https://fakefeed.com/page2" invoked = 0 + def request_callback(request, uri, response_headers): nonlocal invoked invoked += 1 logger.info("Returning normal response file") - testdata = read_testdata('testdata/fakefeed.xml') if invoked == 1 else read_testdata('testdata/pagedfakefeed.xml') + testdata = read_testdata('testdata/fakefeed.xml') if invoked == 1 else read_testdata( + 'testdata/pagedfakefeed.xml') return [200, response_headers, testdata] - HTTPretty.register_uri(HTTPretty.GET, url_f1, body=request_callback, adding_headers=xml_headers) HTTPretty.register_uri(HTTPretty.GET, url_f2, body=read_testdata('testdata/fakefeed.xml'), @@ -261,6 +270,7 @@ def test_pagination_stops(): assert 2 == podcast.count_episodes() assert 1 == len(episodes) + @httpretty.activate def test_pagination(): url_f = "https://fakefeed.com/page" @@ -270,7 +280,7 @@ def test_pagination(): assert 2 == podcast.count_episodes() Podpost.delete_by_id(episodes[1].id) assert 1 == podcast.count_episodes() - list(podcast.refresh(0,0,True)) + list(podcast.refresh(0, 0, True)) assert 2 == podcast.count_episodes() @@ -285,4 +295,14 @@ def test_new_feed_url(): podcast, episodes = Podcast.create_from_url(url_f) assert podcast.url == "https://newhoster.com/feed/ogg" assert 1 == ilen(get_log_messages()) - assert list(get_log_messages())[0].messagetype == LogType.FeedRedirect.name \ No newline at end of file + assert list(get_log_messages())[0].messagetype == LogType.FeedRedirect.name + + +@pytest.mark.parametrize("refreshable_podcast_fixture", ["testdata/feed_entries_not_ordered"], indirect=True) +def test_podcastlist_refresh(refreshable_podcast_fixture): + p, episodes = refreshable_podcast_fixture + plist = PodcastList() + assert ilen(p.get_entries()) == 1 + assert plist.get_podcast_count() == 1 + list(plist.refresh(0)) + assert ilen(p.get_entries()) == 2 diff --git a/test/testdata/feed_entries_not_ordered.xml b/test/testdata/feed_entries_not_ordered.xml new file mode 100644 index 0000000..7e1f510 --- /dev/null +++ b/test/testdata/feed_entries_not_ordered.xml @@ -0,0 +1,70 @@ + + + + fakefeed + http://fakefeed.com + Tue, 02 Mar 2021 24:20:08 -0000 + Tue, 02 Mar 2021 23:20:11 -0000 + 60 + en-us + No Copyright © 2021 Podqast. No rights reserved. + no, none + + + + Richard + Test + thigg + http://blogs.law.harvard.edu/tech/rss + + https://example.com/image.png + imagetitle + imagelink.com + 1337 + 1337 + + + thigg + episodic + + + + + true + + nomail@example.com + bunny nunny + + subtitle + summary + + No Copyright © 2021 podqast. No Rights reserved. + + Arts + + prx_96_3554d3a0-e636-445f-b379-f1337deadbeef + Hello, I am a fake episode for testing!!öäü + Tue, 03 Mar 2021 23:20:08 -0000 + https://fakefeed.com/episode/1 + + + + I am a fake itunes subtitle + true + full + 1 + 13:37 + + + thigg + + + + + + + + + \ No newline at end of file diff --git a/test/testdata/feed_entries_not_ordered2.xml b/test/testdata/feed_entries_not_ordered2.xml new file mode 100644 index 0000000..f8ae608 --- /dev/null +++ b/test/testdata/feed_entries_not_ordered2.xml @@ -0,0 +1,93 @@ + + + + fakefeed + http://fakefeed.com + Tue, 04 Mar 2021 24:20:08 -0000 + Tue, 04 Mar 2021 23:20:11 -0000 + 60 + en-us + No Copyright © 2021 Podqast. No rights reserved. + no, none + + + + Richard + Test + thigg + http://blogs.law.harvard.edu/tech/rss + + https://example.com/image.png + imagetitle + imagelink.com + 1337 + 1337 + + + thigg + episodic + + + + + true + + nomail@example.com + bunny nunny + + subtitle + summary + + No Copyright © 2021 podqast. No Rights reserved. + + Arts + + prx_96_3554d3a0-e636-445f-b379-f1337deadbeef + Hello, I am a fake episode for testing!!öäü + Tue, 03 Mar 2021 23:20:08 -0000 + https://fakefeed.com/episode/1 + + + + I am a fake itunes subtitle + true + full + 1 + 13:37 + + + thigg + + + + + + + + + prx_96_3554d3a0-e636-445f-b379-f1338deadbeef + Hello, I am the second fakefeed episode + Tue, 04 Mar 2021 23:20:08 -0000 + https://fakefeed.com/episode/1 + + + + I am a fake itunes subtitle + true + full + 1 + 13:37 + + + thigg + + + + + + + + + \ No newline at end of file diff --git a/translations/harbour-podqast-de.ts b/translations/harbour-podqast-de.ts index 86a3361..ea45830 100644 --- a/translations/harbour-podqast-de.ts +++ b/translations/harbour-podqast-de.ts @@ -388,12 +388,12 @@ Verlauf - + Rendering Zeichne - + Collecting Posts Sammle Beiträge @@ -411,12 +411,12 @@ Lege alle Beiträge ins Archiv - + No new posts Keine neuen Beiträge - + Pull down to Discover new podcasts, get posts from Library, or play the Playlist Menü um neue Podcasts zu Entdecken, hole Beiträge von der Bibliothek oder spiele die Playlist diff --git a/translations/harbour-podqast-es.ts b/translations/harbour-podqast-es.ts index 19356c8..e7b0be7 100644 --- a/translations/harbour-podqast-es.ts +++ b/translations/harbour-podqast-es.ts @@ -388,12 +388,12 @@ Historial - + Rendering Generando - + Collecting Posts Recopilando publicaciones @@ -411,12 +411,12 @@ Mover todas las publicaciones al almacén - + No new posts No hay nuevas publicaciones - + Pull down to Discover new podcasts, get posts from Library, or play the Playlist Desliza hacia abajo para encontrar nuevos podcasts, ver publicaciones de la biblioteca, o reproducir la lista de reproducción diff --git a/translations/harbour-podqast-fr.ts b/translations/harbour-podqast-fr.ts index 427fd91..ff5cb3e 100644 --- a/translations/harbour-podqast-fr.ts +++ b/translations/harbour-podqast-fr.ts @@ -389,12 +389,12 @@ Historique - + Rendering Chargement - + Collecting Posts Récupération des Articles @@ -412,12 +412,12 @@ Transfert des courriers aux archives - + No new posts Aucun nouveau courrier - + Pull down to Discover new podcasts, get posts from Library, or play the Playlist Glisser vers le bas pour Découvrir de nouveaux podcasts, obtenir de nouveaux courriers de la Bibliothèque, ou écouter la Playlist diff --git a/translations/harbour-podqast-sv.ts b/translations/harbour-podqast-sv.ts index c275354..3411304 100644 --- a/translations/harbour-podqast-sv.ts +++ b/translations/harbour-podqast-sv.ts @@ -388,12 +388,12 @@ Historik - + Rendering Rendering - + Collecting Posts Hämtar poster @@ -411,12 +411,12 @@ Flyttar alla poster till arkiv - + No new posts Inga nya poster - + Pull down to Discover new podcasts, get posts from Library, or play the Playlist Dra neråt för att upptäcka nya poddar, hämta poster från biblioteket eller spela upp spelningslistan. diff --git a/translations/harbour-podqast-zh_CN.ts b/translations/harbour-podqast-zh_CN.ts index d34b112..87dc12d 100644 --- a/translations/harbour-podqast-zh_CN.ts +++ b/translations/harbour-podqast-zh_CN.ts @@ -388,12 +388,12 @@ 历史 - + Rendering 生成 - + Collecting Posts 收藏内容 @@ -411,12 +411,12 @@ 移动所有内容到存档 - + No new posts 无新内容 - + Pull down to Discover new podcasts, get posts from Library, or play the Playlist 下拉以找到新播客、从库获取内容或播放播放列表中的内容 diff --git a/translations/harbour-podqast.ts b/translations/harbour-podqast.ts index 0d4bec2..6433617 100644 --- a/translations/harbour-podqast.ts +++ b/translations/harbour-podqast.ts @@ -388,12 +388,12 @@ History - + Rendering Rendering - + Collecting Posts Collecting Posts @@ -411,12 +411,12 @@ Moving all posts to archive - + No new posts No new posts - + Pull down to Discover new podcasts, get posts from Library, or play the Playlist Pull down to Discover new podcasts, get posts from Library, or play the Playlist -- GitLab From 98679e0f41ab24f4ce5ff5b4fecb1d291678ec19 Mon Sep 17 00:00:00 2001 From: Thilo Kogge Date: Thu, 12 Aug 2021 09:40:19 +0200 Subject: [PATCH 13/20] removed obsolete calls in inbox --- qml/components/FeedParser.py | 1 - qml/components/InboxHandler.py | 6 ------ qml/components/InboxHandlerPython.qml | 3 --- qml/pages/Inbox.qml | 2 -- translations/harbour-podqast-de.ts | 16 ++++++++-------- translations/harbour-podqast-es.ts | 16 ++++++++-------- translations/harbour-podqast-fr.ts | 16 ++++++++-------- translations/harbour-podqast-sv.ts | 16 ++++++++-------- translations/harbour-podqast-zh_CN.ts | 16 ++++++++-------- translations/harbour-podqast.ts | 16 ++++++++-------- 10 files changed, 48 insertions(+), 60 deletions(-) diff --git a/qml/components/FeedParser.py b/qml/components/FeedParser.py index 14f4dc4..8c58769 100644 --- a/qml/components/FeedParser.py +++ b/qml/components/FeedParser.py @@ -158,7 +158,6 @@ class FeedParser: except Exception as e: persistent_log.persist_log(LogType.Exception, msg="Could not move episode to according list", exception=e) - pyotherside.send("refreshFinished") async def get_podcast_params(self, url): diff --git a/qml/components/InboxHandler.py b/qml/components/InboxHandler.py index ecb1d2b..b5de1c5 100644 --- a/qml/components/InboxHandler.py +++ b/qml/components/InboxHandler.py @@ -96,12 +96,6 @@ class InboxHandler: self.bgthread = threading.Thread(target=get_inbox_posts) self.bgthread.start() - def getinboxpoddata(self): - if self.bgthread.is_alive(): - return - self.bgthread = threading.Thread(target=get_inbox_pod_data) - self.bgthread.start() - def queuedownload(self, id): """ download audio post diff --git a/qml/components/InboxHandlerPython.qml b/qml/components/InboxHandlerPython.qml index 8fc130a..448c118 100644 --- a/qml/components/InboxHandlerPython.qml +++ b/qml/components/InboxHandlerPython.qml @@ -44,7 +44,4 @@ Python { function moveAllArchive(podpost) { call("InboxHandler.move_all_archive", function () {}) } - function getInboxPodData() { - call("InboxHandler.get_inbox_pod_data", function () {}) - } } diff --git a/qml/pages/Inbox.qml b/qml/pages/Inbox.qml index e1c266d..2d929f4 100644 --- a/qml/pages/Inbox.qml +++ b/qml/pages/Inbox.qml @@ -34,7 +34,6 @@ Page { } onGetInboxPosts: { inboxhandler.getInboxEntries(podqast.ifilter) - inboxhandler.getInboxPodData() } } @@ -42,7 +41,6 @@ Page { target: feedparserhandler onRefreshFinished: { inboxhandler.getInboxEntries(podqast.ifilter) - inboxhandler.getInboxPodData() } } diff --git a/translations/harbour-podqast-de.ts b/translations/harbour-podqast-de.ts index ea45830..5f53e15 100644 --- a/translations/harbour-podqast-de.ts +++ b/translations/harbour-podqast-de.ts @@ -360,22 +360,22 @@ FeedParserPython - + Auto-Post-Limit reached - + for %1 - + Auto-Post-Limit reached for %1 - + Error Fehler @@ -401,22 +401,22 @@ Inbox - + Inbox Eingang - + Moving all posts to archive Lege alle Beiträge ins Archiv - + No new posts Keine neuen Beiträge - + Pull down to Discover new podcasts, get posts from Library, or play the Playlist Menü um neue Podcasts zu Entdecken, hole Beiträge von der Bibliothek oder spiele die Playlist diff --git a/translations/harbour-podqast-es.ts b/translations/harbour-podqast-es.ts index e7b0be7..55d87a9 100644 --- a/translations/harbour-podqast-es.ts +++ b/translations/harbour-podqast-es.ts @@ -360,22 +360,22 @@ FeedParserPython - + Auto-Post-Limit reached - + for %1 - + Auto-Post-Limit reached for %1 - + Error Error @@ -401,22 +401,22 @@ Inbox - + Inbox Entradas - + Moving all posts to archive Mover todas las publicaciones al almacén - + No new posts No hay nuevas publicaciones - + Pull down to Discover new podcasts, get posts from Library, or play the Playlist Desliza hacia abajo para encontrar nuevos podcasts, ver publicaciones de la biblioteca, o reproducir la lista de reproducción diff --git a/translations/harbour-podqast-fr.ts b/translations/harbour-podqast-fr.ts index ff5cb3e..57bb277 100644 --- a/translations/harbour-podqast-fr.ts +++ b/translations/harbour-podqast-fr.ts @@ -361,22 +361,22 @@ FeedParserPython - + Auto-Post-Limit reached - + for %1 - + Auto-Post-Limit reached for %1 - + Error Erreur @@ -402,22 +402,22 @@ Inbox - + Inbox Boîte aux lettres - + Moving all posts to archive Transfert des courriers aux archives - + No new posts Aucun nouveau courrier - + Pull down to Discover new podcasts, get posts from Library, or play the Playlist Glisser vers le bas pour Découvrir de nouveaux podcasts, obtenir de nouveaux courriers de la Bibliothèque, ou écouter la Playlist diff --git a/translations/harbour-podqast-sv.ts b/translations/harbour-podqast-sv.ts index 3411304..15c418c 100644 --- a/translations/harbour-podqast-sv.ts +++ b/translations/harbour-podqast-sv.ts @@ -360,22 +360,22 @@ FeedParserPython - + Auto-Post-Limit reached - + for %1 - + Auto-Post-Limit reached for %1 - + Error Fel @@ -401,22 +401,22 @@ Inbox - + Inbox Inkorg - + Moving all posts to archive Flyttar alla poster till arkiv - + No new posts Inga nya poster - + Pull down to Discover new podcasts, get posts from Library, or play the Playlist Dra neråt för att upptäcka nya poddar, hämta poster från biblioteket eller spela upp spelningslistan. diff --git a/translations/harbour-podqast-zh_CN.ts b/translations/harbour-podqast-zh_CN.ts index 87dc12d..642d180 100644 --- a/translations/harbour-podqast-zh_CN.ts +++ b/translations/harbour-podqast-zh_CN.ts @@ -360,22 +360,22 @@ FeedParserPython - + Auto-Post-Limit reached - + for %1 - + Auto-Post-Limit reached for %1 - + Error 错误 @@ -401,22 +401,22 @@ Inbox - + Inbox 收件箱 - + Moving all posts to archive 移动所有内容到存档 - + No new posts 无新内容 - + Pull down to Discover new podcasts, get posts from Library, or play the Playlist 下拉以找到新播客、从库获取内容或播放播放列表中的内容 diff --git a/translations/harbour-podqast.ts b/translations/harbour-podqast.ts index 6433617..b0f4fc2 100644 --- a/translations/harbour-podqast.ts +++ b/translations/harbour-podqast.ts @@ -360,22 +360,22 @@ FeedParserPython - + Auto-Post-Limit reached - + for %1 - + Auto-Post-Limit reached for %1 - + Error Error @@ -401,22 +401,22 @@ Inbox - + Inbox Inbox - + Moving all posts to archive Moving all posts to archive - + No new posts No new posts - + Pull down to Discover new podcasts, get posts from Library, or play the Playlist Pull down to Discover new podcasts, get posts from Library, or play the Playlist -- GitLab From 91994f968fba5501efe019509c502fc64c19ec12 Mon Sep 17 00:00:00 2001 From: Thilo Kogge Date: Fri, 3 Sep 2021 08:20:48 +0200 Subject: [PATCH 14/20] added autoplay option to settings --- qml/pages/Settings.qml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/qml/pages/Settings.qml b/qml/pages/Settings.qml index 4200900..555b361 100644 --- a/qml/pages/Settings.qml +++ b/qml/pages/Settings.qml @@ -148,6 +148,15 @@ Dialog { valueText: value + qsTr("min") stepSize: 1 } + TextSwitch { + anchors { + left: parent.left + right: parent.right + margins: Theme.paddingMedium + } + id: autoPlayQueue + text: qsTr("Auto-play next episode in queue") + } Label { anchors { @@ -353,12 +362,14 @@ Dialog { experimentalFlag.checked = experimentalConf.value sleepTimer.value = sleepTimerConf.value listenedThreshold.value = markListenedEndThresholdConf.value + autoPlayQueue.checked = autoPlayNextInQueue.value // downLimit.value = downLimitConf.value // refreshTime = refreshTimeConf.value } onAccepted: { // useGpodderConf.value = useGpodder.checked dataTrackableConf.value = dataTrackable.checked + autoPlayNextInQueue.value = autoPlayQueue.checked feedparserhandler.nomedia(dataTrackableConf.value) -- GitLab From 7129963b8703d05758dc4b427ad9ccdf60d6bce8 Mon Sep 17 00:00:00 2001 From: Thilo Kogge Date: Fri, 3 Sep 2021 08:21:29 +0200 Subject: [PATCH 15/20] removed obsolete code --- qml/components/ArchiveHandlerPython.qml | 6 ------ qml/components/ExternalHandlerPython.qml | 2 -- 2 files changed, 8 deletions(-) diff --git a/qml/components/ArchiveHandlerPython.qml b/qml/components/ArchiveHandlerPython.qml index b2d5f43..94dcf9e 100644 --- a/qml/components/ArchiveHandlerPython.qml +++ b/qml/components/ArchiveHandlerPython.qml @@ -6,12 +6,10 @@ Python { id: archivehandler signal historyData(int offset, var data) - signal archivePodList(var data) Component.onCompleted: { addImportPath(Qt.resolvedUrl("./python")) setHandler("historyData", historyData) - setHandler("archivePodList", archivePodList) addImportPath(Qt.resolvedUrl('.')) importModule('ArchiveHandler', function () { @@ -32,8 +30,4 @@ Python { function () {}) } } - - function getArchivePodData() { - call("ArchiveHandler.archivehandler.getarchivepoddata", function () {}) - } } diff --git a/qml/components/ExternalHandlerPython.qml b/qml/components/ExternalHandlerPython.qml index eb73ae7..54161d9 100644 --- a/qml/components/ExternalHandlerPython.qml +++ b/qml/components/ExternalHandlerPython.qml @@ -23,13 +23,11 @@ Python { } function getExternalEntries() { - // call("ExternalHandler.externalhandler.getexternalposts", function() {}); call("ExternalHandler.get_external_posts", function () {}) } function waitNew() { call("ExternalHandler.externalhandler.waitnew", function () {}) - // call("ExternalHandler.wait_new", function() {}); } function checkNew() { call("ExternalHandler.check_new", function () {}) -- GitLab From c13693f31d318d5e8b3fa6f0a62bc7b707487ebb Mon Sep 17 00:00:00 2001 From: Thilo Kogge Date: Fri, 3 Sep 2021 08:23:41 +0200 Subject: [PATCH 16/20] refactored mediaplayer into PodqastAudioPlayer.qml --- python/podcast/podpost.py | 3 +- qml/components/PlayDockedPanel.qml | 4 +- qml/components/PlayerHandler.qml | 41 ++++++++++----- qml/components/PodqastAudioPlayer.qml | 55 +++++++++++++++++++ qml/components/QueueHandlerPython.qml | 2 +- qml/cover/CoverPage.qml | 9 ++-- qml/harbour-podqast.qml | 76 ++------------------------- qml/pages/Chapters.qml | 40 +++++++------- qml/pages/Player.qml | 14 ++--- 9 files changed, 125 insertions(+), 119 deletions(-) create mode 100644 qml/components/PodqastAudioPlayer.qml diff --git a/python/podcast/podpost.py b/python/podcast/podpost.py index 62a196f..24263db 100644 --- a/python/podcast/podpost.py +++ b/python/podcast/podpost.py @@ -233,7 +233,8 @@ class Podpost(BaseModel): return { "id": self.id, "title": self.title, - "url": self.podcast.url, + "podcast_url": self.podcast.url, + "audio_url": self.href, "logo_url": image, "link": self.link, "description": self.plainpart, diff --git a/qml/components/PlayDockedPanel.qml b/qml/components/PlayDockedPanel.qml index 55b2bd4..e336570 100644 --- a/qml/components/PlayDockedPanel.qml +++ b/qml/components/PlayDockedPanel.qml @@ -26,7 +26,7 @@ DockedPanel { IconButton { icon.source: "image://theme/icon-m-previous" onClicked: { - podqast.fast_backward() + playerHandler.fast_backward() } } IconButton { @@ -38,7 +38,7 @@ DockedPanel { IconButton { icon.source: "image://theme/icon-m-next" onClicked: { - podqast.fast_forward() + playerHandler.fast_forward() } } IconButton { diff --git a/qml/components/PlayerHandler.qml b/qml/components/PlayerHandler.qml index 16b4e5f..9197f4d 100644 --- a/qml/components/PlayerHandler.qml +++ b/qml/components/PlayerHandler.qml @@ -4,6 +4,9 @@ import io.thp.pyotherside 1.4 Python { id: playerHandler + property var position: mediaplayer.position + property var duration: mediaplayer.duration + property bool isPlaying: false property string playicon: "../../images/podcast.png" property string playtext: "" @@ -42,16 +45,14 @@ Python { } onPlayrateChanged: { - seekPos = mediaplayer.position mediaplayer.playbackRate = playrate - mediaplayer.seek(seekPos) } onPlaying: { - console.log("Playing audio_url: " + audio_url) - console.log("Seekable: " + mediaplayer.seekable) + mediaplayer.playbackRate = playrate + console.info("Playing audio_url: " + audio_url + " @ rate " + playrate) + if (audio_url != "") { - playrate = globalPlayrateConf.value mediaplayer.source = audio_url mediaplayer.seek(position - 15 * 1000) mediaplayer.play() @@ -61,10 +62,8 @@ Python { mediaplayer.play() } - mediaplayer.playbackRate = playrate - - console.log("Duration: ", mediaplayer.duration) - console.log("Position: ", mediaplayer.position) + console.debug("Duration: ", mediaplayer.duration + " Position: ", + mediaplayer.position) isPlaying = true queuehandler.updatePlayingPosition() externalhandler.getAudioData(audio_url) @@ -86,13 +85,12 @@ Python { } function setEpisode(data, chapterlist) { - console.log("setting episode: " + data) + console.log("setting episode: " + data.title + " url:" + data.audio_url) firstid = data.id firsttitle = data.title chapters = chapterlist playicon = data.logo_url playtext = data.title - mediaplayer.source = data.audio_url } function getaktchapter() { @@ -156,7 +154,7 @@ Python { } function playpause() { - console.debug("Toggle play: " + isPlaying) + console.info("Toggle play: " + isPlaying) if (!isPlaying) { play() } else { @@ -172,6 +170,7 @@ Python { function seek(position) { call("QueueHandler.instance.queue_seek", [position], function () {}) + mediaplayer.seek(position) } function setDuration() { @@ -179,4 +178,22 @@ Python { call("QueueHandler.instance.set_duration", [mediaplayer.duration / 1000], function () {}) } + + function fast_backward() { + var posi = mediaplayer.position + posi = posi - 15 * 1000 + if (posi < 0) { + posi = 0 + } + seek(posi) + } + + function fast_forward() { + var posi = mediaplayer.position + posi = posi + 30 * 1000 + if (posi > mediaplayer.duration) { + posi = mediaplayer.duration + } + seek(posi) + } } diff --git a/qml/components/PodqastAudioPlayer.qml b/qml/components/PodqastAudioPlayer.qml new file mode 100644 index 0000000..116198b --- /dev/null +++ b/qml/components/PodqastAudioPlayer.qml @@ -0,0 +1,55 @@ +import QtQuick 2.0 +import io.thp.pyotherside 1.4 +import QtMultimedia 5.0 + +MediaPlayer { + id: mediaplayer + + readonly property bool isPlaying: playbackState === Audio.PlayingState + + onSourceChanged: console.log( + "changing mediaplayer src to: " + mediaplayer.source) + + onPlaybackRateChanged: { + if (isPlaying) + seek(position - 0.01) + } + + onPaused: { + console.log("onPaused status: " + mediaplayer.status) + if (mediaplayer.status == 6) { + playerHandler.pause() + } + } + onSeekableChanged: { + console.log("onSeekableChanged status: " + mediaplayer.status + + " seekPos: " + playerHandler.seekPos + + " dostartafterSeek:" + playerHandler.doStartAfterSeek) + playerHandler.setDuration() + if (playerHandler.seekPos > 0) { + mediaplayer.seek(playerHandler.seekPos) + playerHandler.seekPos = 0.0 + } + playerHandler.getaktchapter() + if (playerHandler.doStartAfterSeek) { + mediaplayer.play() + playerHandler.doStartAfterSeek = false + } + } + onStatusChanged: { + switch (mediaplayer.status) { + case MediaPlayer.EndOfMedia: + console.log("End of media " + mediaplayer.source) + queuehandler.queueTopToArchive(autoPlayNextInQueue.value) + break + case MediaPlayer.StoppedState: + console.log("Stopped Mediaplayer " + mediaplayer.source) + break + case MediaPlayer.PlayingState: + console.log("Playing " + mediaplayer.source) + break + case MediaPlayer.PausedState: + console.log("Paused " + mediaplayer.source) + } + } +} diff --git a/qml/components/QueueHandlerPython.qml b/qml/components/QueueHandlerPython.qml index 002a89f..62de938 100644 --- a/qml/components/QueueHandlerPython.qml +++ b/qml/components/QueueHandlerPython.qml @@ -72,7 +72,7 @@ Python { call("QueueHandler.instance.get_first_entry", function () {}) } function updatePlayingPosition() { - call("QueueHandler.instance.update_position", [mediaplayer.position], + call("QueueHandler.instance.update_position", [playerHandler.position], function () {}) } function downloadAudio(podpost) { diff --git a/qml/cover/CoverPage.qml b/qml/cover/CoverPage.qml index 9d0e842..3ab6e4b 100644 --- a/qml/cover/CoverPage.qml +++ b/qml/cover/CoverPage.qml @@ -34,7 +34,8 @@ CoverBackground { Label { height: parent.height width: parent.width - text: playerHandler.chapters ? playerHandler.chapters.length + qsTr(" chapters") : "" + text: playerHandler.chapters ? playerHandler.chapters.length + qsTr( + " chapters") : "" font.pixelSize: Theme.fontSizeSmall padding: 10 } @@ -44,20 +45,20 @@ CoverBackground { anchors.top: chaptcount.bottom anchors.horizontalCenter: parent.horizontalCenter Label { - text: to_pos_str(mediaplayer.position / 1000) + text: to_pos_str(playerHandler.position / 1000) font.bold: true font.pixelSize: Theme.fontSizeLarge color: Theme.highlightColor anchors.horizontalCenter: parent.horizontalCenter } Label { - text: to_pos_str((mediaplayer.duration - mediaplayer.position) / 1000) + text: to_pos_str( + (playerHandler.duration - playerHandler.position) / 1000) font.bold: true font.pixelSize: Theme.fontSizeLarge color: Theme.highlightColor anchors.horizontalCenter: parent.horizontalCenter } - } CoverActionList { diff --git a/qml/harbour-podqast.qml b/qml/harbour-podqast.qml index 07b766d..4cdb4e8 100644 --- a/qml/harbour-podqast.qml +++ b/qml/harbour-podqast.qml @@ -397,49 +397,6 @@ ApplicationWindow { } } - Connections { - target: mediaplayer - - ignoreUnknownSignals: true - onPaused: { - console.log("onPaused status: " + mediaplayer.status) - if (mediaplayer.status == 6) { - playerHandler.pause() - } - } - onSeekableChanged: { - console.log("onSeekableChanged status: " + mediaplayer.status - + " seekPos: " + playerHandler.seekPos - + " dostartafterSeek:" + playerHandler.doStartAfterSeek) - playerHandler.setDuration() - if (playerHandler.seekPos > 0) { - mediaplayer.seek(playerHandler.seekPos) - playerHandler.seekPos = 0.0 - } - playerHandler.getaktchapter() - if (playerHandler.doStartAfterSeek) { - mediaplayer.play() - playerHandler.doStartAfterSeek = false - } - } - onStatusChanged: { - switch (mediaplayer.status) { - case MediaPlayer.EndOfMedia: - console.log("End of media " + mediaplayer.source) - queuehandler.queueTopToArchive(autoPlayNextInQueue.value) - break - case MediaPlayer.StoppedState: - console.log("Stopped Mediaplayer " + mediaplayer.source) - break - case MediaPlayer.PlayingState: - console.log("Playing " + mediaplayer.source) - break - case MediaPlayer.PausedState: - console.log("Paused " + mediaplayer.source) - } - } - } - Connections { target: externalhandler onAudioInfo: { @@ -479,6 +436,10 @@ ApplicationWindow { id: loghandler } + PodqastAudioPlayer { + id: mediaplayer + } + PlayerHandler { id: playerHandler onAudioNotExist: { @@ -495,38 +456,9 @@ ApplicationWindow { id: favoritehandler } - Item { - MediaPlayer { - id: mediaplayer - - onSourceChanged: console.log( - "changing mediaplayer src to: " + mediaplayer.source) - } - } - function to_pos_str(secs) { var date = new Date(null) date.setSeconds(secs) return date.toISOString().substr(11, 8) } - - function fast_backward() { - var posi = mediaplayer.position - posi = posi - 15 * 1000 - if (posi < 0) { - posi = 0 - } - playerHandler.seek(posi) - mediaplayer.seek(posi) - } - - function fast_forward() { - var posi = mediaplayer.position - posi = posi + 30 * 1000 - if (posi > mediaplayer.duration) { - posi = mediaplayer.duration - } - playerHandler.seek(posi) - mediaplayer.seek(posi) - } } diff --git a/qml/pages/Chapters.qml b/qml/pages/Chapters.qml index f737db1..0390e9b 100644 --- a/qml/pages/Chapters.qml +++ b/qml/pages/Chapters.qml @@ -9,12 +9,11 @@ Page { property var chapterid property bool currentlyPlayingEpisode: (episodeid === playerHandler.firstid - && playerHandler.isPlaying === true) + && playerHandler.isPlaying === true) // The effective value will be restricted by ApplicationWindow.allowedOrientations allowedOrientations: Orientation.All - Component.onCompleted: { chapterModel.clear() queuehandler.getEpisodeChapters(episodeid) @@ -25,7 +24,8 @@ Page { onEpisodeChapters: { for (var i = 0; i < chapters.length; i++) { - console.log("got chapter",i,chapters[i].selected,chapters[i].title) + console.log("got chapter", i, chapters[i].selected, + chapters[i].title) chapterModel.append(chapters[i]) } } @@ -46,7 +46,7 @@ Page { height: page.height header: PageHeader { - title: qsTr("Chapters") + title: qsTr("Chapters") } model: ListModel { @@ -65,10 +65,12 @@ Page { text: title description: msToTime(start_millis) checked: selected - busy: (currentlyPlayingEpisode && playerHandler.aktchapter === index) + busy: (currentlyPlayingEpisode + && playerHandler.aktchapter === index) onClicked: { - queuehandler.toggleChapter(episodeid, chapterid=index) - console.log(episodeid,playerHandler.firstid,playerHandler.isPlaying) + queuehandler.toggleChapter(episodeid, chapterid = index) + console.log(episodeid, playerHandler.firstid, + playerHandler.isPlaying) if (currentlyPlayingEpisode) { console.log("changed chapter selection of currently playing episode") queuehandler.sendFirstEpisodeChapters(episodeid) @@ -77,17 +79,17 @@ Page { function msToTime(s) { function pad(n, z) { - z = z || 2; - return ('00' + n).slice(-z); - } - var ms = s % 1000; - s = (s - ms) / 1000; - var secs = s % 60; - s = (s - secs) / 60; - var mins = s % 60; - var hrs = (s - mins) / 60; - - return pad(hrs) + ':' + pad(mins) + ':' + pad(secs); + z = z || 2 + return ('00' + n).slice(-z) + } + var ms = s % 1000 + s = (s - ms) / 1000 + var secs = s % 60 + s = (s - secs) / 60 + var mins = s % 60 + var hrs = (s - mins) / 60 + + return pad(hrs) + ':' + pad(mins) + ':' + pad(secs) } } @@ -98,9 +100,7 @@ Page { icon.source: "image://theme/icon-m-next" onClicked: { var millis = start_millis - console.log(millis) playerHandler.seek(millis) - mediaplayer.seek(millis) } } } diff --git a/qml/pages/Player.qml b/qml/pages/Player.qml index 03379ce..da01bce 100644 --- a/qml/pages/Player.qml +++ b/qml/pages/Player.qml @@ -143,7 +143,8 @@ Page { height: Theme.itemSizeExtraLarge IconButton { id: lastchapter - enabled: playerHandler.chapters.length > 0 + enabled: playerHandler.chapters !== undefined + && playerHandler.chapters.length > 0 anchors.left: parent.left icon.source: "image://theme/icon-m-previous" onClicked: { @@ -152,7 +153,6 @@ Page { playerHandler.aktchapter -= 1 playerHandler.playpos = playerHandler.chapters[playerHandler.aktchapter].start_millis playerHandler.seek(playerHandler.playpos) - mediaplayer.seek(playerHandler.playpos) chapterLabel.text = Number( playerHandler.aktchapter + 1) + ". " + playerHandler.chapters[playerHandler.aktchapter].title @@ -239,11 +239,11 @@ Page { rightMargin: 50 height: parent.height handleVisible: false - maximumValue: mediaplayer.duration + maximumValue: playerHandler.duration value: mediaplayer.position - valueText: to_pos_str(mediaplayer.position / 1000) + valueText: to_pos_str(playerHandler.position / 1000) onPressedChanged: { - mediaplayer.seek(playSlider.value) + playerHandler.seek(playSlider.value) } } } @@ -255,7 +255,7 @@ Page { IconButton { icon.source: "image://theme/icon-m-previous" onClicked: { - podqast.fast_backward() + playerHandler.fast_backward() } } IconButton { @@ -269,7 +269,7 @@ Page { icon.source: "image://theme/icon-m-next" onClicked: { - podqast.fast_forward() + playerHandler.fast_forward() } } } -- GitLab From cc061cd97a947a5d1f8ec16c1ca0b73de34d08c1 Mon Sep 17 00:00:00 2001 From: Thilo Kogge Date: Mon, 6 Sep 2021 17:21:36 +0200 Subject: [PATCH 17/20] Added a tutorial with hints to the app --- harbour-podqast.pro | 9 ++ qml/components/EpisodeImage.qml | 15 +-- qml/components/PostListItem.qml | 4 +- qml/components/hints/HintDockIcons.qml | 23 ++++ qml/components/hints/HintEpisodeImage.qml | 17 +++ qml/components/hints/MainPageUsageHint.qml | 61 +++++++++ qml/components/hints/PlayerHint.qml | 13 ++ qml/lib/hints/SingleTimeHint.qml | 20 +++ qml/lib/hints/SlideShowHint.qml | 85 ++++++++++++ qml/pages/Archive.qml | 74 +++++++---- qml/pages/Player.qml | 8 ++ translations/harbour-podqast-de.ts | 144 ++++++++++++++------- translations/harbour-podqast-es.ts | 144 ++++++++++++++------- translations/harbour-podqast-fr.ts | 144 ++++++++++++++------- translations/harbour-podqast-sv.ts | 144 ++++++++++++++------- translations/harbour-podqast-zh_CN.ts | 144 ++++++++++++++------- translations/harbour-podqast.ts | 144 ++++++++++++++------- 17 files changed, 891 insertions(+), 302 deletions(-) create mode 100644 qml/components/hints/HintDockIcons.qml create mode 100644 qml/components/hints/HintEpisodeImage.qml create mode 100644 qml/components/hints/MainPageUsageHint.qml create mode 100644 qml/components/hints/PlayerHint.qml create mode 100644 qml/lib/hints/SingleTimeHint.qml create mode 100644 qml/lib/hints/SlideShowHint.qml diff --git a/harbour-podqast.pro b/harbour-podqast.pro index c7679a5..89be3d7 100644 --- a/harbour-podqast.pro +++ b/harbour-podqast.pro @@ -20,13 +20,20 @@ DISTFILES += qml/podqast.qml \ qml/components/FyydDePython.qml \ qml/components/LogHandler.qml \ qml/components/MigrationHandler.qml \ + qml/components/PodqastAudioPlayer.qml \ + lib/hints/SlideShowHint.qml \ + qml/components/hints/HintDockIcons.qml \ + qml/components/hints/HintEpisodeImage.qml \ qml/components/timeutil.js \ qml/cover/CoverPage.qml \ qml/pages/DataMigration.qml \ qml/pages/FyydSearchPage.qml \ qml/pages/GpodderSearchPage.qml \ qml/pages/Log.qml \ + components/hints/MainPageUsageHint.qml \ + components/hints/PlayerHint.qml \ qml/pages/PodcastDirectorySearchPage.qml \ + lib/hints/PodqastSingleTimeHint.qml \ qml/pages/SubscribePodcast.qml \ rpm/podqast.changes.in \ rpm/podqast.changes.run.in \ @@ -87,6 +94,8 @@ DISTFILES += qml/podqast.qml \ SAILFISHAPP_ICONS = 86x86 108x108 128x128 172x172 +QML_IMPORT_PATH += "qml/componentspo" + # to disable building translations every time, comment out the # following CONFIG line CONFIG += sailfishapp_i18n diff --git a/qml/components/EpisodeImage.qml b/qml/components/EpisodeImage.qml index d804b5e..82d51e8 100644 --- a/qml/components/EpisodeImage.qml +++ b/qml/components/EpisodeImage.qml @@ -4,14 +4,13 @@ import Nemo.Thumbnailer 1.0 Thumbnail { id: listicon - property int percentage: dlperc + property int percentage anchors.leftMargin: Theme.paddingMedium - property bool showDlIcon: listicon.percentage > 0 - && listicon.percentage < 100 - property bool showIsAudio: isaudio - property bool showIsPlaying: id === playerHandler.firstid - property bool showIsListened: listened + property bool showDownladingState: percentage > 0 && percentage < 100 + property bool showIsAudio + property bool showIsPlaying + property bool showIsListened MouseArea { anchors.fill: parent @@ -21,16 +20,14 @@ Thumbnail { }) } } - anchors.left: parent.left width: Theme.iconSizeLarge height: Theme.iconSizeLarge sourceSize.width: Theme.iconSizeLarge sourceSize.height: Theme.iconSizeLarge - anchors.verticalCenter: parent.verticalCenter source: logo_url == "" ? "../../images/podcast.png" : logo_url Rectangle { id: dlstatus - visible: showDlIcon + visible: showDownladingState anchors.right: parent.right height: parent.height width: (100 - listicon.percentage) * parent.width / 100 diff --git a/qml/components/PostListItem.qml b/qml/components/PostListItem.qml index 7fe06f5..1b12e4a 100644 --- a/qml/components/PostListItem.qml +++ b/qml/components/PostListItem.qml @@ -52,10 +52,12 @@ ListItem { EpisodeImage { id: episodeImage - showDlIcon: episodeImage.percentage > 0 && episodeImage.percentage < 100 + anchors.left: left + anchors.verticalCenter: verticalCenter showIsAudio: isaudio showIsPlaying: postId === playerHandler.firstid showIsListened: listened + percentage: model.dlperc } Column { diff --git a/qml/components/hints/HintDockIcons.qml b/qml/components/hints/HintDockIcons.qml new file mode 100644 index 0000000..e4e3e7c --- /dev/null +++ b/qml/components/hints/HintDockIcons.qml @@ -0,0 +1,23 @@ +import QtQuick 2.0 +import Sailfish.Silica 1.0 + +Rectangle { + width: Theme.iconSizeExtraLarge + height: Theme.iconSizeExtraLarge + anchors.centerIn: centeredComponent + color: Theme.darkPrimaryColor + Icon { + property string streamIcon: "image://theme/icon-s-cloud-download" + property string sleepTimerIcon: "image://theme/icon-s-timer" + property string playrateIcon: "image://theme/icon-s-duration" + property var sources: [streamIcon, sleepTimerIcon, playrateIcon] + width: Theme.iconSizeExtraLarge + height: Theme.iconSizeExtraLarge + anchors.fill: centeredComponent + property int baseindex: 4 + property int relativeIndex: index - baseindex + source: sources[relativeIndex] + + onRelativeIndexChanged: console.log("relative index" + relativeIndex) + } +} diff --git a/qml/components/hints/HintEpisodeImage.qml b/qml/components/hints/HintEpisodeImage.qml new file mode 100644 index 0000000..78a6d88 --- /dev/null +++ b/qml/components/hints/HintEpisodeImage.qml @@ -0,0 +1,17 @@ +import QtQuick 2.0 +import Sailfish.Silica 1.0 +import "../" + +EpisodeImage { + property int baseindex: 2 + property int relativeIndex: index - baseindex + showDownladingState: false + showIsAudio: relativeIndex === 3 + showIsPlaying: relativeIndex === 1 + showIsListened: relativeIndex === 2 + percentage: relativeIndex === 0 ? 100 : 0 + anchors.fill: centeredComponent + property string logo_url: "../../images/podcast.png" + width: Theme.iconSizeExtraLarge + height: Theme.iconSizeExtraLarge +} diff --git a/qml/components/hints/MainPageUsageHint.qml b/qml/components/hints/MainPageUsageHint.qml new file mode 100644 index 0000000..d4d18d2 --- /dev/null +++ b/qml/components/hints/MainPageUsageHint.qml @@ -0,0 +1,61 @@ +import QtQuick 2.0 +import Sailfish.Silica 1.0 +import "../../lib/hints" +import "../" + +SlideShowHint { + + slides: [{ + "text": qsTr("You can see whats going on by looking into the log down here"), + "direction": TouchInteraction.Up, + "interactionMode": TouchInteraction.EdgeSwipe, + "loops": 3 + }, { + "text": qsTr("Podqast supports pagination of feeds. If you are missing old episodes of feeds, you can find a force refresh button in the settings."), + "direction": TouchInteraction.Up, + "interactionMode": TouchInteraction.EdgeSwipe, + "loops": 6 + }, { + "text": qsTr("On an episode image, this icon shows that it is downloaded..."), + "direction": 0, + "interactionMode": 0, + "interactionHidden": true, + "loops": 3, + "centeredComponent": "../../components/hints/HintEpisodeImage.qml" + }, { + "text": qsTr("...currently playing..."), + "direction": 0, + "interactionMode": 0, + "interactionHidden": true, + "loops": 2, + "centeredComponent": "../../components/hints/HintEpisodeImage.qml" + }, { + "text": qsTr("...already listened."), + "direction": 0, + "interactionMode": 0, + "interactionHidden": true, + "loops": 2, + "centeredComponent": "../../components/hints/HintEpisodeImage.qml" + }, { + "text": qsTr("In the dock below, this icon means the file currently played is streaming from the internet right now..."), + "direction": 0, + "interactionMode": 0, + "interactionHidden": true, + "loops": 4, + "centeredComponent": "../../components/hints/HintDockIcons.qml" + }, { + "text": qsTr("...the sleeptimer is active"), + "direction": 0, + "interactionMode": 0, + "interactionHidden": true, + "loops": 2, + "centeredComponent": "../../components/hints/HintDockIcons.qml" + }, { + "text": qsTr("...a playrate different from 1 is set."), + "direction": 0, + "interactionMode": 0, + "interactionHidden": true, + "loops": 2, + "centeredComponent": "../../components/hints/HintDockIcons.qml" + }] +} diff --git a/qml/components/hints/PlayerHint.qml b/qml/components/hints/PlayerHint.qml new file mode 100644 index 0000000..e81665c --- /dev/null +++ b/qml/components/hints/PlayerHint.qml @@ -0,0 +1,13 @@ +import QtQuick 2.0 +import Sailfish.Silica 1.0 +import "../../lib/hints" +import "../" + +SlideShowHint { + slides: [{ + "text": qsTr("Select, if the next item of the playlist should be played automatically"), + "direction": TouchInteraction.Down, + "interactionMode": TouchInteraction.EdgeSwipe, + "loops": 3 + }] +} diff --git a/qml/lib/hints/SingleTimeHint.qml b/qml/lib/hints/SingleTimeHint.qml new file mode 100644 index 0000000..80cad73 --- /dev/null +++ b/qml/lib/hints/SingleTimeHint.qml @@ -0,0 +1,20 @@ +import QtQuick 2.0 +import Sailfish.Silica 1.0 +import "../../components/hints" + +FirstTimeUseCounter { + id: firstTimeUseCounter + limit: 2 + defaultValue: 1 + property string hintComponent + onActiveChanged: { + if (value < limit) { + console.info("Showing hint " + hintComponent) + var comp = Qt.createComponent(hintComponent) + if (comp.status === Component.Ready) { + var obj = comp.createObject(parent) + } + firstTimeUseCounter.increase() + } + } +} diff --git a/qml/lib/hints/SlideShowHint.qml b/qml/lib/hints/SlideShowHint.qml new file mode 100644 index 0000000..c121957 --- /dev/null +++ b/qml/lib/hints/SlideShowHint.qml @@ -0,0 +1,85 @@ +import QtQuick 2.0 +import Sailfish.Silica 1.0 + +Item { + anchors.fill: slideNr < slides.length ? parent : null + height: slideNr < slides.length ? parent.height : 0 + + property var slides: [] + property int slideNr: -1 + property var slideconfig: null + property bool slideloaded: true + + MouseArea { + anchors.fill: parent + + onClicked: { + console.info("clicked hint") + touchInteractionHint.stop() + } + } + + Component.onCompleted: nextSlide() + + Loader { + id: centeredComponent + anchors.centerIn: parent + width: Theme.iconSizeExtraLarge + height: Theme.iconSizeExtraLarge + property int index: slideNr + source: !slideloaded ? "" : slideconfig.centeredComponent + visible: !slideloaded ? false : slideconfig.centeredComponent !== undefined + active: !slideloaded ? false : slideconfig.centeredComponent !== undefined + onActiveChanged: console.info("loading component " + source) + } + + InteractionHintLabel { + anchors.bottom: parent.bottom + width: parent.width + visible: true + + opacity: touchInteractionHint.running ? 1.0 : 0.0 + Behavior on opacity { + FadeAnimation { + duration: 750 + } + } + + text: !slideloaded ? "" : slideconfig.text + } + Item { + anchors.fill: parent + visible: slideloaded && slideconfig.interactionHidden !== true + onVisibleChanged: console.info( + "Slide " + slideNr + " hides the touchInteractionHint: " + !visible) + TouchInteractionHint { + id: touchInteractionHint + + direction: !slideloaded ? -1 : slideconfig.direction + interactionMode: !slideloaded ? -1 : slideconfig.interactionMode + anchors.verticalCenter: parent.bottom + loops: !slideloaded ? 0 : slideconfig.loops + onRunningChanged: if (!running && slideNr < slides.length) { + console.info("running changed switched to next slide") + nextSlide() + } + } + } + + function nextSlide() { + slideNr++ + console.info("nextslide: " + slideNr) + if (slideNr < slides.length) { + slideconfig = slides[slideNr] + slideloaded = true + console.info("switching to hint slide " + (slideNr) + " : " + JSON.stringify( + slideconfig)) + touchInteractionHint.restart() + } else { + slideloaded = false + slideconfig = null + centeredComponent.visible = false + touchInteractionHint.stop() + } + } +} diff --git a/qml/pages/Archive.qml b/qml/pages/Archive.qml index 71ea99a..bc7dc9f 100644 --- a/qml/pages/Archive.qml +++ b/qml/pages/Archive.qml @@ -1,6 +1,8 @@ import QtQuick 2.0 import Sailfish.Silica 1.0 +import "../lib/hints" import "../components" +import "../components/hints" Page { id: page @@ -11,10 +13,14 @@ Page { SilicaFlickable { anchors.fill: parent - VerticalScrollDecorator { } + VerticalScrollDecorator {} - AppMenu { thispage: "Archive" } - PrefAboutMenu { thelink: "https://gitlab.com/cy8aer/podqast/wikis/help-library" } + AppMenu { + thispage: "Archive" + } + PrefAboutMenu { + thelink: "https://gitlab.com/cy8aer/podqast/wikis/help-library" + } // Tell SilicaFlickable the height of its content. contentHeight: page.height @@ -37,11 +43,12 @@ Page { anchors.verticalCenter: parent.verticalCenter IconButton { id: refreshicon - enabled: refreshcol.refreshprogress == 1.0 && ! archivetitle.refreshing + enabled: refreshcol.refreshprogress == 1.0 + && !archivetitle.refreshing icon.source: "image://theme/icon-m-refresh" onClicked: { // refresh() - if (wifiConnected || doMobileDownConf.value ) { + if (wifiConnected || doMobileDownConf.value) { archivetitle.refreshing = true feedparserhandler.refreshPodcasts() } @@ -98,31 +105,35 @@ Page { console.log("Data length: " + pcdata.length) podcastsModel.clear() podcastsModel.append({ - bethead: "00title", - url: "", - description: "", - logo_url: "image://theme/icon-m-backup", - title: qsTr("History"), - topage: "History.qml"}) + "bethead": "00title", + "url": "", + "description": "", + "logo_url": "image://theme/icon-m-backup", + "title": qsTr("History"), + "topage": "History.qml" + }) podcastsModel.append({ - bethead: "00title", - url: "", - description: "", - logo_url: "image://theme/icon-m-favorite-selected", - title: qsTr("Favorites"), - topage: "Favorites.qml"}) - if(allowExtConf.value) { + "bethead": "00title", + "url": "", + "description": "", + "logo_url": "image://theme/icon-m-favorite-selected", + "title": qsTr("Favorites"), + "topage": "Favorites.qml" + }) + if (allowExtConf.value) { podcastsModel.append({ - bethead: "0ext", - url: "", - description: "", - logo_url: "image://theme/icon-m-device-upload", - title: qsTr("External Audio"), - topage: "External.qml"}) + "bethead": "0ext", + "url": "", + "description": "", + "logo_url": "image://theme/icon-m-device-upload", + "title": qsTr( + "External Audio"), + "topage": "External.qml" + }) } for (var i = 0; i < pcdata.length; i++) { pcdata[i]["bethead"] = "Podcast" - podcastsModel.append(pcdata[i]); + podcastsModel.append(pcdata[i]) } } } @@ -131,15 +142,22 @@ Page { enabled: podcastsModel.count == 0 text: qsTr("Rendering") hintText: qsTr("Collecting Podcasts") - verticalOffset: - archivetitle.height + verticalOffset: -archivetitle.height } model: ListModel { id: podcastsModel } - delegate: PodcastItem { } + delegate: PodcastItem {} } - PlayDockedPanel { id: pdp } + PlayDockedPanel { + id: pdp + } + } + + SingleTimeHint { + key: "/apps/ControlPanel/podqast/hints/archive_hint" + hintComponent: "../../components/hints/MainPageUsageHint.qml" } } diff --git a/qml/pages/Player.qml b/qml/pages/Player.qml index da01bce..ed62830 100644 --- a/qml/pages/Player.qml +++ b/qml/pages/Player.qml @@ -1,5 +1,8 @@ import QtQuick 2.0 import Sailfish.Silica 1.0 +import "../lib/hints" +import "../components/hints" +import "../components" Page { id: page @@ -276,4 +279,9 @@ Page { } } } + + SingleTimeHint { + key: "/apps/ControlPanel/podqast/hints/player_hint" + hintComponent: "../../components/hints/PlayerHint.qml" + } } diff --git a/translations/harbour-podqast-de.ts b/translations/harbour-podqast-de.ts index 5f53e15..a1d8a09 100644 --- a/translations/harbour-podqast-de.ts +++ b/translations/harbour-podqast-de.ts @@ -40,32 +40,32 @@ Archive - + Library Bibliothek - + History Verlauf - + Favorites Favoriten - + External Audio Fremde Audiodateien - + Rendering Zeichne - + Collecting Podcasts Sammle Podcasts @@ -360,22 +360,22 @@ FeedParserPython - + Auto-Post-Limit reached - + for %1 - + Auto-Post-Limit reached for %1 - + Error Fehler @@ -401,22 +401,22 @@ Inbox - + Inbox Eingang - + Moving all posts to archive Lege alle Beiträge ins Archiv - + No new posts Keine neuen Beiträge - + Pull down to Discover new podcasts, get posts from Library, or play the Playlist Menü um neue Podcasts zu Entdecken, hole Beiträge von der Bibliothek oder spiele die Playlist @@ -449,39 +449,90 @@ + + MainPageUsageHint + + + You can see whats going on by looking into the log down here + + + + + Podqast supports pagination of feeds. If you are missing old episodes of feeds, you can find a force refresh button in the settings. + + + + + On an episode image, this icon shows that it is downloaded... + + + + + ...currently playing... + + + + + ...already listened. + + + + + In the dock below, this icon means the file currently played is streaming from the internet right now... + + + + + ...the sleeptimer is active + + + + + ...a playrate different from 1 is set. + + + Player - + Auto-play next episode in queue - + Stop after each episode - + Audio playrate Abspielgeschwindigkeit - + Sleep timer Schlummer-Uhr - + running... läuft... - + chapters Kapitel + + PlayerHint + + + Select, if the next item of the playlist should be played automatically + + + PodcastDirectorySearchPage @@ -618,17 +669,17 @@ PostListItem - + playing - + listened - + remaining @@ -688,13 +739,13 @@ - + Inbox in den Eingang - + Archive in das Archiv @@ -724,29 +775,29 @@ - + Download/Streaming Laden/Stream - + Development Entwicklung - + Experimental features Experimentelle Funktionen - + Top of Playlist an den Anfang der Playlist - + Bottom of Playlist ans Ende der Playlist @@ -771,61 +822,66 @@ min - + External Audio Fremde Audiodateien - + Allow external audio Erlaube fremde Audiodateien - + Will take data from Hole Daten von - + Move external audio to Externe Audiodateien - + Download Playlist Posts Herunterladen von Playlist-Beiträgen - + Download on Mobile Bei Mobilfunk laden - + Keep Favorites downloaded Favoriten speichern - + Will save data in Speicher Daten in - + System System - + Audio viewable in system Audio im System sichbar - + + Auto-play next episode in queue + + + + Force refresh of old episodes @@ -975,13 +1031,13 @@ Podcasts vom OPML importiert - + Error Fehler - - + + Audio File not existing Audiodatei existiert nicht diff --git a/translations/harbour-podqast-es.ts b/translations/harbour-podqast-es.ts index 55d87a9..2107608 100644 --- a/translations/harbour-podqast-es.ts +++ b/translations/harbour-podqast-es.ts @@ -40,32 +40,32 @@ Archive - + Library Biblioteca - + History Historial - + Favorites Favoritos - + External Audio Audio externo - + Rendering Generando - + Collecting Podcasts Obteniendo podcasts @@ -360,22 +360,22 @@ FeedParserPython - + Auto-Post-Limit reached - + for %1 - + Auto-Post-Limit reached for %1 - + Error Error @@ -401,22 +401,22 @@ Inbox - + Inbox Entradas - + Moving all posts to archive Mover todas las publicaciones al almacén - + No new posts No hay nuevas publicaciones - + Pull down to Discover new podcasts, get posts from Library, or play the Playlist Desliza hacia abajo para encontrar nuevos podcasts, ver publicaciones de la biblioteca, o reproducir la lista de reproducción @@ -449,39 +449,90 @@ + + MainPageUsageHint + + + You can see whats going on by looking into the log down here + + + + + Podqast supports pagination of feeds. If you are missing old episodes of feeds, you can find a force refresh button in the settings. + + + + + On an episode image, this icon shows that it is downloaded... + + + + + ...currently playing... + + + + + ...already listened. + + + + + In the dock below, this icon means the file currently played is streaming from the internet right now... + + + + + ...the sleeptimer is active + + + + + ...a playrate different from 1 is set. + + + Player - + Auto-play next episode in queue - + Stop after each episode - + Audio playrate Velocidad de reproducción - + Sleep timer - + running... - + chapters capítulos + + PlayerHint + + + Select, if the next item of the playlist should be played automatically + + + PodcastDirectorySearchPage @@ -618,17 +669,17 @@ PostListItem - + playing - + listened - + remaining @@ -688,13 +739,13 @@ - + Inbox Entradas - + Archive Almacén @@ -724,29 +775,29 @@ - + Download/Streaming Descarga/Transmisión - + Development Desarrollo - + Experimental features Funciones experimentales - + Top of Playlist Al principio de la lista - + Bottom of Playlist Al final de la lista @@ -771,59 +822,64 @@ - + External Audio Audio externo - + Allow external audio Permitir audio externo - + Will take data from Cogerá datos de - + Move external audio to Mover audio externo a - + Download Playlist Posts Descargar publicaciones de la lista - + Download on Mobile Descargar en el móvil - + Keep Favorites downloaded Conservar favoritos descargados - + Will save data in Guardará los datos en - + System Sistema - + Audio viewable in system Audio visible en el sistema - + + Auto-play next episode in queue + + + + Force refresh of old episodes @@ -973,13 +1029,13 @@ podcasts importados desde OPML - + Error Error - - + + Audio File not existing El archivo de audio no existe diff --git a/translations/harbour-podqast-fr.ts b/translations/harbour-podqast-fr.ts index 57bb277..2625550 100644 --- a/translations/harbour-podqast-fr.ts +++ b/translations/harbour-podqast-fr.ts @@ -40,32 +40,32 @@ Archive - + Library Bibliothèque - + History Historique - + Favorites Favoris - + External Audio Fichiers Audios Externes - + Rendering Chargement - + Collecting Podcasts Récupération des Podcasts @@ -361,22 +361,22 @@ FeedParserPython - + Auto-Post-Limit reached - + for %1 - + Auto-Post-Limit reached for %1 - + Error Erreur @@ -402,22 +402,22 @@ Inbox - + Inbox Boîte aux lettres - + Moving all posts to archive Transfert des courriers aux archives - + No new posts Aucun nouveau courrier - + Pull down to Discover new podcasts, get posts from Library, or play the Playlist Glisser vers le bas pour Découvrir de nouveaux podcasts, obtenir de nouveaux courriers de la Bibliothèque, ou écouter la Playlist @@ -450,39 +450,90 @@ + + MainPageUsageHint + + + You can see whats going on by looking into the log down here + + + + + Podqast supports pagination of feeds. If you are missing old episodes of feeds, you can find a force refresh button in the settings. + + + + + On an episode image, this icon shows that it is downloaded... + + + + + ...currently playing... + + + + + ...already listened. + + + + + In the dock below, this icon means the file currently played is streaming from the internet right now... + + + + + ...the sleeptimer is active + + + + + ...a playrate different from 1 is set. + + + Player - + Auto-play next episode in queue - + Stop after each episode - + Audio playrate Vitesse de lecture - + Sleep timer Minuterie avant coupure - + running... activée... - + chapters chapitres + + PlayerHint + + + Select, if the next item of the playlist should be played automatically + + + PodcastDirectorySearchPage @@ -619,17 +670,17 @@ PostListItem - + playing - + listened - + remaining @@ -689,13 +740,13 @@ - + Inbox Boîte aux lettres - + Archive Archives @@ -725,29 +776,29 @@ - + Download/Streaming Téléchargement/Streaming - + Development Développement - + Experimental features Fonctionnalités expérimentales - + Top of Playlist En début de Playlist - + Bottom of Playlist En fin de la Playlist @@ -772,61 +823,66 @@ - + External Audio Fichiers Audios Externes - + Allow external audio Autoriser les fichiers externes - + Will take data from Prendra les données depuis - + Move external audio to Transférer les fichiers externes - + Download Playlist Posts Télécharger les Playlists d'Articles - + Download on Mobile Télécharger hors Wifi - + Keep Favorites downloaded Conserver les favoris déjà téléchargés - + Will save data in Sauvegardera les données dans - + System Système - + Audio viewable in system Audio visible dans le système - + + Auto-play next episode in queue + + + + Force refresh of old episodes @@ -976,13 +1032,13 @@ Podcasts importés depuis OPML - + Error Erreur - - + + Audio File not existing Le fichier audio n'existe pas diff --git a/translations/harbour-podqast-sv.ts b/translations/harbour-podqast-sv.ts index 15c418c..89013d6 100644 --- a/translations/harbour-podqast-sv.ts +++ b/translations/harbour-podqast-sv.ts @@ -40,32 +40,32 @@ Archive - + Library Bibliotek - + History Historik - + Favorites Favoriter - + External Audio Externt ljud - + Rendering Rendering - + Collecting Podcasts Hämtar poddar @@ -360,22 +360,22 @@ FeedParserPython - + Auto-Post-Limit reached - + for %1 - + Auto-Post-Limit reached for %1 - + Error Fel @@ -401,22 +401,22 @@ Inbox - + Inbox Inkorg - + Moving all posts to archive Flyttar alla poster till arkiv - + No new posts Inga nya poster - + Pull down to Discover new podcasts, get posts from Library, or play the Playlist Dra neråt för att upptäcka nya poddar, hämta poster från biblioteket eller spela upp spelningslistan. @@ -449,39 +449,90 @@ + + MainPageUsageHint + + + You can see whats going on by looking into the log down here + + + + + Podqast supports pagination of feeds. If you are missing old episodes of feeds, you can find a force refresh button in the settings. + + + + + On an episode image, this icon shows that it is downloaded... + + + + + ...currently playing... + + + + + ...already listened. + + + + + In the dock below, this icon means the file currently played is streaming from the internet right now... + + + + + ...the sleeptimer is active + + + + + ...a playrate different from 1 is set. + + + Player - + Auto-play next episode in queue - + Stop after each episode - + Audio playrate Spelningsfrekvens - + Sleep timer Insomningsur - + running... körs... - + chapters avsnitt + + PlayerHint + + + Select, if the next item of the playlist should be played automatically + + + PodcastDirectorySearchPage @@ -618,17 +669,17 @@ PostListItem - + playing - + listened - + remaining @@ -688,13 +739,13 @@ - + Inbox Inkorg - + Archive Arkiv @@ -724,29 +775,29 @@ - + Download/Streaming Nerladdning/Strömmning - + Development Utveckling - + Experimental features Experimentella funktioner - + Top of Playlist Först i spelningslistan - + Bottom of Playlist Sist i spelningslistan @@ -771,61 +822,66 @@ min - + External Audio Externt ljud - + Allow external audio Tillåt externt ljud - + Will take data from Hämtar data från - + Move external audio to Flytta externt ljud till - + Download Playlist Posts Ladda ner spelningslistans poster - + Download on Mobile Ladda ner på mobilanslutning - + Keep Favorites downloaded Behåll favoriter nerladdade - + Will save data in Sparar data i - + System System - + Audio viewable in system Ljud som visas i systemet - + + Auto-play next episode in queue + + + + Force refresh of old episodes @@ -975,13 +1031,13 @@ poddar importerade från OPML - + Error Fel - - + + Audio File not existing Ljudfilen finns inte diff --git a/translations/harbour-podqast-zh_CN.ts b/translations/harbour-podqast-zh_CN.ts index 642d180..7f098d8 100644 --- a/translations/harbour-podqast-zh_CN.ts +++ b/translations/harbour-podqast-zh_CN.ts @@ -40,32 +40,32 @@ Archive - + Library - + History 历史 - + Favorites 收藏 - + External Audio 外部音频 - + Rendering 生成 - + Collecting Podcasts 收藏的播客 @@ -360,22 +360,22 @@ FeedParserPython - + Auto-Post-Limit reached - + for %1 - + Auto-Post-Limit reached for %1 - + Error 错误 @@ -401,22 +401,22 @@ Inbox - + Inbox 收件箱 - + Moving all posts to archive 移动所有内容到存档 - + No new posts 无新内容 - + Pull down to Discover new podcasts, get posts from Library, or play the Playlist 下拉以找到新播客、从库获取内容或播放播放列表中的内容 @@ -449,39 +449,90 @@ + + MainPageUsageHint + + + You can see whats going on by looking into the log down here + + + + + Podqast supports pagination of feeds. If you are missing old episodes of feeds, you can find a force refresh button in the settings. + + + + + On an episode image, this icon shows that it is downloaded... + + + + + ...currently playing... + + + + + ...already listened. + + + + + In the dock below, this icon means the file currently played is streaming from the internet right now... + + + + + ...the sleeptimer is active + + + + + ...a playrate different from 1 is set. + + + Player - + Auto-play next episode in queue - + Stop after each episode - + Audio playrate 音频播放率 - + Sleep timer 睡眠计时器 - + running... 运行中…… - + chapters 章节 + + PlayerHint + + + Select, if the next item of the playlist should be played automatically + + + PodcastDirectorySearchPage @@ -618,17 +669,17 @@ PostListItem - + playing - + listened - + remaining @@ -688,13 +739,13 @@ - + Inbox 收件箱 - + Archive 存档 @@ -724,29 +775,29 @@ - + Download/Streaming 下载流媒体 - + Development 开发 - + Experimental features 实验功能 - + Top of Playlist 播放列表顶部 - + Bottom of Playlist 播放列表底部 @@ -771,59 +822,64 @@ 分钟 - + External Audio 外部音频 - + Allow external audio 允许外部音频 - + Will take data from 将会获取数据自 - + Move external audio to 移动外部音频到 - + Download Playlist Posts 下载播放列表内容 - + Download on Mobile 通过移动网络下载 - + Keep Favorites downloaded 保留下载的收藏 - + Will save data in 将会保存数据到 - + System 系统 - + Audio viewable in system 音频在系统可见 - + + Auto-play next episode in queue + + + + Force refresh of old episodes @@ -973,13 +1029,13 @@ 已从 OPML 导入播客 - + Error 错误 - - + + Audio File not existing 音频文件不存在 diff --git a/translations/harbour-podqast.ts b/translations/harbour-podqast.ts index b0f4fc2..3512149 100644 --- a/translations/harbour-podqast.ts +++ b/translations/harbour-podqast.ts @@ -40,32 +40,32 @@ Archive - + Library - + History History - + Favorites Favorites - + External Audio External Audio - + Rendering Rendering - + Collecting Podcasts Collecting Podcasts @@ -360,22 +360,22 @@ FeedParserPython - + Auto-Post-Limit reached - + for %1 - + Auto-Post-Limit reached for %1 - + Error Error @@ -401,22 +401,22 @@ Inbox - + Inbox Inbox - + Moving all posts to archive Moving all posts to archive - + No new posts No new posts - + Pull down to Discover new podcasts, get posts from Library, or play the Playlist Pull down to Discover new podcasts, get posts from Library, or play the Playlist @@ -449,39 +449,90 @@ + + MainPageUsageHint + + + You can see whats going on by looking into the log down here + + + + + Podqast supports pagination of feeds. If you are missing old episodes of feeds, you can find a force refresh button in the settings. + + + + + On an episode image, this icon shows that it is downloaded... + + + + + ...currently playing... + + + + + ...already listened. + + + + + In the dock below, this icon means the file currently played is streaming from the internet right now... + + + + + ...the sleeptimer is active + + + + + ...a playrate different from 1 is set. + + + Player - + Auto-play next episode in queue - + Stop after each episode - + Audio playrate Audio playrate - + Sleep timer Sleep timer - + running... running... - + chapters chapters + + PlayerHint + + + Select, if the next item of the playlist should be played automatically + + + PodcastDirectorySearchPage @@ -618,17 +669,17 @@ PostListItem - + playing - + listened - + remaining @@ -688,13 +739,13 @@ - + Inbox Inbox - + Archive Archive @@ -724,29 +775,29 @@ - + Download/Streaming Download/Streaming - + Development Development - + Experimental features Experimental features - + Top of Playlist Top of Playlist - + Bottom of Playlist Bottom of Playlist @@ -771,61 +822,66 @@ min - + External Audio External Audio - + Allow external audio Allow external audio - + Will take data from Will take data from - + Move external audio to Move external audio to - + Download Playlist Posts Download Playlist Posts - + Download on Mobile Download on Mobile - + Keep Favorites downloaded Keep Favorites downloaded - + Will save data in Will save data in - + System System - + Audio viewable in system Audio viewable in system - + + Auto-play next episode in queue + + + + Force refresh of old episodes @@ -975,13 +1031,13 @@ Podcasts imported from OPML - + Error Error - - + + Audio File not existing Audio File not existing -- GitLab From 2e1b6491e04afe79db76b9ae073fe0a5d2d33635 Mon Sep 17 00:00:00 2001 From: Thilo Kogge Date: Sat, 18 Sep 2021 10:52:39 +0200 Subject: [PATCH 18/20] can now sort episode to top of queue and replace currently playing. contains a bug that its not starting. playbackrate is now only configured globally. added icon for playbackrate to player page --- python/podcast/podcast.py | 4 +- python/podcast/queue.py | 1 + qml/components/PlayerHandler.qml | 15 +++----- qml/components/PodqastAudioPlayer.qml | 7 ++-- qml/components/PostListItem.qml | 4 +- qml/components/QueueHandler.py | 8 ++-- qml/components/hints/MainPageUsageHint.qml | 26 +++++++++---- qml/components/hints/PlayerHint.qml | 8 +++- qml/pages/Player.qml | 43 +++++++++++++++------- translations/harbour-podqast-de.ts | 41 ++++++++++++++------- translations/harbour-podqast-es.ts | 41 ++++++++++++++------- translations/harbour-podqast-fr.ts | 41 ++++++++++++++------- translations/harbour-podqast-sv.ts | 41 ++++++++++++++------- translations/harbour-podqast-zh_CN.ts | 41 ++++++++++++++------- translations/harbour-podqast.ts | 41 ++++++++++++++------- 15 files changed, 243 insertions(+), 119 deletions(-) diff --git a/python/podcast/podcast.py b/python/podcast/podcast.py index 83d6945..645d283 100644 --- a/python/podcast/podcast.py +++ b/python/podcast/podcast.py @@ -169,9 +169,9 @@ class Podcast(BaseModel): continue try: post = Podpost.from_feed_entry(self, entry, self.url, image) - except: + except Exception as e: logger.exception("could not process entry of podcast %s", self.url) - persist_log(LogType.FeedEpisodeParseError, title=self.title, entry=entry) + persist_log(LogType.FeedEpisodeParseError, title=self.title, entry=entry, exception=e) continue episode_count += 1 new_posts.append(post) diff --git a/python/podcast/queue.py b/python/podcast/queue.py index 4fc1d06..ae33bbc 100644 --- a/python/podcast/queue.py +++ b/python/podcast/queue.py @@ -263,6 +263,7 @@ class Queue: self.podposts.remove(podpost) self.podposts.insert(ind - 1, podpost) self.save() + return ind - 1 @sanitize_podpost def move_down(self, podpost): diff --git a/qml/components/PlayerHandler.qml b/qml/components/PlayerHandler.qml index 9197f4d..f97fe20 100644 --- a/qml/components/PlayerHandler.qml +++ b/qml/components/PlayerHandler.qml @@ -15,13 +15,13 @@ Python { property var chapters property int aktchapter property double playpos: 0 - property double playrate: 1.0 + property double playrate: globalPlayrateConf.value property double seekPos property bool doStartAfterSeek: false property bool streaming: mediaplayer.source && !/^file:/.test( mediaplayer.source) - signal playing(string audio_url, int position) + signal playing(string audio_url, int position, bool only_start_if_playing) signal pausing signal stopping signal downloading(int percent) @@ -44,21 +44,18 @@ Python { console.log('python error: ' + traceback) } - onPlayrateChanged: { - mediaplayer.playbackRate = playrate - } - onPlaying: { - mediaplayer.playbackRate = playrate console.info("Playing audio_url: " + audio_url + " @ rate " + playrate) if (audio_url != "") { mediaplayer.source = audio_url mediaplayer.seek(position - 15 * 1000) - mediaplayer.play() + if (!only_start_if_playing || mediaplayer.isPlaying) { + mediaplayer.play() + } mediaplayer.seek(position) seekPos = position - } else { + } else if (!only_start_if_playing || mediaplayer.isPlaying) { mediaplayer.play() } diff --git a/qml/components/PodqastAudioPlayer.qml b/qml/components/PodqastAudioPlayer.qml index 116198b..4947647 100644 --- a/qml/components/PodqastAudioPlayer.qml +++ b/qml/components/PodqastAudioPlayer.qml @@ -7,6 +7,8 @@ MediaPlayer { readonly property bool isPlaying: playbackState === Audio.PlayingState + playbackRate: globalPlayrateConf.value + onSourceChanged: console.log( "changing mediaplayer src to: " + mediaplayer.source) @@ -22,9 +24,8 @@ MediaPlayer { } } onSeekableChanged: { - console.log("onSeekableChanged status: " + mediaplayer.status - + " seekPos: " + playerHandler.seekPos - + " dostartafterSeek:" + playerHandler.doStartAfterSeek) + console.log("onSeekableChanged status: " + mediaplayer.status + " seekPos: " + + playerHandler.seekPos + " dostartafterSeek:" + playerHandler.doStartAfterSeek) playerHandler.setDuration() if (playerHandler.seekPos > 0) { mediaplayer.seek(playerHandler.seekPos) diff --git a/qml/components/PostListItem.qml b/qml/components/PostListItem.qml index 1b12e4a..c3f10b3 100644 --- a/qml/components/PostListItem.qml +++ b/qml/components/PostListItem.qml @@ -52,8 +52,8 @@ ListItem { EpisodeImage { id: episodeImage - anchors.left: left - anchors.verticalCenter: verticalCenter + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter showIsAudio: isaudio showIsPlaying: postId === playerHandler.firstid showIsListened: listened diff --git a/qml/components/QueueHandler.py b/qml/components/QueueHandler.py index 37bd0f2..4d7a49f 100644 --- a/qml/components/QueueHandler.py +++ b/qml/components/QueueHandler.py @@ -77,7 +77,9 @@ class QueueHandler(object): move element up """ - QueueFactory().get_queue().move_up(id) + if QueueFactory().get_queue().move_up(id) == 0: + await self.send_first_episode_chapters(id) + await self.queue_play(only_start_if_playing=True) await self.get_queue_posts(moved=id) async def queue_move_down(self, id): @@ -88,7 +90,7 @@ class QueueHandler(object): QueueFactory().get_queue().move_down(id) await self.get_queue_posts(moved=id) - async def queue_play(self, ): + async def queue_play(self, only_start_if_playing = False): """ Get the play information from queue """ @@ -100,7 +102,7 @@ class QueueHandler(object): if not data["url"]: pyotherside.send("audioNotExist") else: - pyotherside.send("playing", data["url"], data["position"]) + pyotherside.send("playing", data["url"], data["position"],only_start_if_playing) async def queue_pause(self, position): """ diff --git a/qml/components/hints/MainPageUsageHint.qml b/qml/components/hints/MainPageUsageHint.qml index d4d18d2..31b3ecf 100644 --- a/qml/components/hints/MainPageUsageHint.qml +++ b/qml/components/hints/MainPageUsageHint.qml @@ -6,15 +6,17 @@ import "../" SlideShowHint { slides: [{ - "text": qsTr("You can see whats going on by looking into the log down here"), - "direction": TouchInteraction.Up, - "interactionMode": TouchInteraction.EdgeSwipe, + "text": qsTr("Hi there, I gonna give you a quick tour to show you basic and new features."), + "direction": 0, + "interactionMode": 0, + "interactionHidden": true, "loops": 3 }, { - "text": qsTr("Podqast supports pagination of feeds. If you are missing old episodes of feeds, you can find a force refresh button in the settings."), - "direction": TouchInteraction.Up, - "interactionMode": TouchInteraction.EdgeSwipe, - "loops": 6 + "text": qsTr("Quick touch on an episode to see its description, long press it to add it to queue or play it"), + "direction": 0, + "interactionMode": 0, + "interactionHidden": true, + "loops": 5 }, { "text": qsTr("On an episode image, this icon shows that it is downloaded..."), "direction": 0, @@ -57,5 +59,15 @@ SlideShowHint { "interactionHidden": true, "loops": 2, "centeredComponent": "../../components/hints/HintDockIcons.qml" + }, { + "text": qsTr("You can see whats going on by looking into the log down here"), + "direction": TouchInteraction.Up, + "interactionMode": TouchInteraction.EdgeSwipe, + "loops": 3 + }, { + "text": qsTr("Podqast supports pagination of feeds. If you are missing old episodes of feeds, you can find a force refresh button in the settings."), + "direction": TouchInteraction.Up, + "interactionMode": TouchInteraction.EdgeSwipe, + "loops": 6 }] } diff --git a/qml/components/hints/PlayerHint.qml b/qml/components/hints/PlayerHint.qml index e81665c..d500d4f 100644 --- a/qml/components/hints/PlayerHint.qml +++ b/qml/components/hints/PlayerHint.qml @@ -5,9 +5,15 @@ import "../" SlideShowHint { slides: [{ - "text": qsTr("Select, if the next item of the playlist should be played automatically"), + "text": qsTr("Select if the next item of the playlist should be played automatically"), "direction": TouchInteraction.Down, "interactionMode": TouchInteraction.EdgeSwipe, "loops": 3 + }, { + "text": qsTr("Click the image to adjust the playrate"), + "direction": 0, + "interactionMode": 0, + "interactionHidden": true, + "loops": 3 }] } diff --git a/qml/pages/Player.qml b/qml/pages/Player.qml index ed62830..2e90f47 100644 --- a/qml/pages/Player.qml +++ b/qml/pages/Player.qml @@ -33,20 +33,36 @@ Page { opacity: 0.8 } - Slider { - id: playrateSlider + Item { + id: playrateRow width: parent.width - label: qsTr("Audio playrate") - minimumValue: 0.85 - maximumValue: 2.0 - handleVisible: true - valueText: "1:" + value - stepSize: 0.15 - value: playerHandler.playrate - onValueChanged: playerHandler.playrate = value + height: Theme.itemSizeExtraLarge + Icon { + id: playrateIcon + source: "image://theme/icon-s-duration" + width: Theme.iconSizeMedium + height: Theme.iconSizeMedium + anchors.verticalCenter: parent.verticalCenter + } + + Slider { + id: playrateSlider + anchors.left: playrateIcon.right + anchors.right: parent.right + label: qsTr("Audio playrate") + minimumValue: 0.75 + maximumValue: 2.5 + handleVisible: true + valueText: "1:" + value + stepSize: 0.05 + value: playerHandler.playrate + onValueChanged: { + globalPlayrateConf.value = value + } + } } - Row { - anchors.top: playrateSlider.bottom + Item { + anchors.top: playrateRow.bottom width: parent.width height: Theme.itemSizeExtraLarge IconButton { @@ -55,7 +71,6 @@ Page { icon.source: "image://theme/icon-m-timer" highlighted: podqast.dosleep onClicked: { - // highlighted = !highlighted podqast.dosleep = !podqast.dosleep } @@ -243,7 +258,7 @@ Page { height: parent.height handleVisible: false maximumValue: playerHandler.duration - value: mediaplayer.position + value: playerHandler.position valueText: to_pos_str(playerHandler.position / 1000) onPressedChanged: { playerHandler.seek(playSlider.value) diff --git a/translations/harbour-podqast-de.ts b/translations/harbour-podqast-de.ts index a1d8a09..06fb8d0 100644 --- a/translations/harbour-podqast-de.ts +++ b/translations/harbour-podqast-de.ts @@ -452,42 +452,52 @@ MainPageUsageHint - + You can see whats going on by looking into the log down here - + Podqast supports pagination of feeds. If you are missing old episodes of feeds, you can find a force refresh button in the settings. - + On an episode image, this icon shows that it is downloaded... - + + Hi there, I gonna give you a quick tour to show you basic and new features. + + + + + Quick touch on an episode to see its description, long press it to add it to queue or play it + + + + ...currently playing... - + ...already listened. - + In the dock below, this icon means the file currently played is streaming from the internet right now... - + ...the sleeptimer is active - + ...a playrate different from 1 is set. @@ -505,22 +515,22 @@ - + Audio playrate Abspielgeschwindigkeit - + Sleep timer Schlummer-Uhr - + running... läuft... - + chapters Kapitel @@ -529,7 +539,12 @@ PlayerHint - Select, if the next item of the playlist should be played automatically + Select if the next item of the playlist should be played automatically + + + + + Click the image to adjust the playrate diff --git a/translations/harbour-podqast-es.ts b/translations/harbour-podqast-es.ts index 2107608..f129711 100644 --- a/translations/harbour-podqast-es.ts +++ b/translations/harbour-podqast-es.ts @@ -452,42 +452,52 @@ MainPageUsageHint - + You can see whats going on by looking into the log down here - + Podqast supports pagination of feeds. If you are missing old episodes of feeds, you can find a force refresh button in the settings. - + On an episode image, this icon shows that it is downloaded... - + + Hi there, I gonna give you a quick tour to show you basic and new features. + + + + + Quick touch on an episode to see its description, long press it to add it to queue or play it + + + + ...currently playing... - + ...already listened. - + In the dock below, this icon means the file currently played is streaming from the internet right now... - + ...the sleeptimer is active - + ...a playrate different from 1 is set. @@ -505,22 +515,22 @@ - + Audio playrate Velocidad de reproducción - + Sleep timer - + running... - + chapters capítulos @@ -529,7 +539,12 @@ PlayerHint - Select, if the next item of the playlist should be played automatically + Select if the next item of the playlist should be played automatically + + + + + Click the image to adjust the playrate diff --git a/translations/harbour-podqast-fr.ts b/translations/harbour-podqast-fr.ts index 2625550..7070ce2 100644 --- a/translations/harbour-podqast-fr.ts +++ b/translations/harbour-podqast-fr.ts @@ -453,42 +453,52 @@ MainPageUsageHint - + You can see whats going on by looking into the log down here - + Podqast supports pagination of feeds. If you are missing old episodes of feeds, you can find a force refresh button in the settings. - + On an episode image, this icon shows that it is downloaded... - + + Hi there, I gonna give you a quick tour to show you basic and new features. + + + + + Quick touch on an episode to see its description, long press it to add it to queue or play it + + + + ...currently playing... - + ...already listened. - + In the dock below, this icon means the file currently played is streaming from the internet right now... - + ...the sleeptimer is active - + ...a playrate different from 1 is set. @@ -506,22 +516,22 @@ - + Audio playrate Vitesse de lecture - + Sleep timer Minuterie avant coupure - + running... activée... - + chapters chapitres @@ -530,7 +540,12 @@ PlayerHint - Select, if the next item of the playlist should be played automatically + Select if the next item of the playlist should be played automatically + + + + + Click the image to adjust the playrate diff --git a/translations/harbour-podqast-sv.ts b/translations/harbour-podqast-sv.ts index 89013d6..b14af1c 100644 --- a/translations/harbour-podqast-sv.ts +++ b/translations/harbour-podqast-sv.ts @@ -452,42 +452,52 @@ MainPageUsageHint - + You can see whats going on by looking into the log down here - + Podqast supports pagination of feeds. If you are missing old episodes of feeds, you can find a force refresh button in the settings. - + On an episode image, this icon shows that it is downloaded... - + + Hi there, I gonna give you a quick tour to show you basic and new features. + + + + + Quick touch on an episode to see its description, long press it to add it to queue or play it + + + + ...currently playing... - + ...already listened. - + In the dock below, this icon means the file currently played is streaming from the internet right now... - + ...the sleeptimer is active - + ...a playrate different from 1 is set. @@ -505,22 +515,22 @@ - + Audio playrate Spelningsfrekvens - + Sleep timer Insomningsur - + running... körs... - + chapters avsnitt @@ -529,7 +539,12 @@ PlayerHint - Select, if the next item of the playlist should be played automatically + Select if the next item of the playlist should be played automatically + + + + + Click the image to adjust the playrate diff --git a/translations/harbour-podqast-zh_CN.ts b/translations/harbour-podqast-zh_CN.ts index 7f098d8..896887c 100644 --- a/translations/harbour-podqast-zh_CN.ts +++ b/translations/harbour-podqast-zh_CN.ts @@ -452,42 +452,52 @@ MainPageUsageHint - + You can see whats going on by looking into the log down here - + Podqast supports pagination of feeds. If you are missing old episodes of feeds, you can find a force refresh button in the settings. - + On an episode image, this icon shows that it is downloaded... - + + Hi there, I gonna give you a quick tour to show you basic and new features. + + + + + Quick touch on an episode to see its description, long press it to add it to queue or play it + + + + ...currently playing... - + ...already listened. - + In the dock below, this icon means the file currently played is streaming from the internet right now... - + ...the sleeptimer is active - + ...a playrate different from 1 is set. @@ -505,22 +515,22 @@ - + Audio playrate 音频播放率 - + Sleep timer 睡眠计时器 - + running... 运行中…… - + chapters 章节 @@ -529,7 +539,12 @@ PlayerHint - Select, if the next item of the playlist should be played automatically + Select if the next item of the playlist should be played automatically + + + + + Click the image to adjust the playrate diff --git a/translations/harbour-podqast.ts b/translations/harbour-podqast.ts index 3512149..534dd3a 100644 --- a/translations/harbour-podqast.ts +++ b/translations/harbour-podqast.ts @@ -452,42 +452,52 @@ MainPageUsageHint - + You can see whats going on by looking into the log down here - + Podqast supports pagination of feeds. If you are missing old episodes of feeds, you can find a force refresh button in the settings. - + On an episode image, this icon shows that it is downloaded... - + + Hi there, I gonna give you a quick tour to show you basic and new features. + + + + + Quick touch on an episode to see its description, long press it to add it to queue or play it + + + + ...currently playing... - + ...already listened. - + In the dock below, this icon means the file currently played is streaming from the internet right now... - + ...the sleeptimer is active - + ...a playrate different from 1 is set. @@ -505,22 +515,22 @@ - + Audio playrate Audio playrate - + Sleep timer Sleep timer - + running... running... - + chapters chapters @@ -529,7 +539,12 @@ PlayerHint - Select, if the next item of the playlist should be played automatically + Select if the next item of the playlist should be played automatically + + + + + Click the image to adjust the playrate -- GitLab From e92bf0aceab1f3741bd076fbedeff37ebb5adf5a Mon Sep 17 00:00:00 2001 From: Thilo Kogge Date: Sat, 18 Sep 2021 11:35:45 +0200 Subject: [PATCH 19/20] a few small margin fixes --- qml/pages/Log.qml | 5 ++--- qml/pages/Player.qml | 4 ++-- translations/harbour-podqast-de.ts | 8 ++++---- translations/harbour-podqast-es.ts | 8 ++++---- translations/harbour-podqast-fr.ts | 8 ++++---- translations/harbour-podqast-sv.ts | 8 ++++---- translations/harbour-podqast-zh_CN.ts | 8 ++++---- translations/harbour-podqast.ts | 8 ++++---- 8 files changed, 28 insertions(+), 29 deletions(-) diff --git a/qml/pages/Log.qml b/qml/pages/Log.qml index b2793a7..b6c8ea6 100644 --- a/qml/pages/Log.qml +++ b/qml/pages/Log.qml @@ -33,9 +33,8 @@ Page { delegate: Column { clip: true - width: ListView.view.width - anchors.leftMargin: Theme.paddingMedium - anchors.rightMargin: Theme.paddingMedium + x: Theme.horizontalPageMargin + width: parent.width - 2 * x Label { wrapMode: Text.WordWrap width: parent.width diff --git a/qml/pages/Player.qml b/qml/pages/Player.qml index 2e90f47..e74c287 100644 --- a/qml/pages/Player.qml +++ b/qml/pages/Player.qml @@ -35,7 +35,7 @@ Page { Item { id: playrateRow - width: parent.width + width: parent.width - 2 * Theme.horizontalPageMargin height: Theme.itemSizeExtraLarge Icon { id: playrateIcon @@ -63,7 +63,7 @@ Page { } Item { anchors.top: playrateRow.bottom - width: parent.width + width: parent.width - 2 * Theme.horizontalPageMargin height: Theme.itemSizeExtraLarge IconButton { id: sleepButton diff --git a/translations/harbour-podqast-de.ts b/translations/harbour-podqast-de.ts index 06fb8d0..0fbc614 100644 --- a/translations/harbour-podqast-de.ts +++ b/translations/harbour-podqast-de.ts @@ -429,22 +429,22 @@ - + No logs - + Skipped refresh on '%1', because the server responded with 'Not Modified' - + Updated '%1'. %2 new episodes - + '%1' could not be refreshed because: %2 diff --git a/translations/harbour-podqast-es.ts b/translations/harbour-podqast-es.ts index f129711..9186355 100644 --- a/translations/harbour-podqast-es.ts +++ b/translations/harbour-podqast-es.ts @@ -429,22 +429,22 @@ - + No logs - + Skipped refresh on '%1', because the server responded with 'Not Modified' - + Updated '%1'. %2 new episodes - + '%1' could not be refreshed because: %2 diff --git a/translations/harbour-podqast-fr.ts b/translations/harbour-podqast-fr.ts index 7070ce2..f59f959 100644 --- a/translations/harbour-podqast-fr.ts +++ b/translations/harbour-podqast-fr.ts @@ -430,22 +430,22 @@ - + No logs - + Skipped refresh on '%1', because the server responded with 'Not Modified' - + Updated '%1'. %2 new episodes - + '%1' could not be refreshed because: %2 diff --git a/translations/harbour-podqast-sv.ts b/translations/harbour-podqast-sv.ts index b14af1c..7e5f84f 100644 --- a/translations/harbour-podqast-sv.ts +++ b/translations/harbour-podqast-sv.ts @@ -429,22 +429,22 @@ - + No logs - + Skipped refresh on '%1', because the server responded with 'Not Modified' - + Updated '%1'. %2 new episodes - + '%1' could not be refreshed because: %2 diff --git a/translations/harbour-podqast-zh_CN.ts b/translations/harbour-podqast-zh_CN.ts index 896887c..eb9a6a2 100644 --- a/translations/harbour-podqast-zh_CN.ts +++ b/translations/harbour-podqast-zh_CN.ts @@ -429,22 +429,22 @@ - + No logs - + Skipped refresh on '%1', because the server responded with 'Not Modified' - + Updated '%1'. %2 new episodes - + '%1' could not be refreshed because: %2 diff --git a/translations/harbour-podqast.ts b/translations/harbour-podqast.ts index 534dd3a..f9b5f40 100644 --- a/translations/harbour-podqast.ts +++ b/translations/harbour-podqast.ts @@ -429,22 +429,22 @@ - + No logs - + Skipped refresh on '%1', because the server responded with 'Not Modified' - + Updated '%1'. %2 new episodes - + '%1' could not be refreshed because: %2 -- GitLab From 29f2e3d08701b426cfd28caeaabcf99ec9fb1fec Mon Sep 17 00:00:00 2001 From: Thilo Kogge Date: Sat, 18 Sep 2021 12:28:13 +0200 Subject: [PATCH 20/20] updated whatsnew text --- qml/pages/DataMigration.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qml/pages/DataMigration.qml b/qml/pages/DataMigration.qml index cc2e14e..430a053 100644 --- a/qml/pages/DataMigration.qml +++ b/qml/pages/DataMigration.qml @@ -76,7 +76,7 @@ Page { width: parent.width wrapMode: Text.WordWrap //: whatsnew section of the migration - text: qsTr("Whats New?
  • Some of you lost episodes in the last migration. " + "Because we found this pretty sad, we're trying to recover them now.
  • " + "
  • If you are missing old episodes of feeds in general and this didn't help, try the force refresh button in the settings (We added support for Pagination).
  • " + "
  • Podcasts are now sorted alphabetically, we hope you like that!
  • " + "
  • Gpodder should work again.
  • Check the new Log Page next to the settings.
  • " + "
  • If you're streaming an episode and the download finishes, we're now switching to the downloaded file automatically.
  • Also more speed, bugfixes and code cleanups.
  • " + "
If you want to contribute help translating the app or report issues on gitlab.") + text: qsTr("Whats New?
  • Some of you lost episodes in the last migration. Because we found this pretty sad, we're trying to recover them now.
  • If you are missing old episodes of feeds in general and this didn't help, try the force refresh button in the settings (We added support for Pagination).
  • Podcasts are now sorted alphabetically, we hope you like that!
  • Gpodder should work again.
  • Check the new Log Page next to the settings.
  • " + "
  • If you're streaming an episode and the download finishes, we're now switching to the downloaded file automatically.
  • Also more speed, bugfixes and code cleanups.

If you want to contribute to podQast, you can help translating the app or report issues on GitLab.") } Label { -- GitLab