diff --git a/common/GPodderPlayback.qml b/common/GPodderPlayback.qml index cfe2dd6..134ab30 100644 --- a/common/GPodderPlayback.qml +++ b/common/GPodderPlayback.qml @@ -26,6 +26,7 @@ MediaPlayer { property int episode: 0 property string episode_title: '' + property var episode_chapters: ([]) property string podcast_title: '' signal playerCreated() @@ -62,6 +63,7 @@ MediaPlayer { var old_episode = player.episode; player.episode = episode_id; player.episode_title = episode.title; + player.episode_chapters = episode.chapters; player.podcast_title = episode.podcast_title; player.source = episode.source; player.seekTargetSeconds = episode.position; diff --git a/main.py b/main.py index 756e773..9039b40 100644 --- a/main.py +++ b/main.py @@ -327,6 +327,7 @@ class gPotherSide: 'position': episode.current_position, 'total': episode.total_time, 'video': episode.file_type() == 'video', + 'chapters': getattr(episode, 'chapters', []), } def report_playback_event(self, episode_id, position_from, position_to, duration): @@ -345,6 +346,7 @@ class gPotherSide: 'description': util.remove_html_tags(episode.description), 'metadata': ' | '.join(self._format_metadata(episode)), 'link': episode.link if episode.link != episode.url else '', + 'chapters': getattr(episode, 'chapters', []), } def _format_metadata(self, episode): diff --git a/touch/EpisodeDetail.qml b/touch/EpisodeDetail.qml index 1f7f7d8..0881562 100644 --- a/touch/EpisodeDetail.qml +++ b/touch/EpisodeDetail.qml @@ -21,6 +21,7 @@ import QtQuick 2.0 import 'common/constants.js' as Constants +import 'common/util.js' as Util import 'icons/icons.js' as Icons SlidePage { @@ -30,6 +31,7 @@ SlidePage { property string title property string link property bool ready: false + property var chapters: ([]) hasMenuButton: detailPage.link != '' menuButtonIcon: Icons.link @@ -47,6 +49,7 @@ SlidePage { metadataLabel.text = episode.metadata; detailPage.link = episode.link; detailPage.ready = true; + detailPage.chapters = episode.chapters; }); } @@ -87,6 +90,45 @@ SlidePage { color: Constants.colors.placeholder } + PExpander { + visible: detailPage.chapters.length > 0 + + width: parent.width + expandedHeight: chaptersColumn.childrenRect.height + + Column { + id: chaptersColumn + width: parent.width + + PLabel { + text: 'Chapters' + color: Constants.colors.secondaryHighlight + } + + Repeater { + model: detailPage.chapters + + delegate: Column { + width: parent.width + + PLabel { + width: parent.width + text: Util.formatDuration(modelData.start) + font.pixelSize: 20 * pgst.scalef + color: Constants.colors.secondaryHighlight + } + + PLabel { + width: parent.width + text: modelData.title + font.pixelSize: 20 * pgst.scalef + color: Constants.colors.placeholder + } + } + } + } + } + PLabel { id: descriptionLabel width: parent.width diff --git a/touch/PExpander.qml b/touch/PExpander.qml new file mode 100644 index 0000000..da1d102 --- /dev/null +++ b/touch/PExpander.qml @@ -0,0 +1,88 @@ + +/** + * + * gPodder QML UI Reference Implementation + * Copyright (c) 2014, Thomas Perl + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + * + */ + +import QtQuick 2.0 + +import 'common/constants.js' as Constants +import 'common/util.js' as Util + + +Item { + id: expander + + property bool expanded: false + property bool canExpand: expandedHeight > contractedHeight + + property real contractedHeight: 100 * pgst.scalef + property real expandedHeight: childrenRect.height + property color backgroundColor: Constants.colors.page + + height: expanded ? expandedHeight : contractedHeight + clip: true + + Behavior on height { PropertyAnimation { } } + + MouseArea { + anchors.fill: parent + onClicked: { + if (expander.canExpand) { + expander.expanded = !expander.expanded + } + } + } + + Rectangle { + z: 100 + + opacity: chapterExpander.opacity + + anchors { + left: parent.left + right: parent.right + bottom: parent.bottom + } + + height: expander.contractedHeight * 0.6 + + gradient: Gradient { + GradientStop { position: 0; color: '#00000000' } + GradientStop { position: 1; color: expander.backgroundColor } + } + } + + PLabel { + id: chapterExpander + + z: 200 + + anchors { + right: parent.right + bottom: parent.bottom + } + + text: '...' + font.pixelSize: 60 * pgst.scalef + + color: Constants.colors.highlight + opacity: !expander.expanded + + Behavior on opacity { PropertyAnimation { } } + } +} diff --git a/touch/PlayerPage.qml b/touch/PlayerPage.qml index 7b09ce2..68d3dbd 100644 --- a/touch/PlayerPage.qml +++ b/touch/PlayerPage.qml @@ -135,6 +135,29 @@ Dialog { icon: Icons.last onClicked: player.seekAndSync(player.position + 60 * 1000); } + + IconMenuItem { + text: 'Chapters' + color: Constants.colors.playback + icon: Icons.tag_fill + visible: player.episode_chapters.length > 0 + onClicked: { + var items = []; + + for (var i in player.episode_chapters) { + (function (items, chapter) { + items.push({ + label: chapter.title + ' (' + Util.formatDuration(chapter.start) + ')', + callback: function () { + player.seekAndSync(chapter.start * 1000); + } + }); + })(items, player.episode_chapters[i]); + } + + pgst.showSelection(items, 'Chapters'); + } + } } } } diff --git a/touch/SelectionDialog.qml b/touch/SelectionDialog.qml index a3dcc69..bcad820 100644 --- a/touch/SelectionDialog.qml +++ b/touch/SelectionDialog.qml @@ -67,6 +67,7 @@ Dialog { PLabel { anchors { left: parent.left + right: parent.right verticalCenter: parent.verticalCenter margins: 20 * pgst.scalef } @@ -74,6 +75,7 @@ Dialog { text: modelData color: (index == selectionDialog.selectedIndex || buttonArea.pressed) ? Constants.colors.dialogHighlight : Constants.colors.dialogText font.pixelSize: 30 * pgst.scalef + elide: Text.ElideRight } onClicked: { diff --git a/touch/icons/icons.js b/touch/icons/icons.js index c7c8752..780910f 100644 --- a/touch/icons/icons.js +++ b/touch/icons/icons.js @@ -21,3 +21,4 @@ var cog = '\u2699'; var link = '\ue077'; var vellipsis = '\u22ee'; var paperclip = '\ue08a'; +var tag_fill = '\ue02b';