QML UI: Nicer MediaPlayer UI, generic blocker wall
This commit is contained in:
parent
2f9be802e6
commit
98fdf50ee8
|
@ -13,16 +13,6 @@ Item {
|
|||
signal close
|
||||
signal response(int index)
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
color: "black"
|
||||
anchors.fill: parent
|
||||
opacity: .9
|
||||
}
|
||||
|
||||
ListView {
|
||||
visible: !contextMenuArea.subscribeMode
|
||||
model: contextMenuArea.items
|
||||
|
|
|
@ -154,14 +154,49 @@ Rectangle {
|
|||
leftMargin: Config.largeSpacing * 2
|
||||
rightMargin: Config.largeSpacing * 2
|
||||
topMargin: Config.largeSpacing * 2
|
||||
bottomMargin: Config.largeSpacing * 2
|
||||
bottomMargin: Config.largeSpacing * 2 + (nowPlayingThrobber.shouldAppear?nowPlayingThrobber.height:0)
|
||||
}
|
||||
|
||||
Behavior on opacity { NumberAnimation { duration: Config.slowTransition } }
|
||||
Behavior on scale { NumberAnimation { duration: Config.slowTransition; easing.type: Easing.InSine } }
|
||||
}
|
||||
|
||||
Item {
|
||||
id: overlayInteractionBlockWall
|
||||
anchors.fill: parent
|
||||
z: nowPlayingThrobber.opened?2:0
|
||||
|
||||
opacity: (nowPlayingThrobber.opened || contextMenu.state == 'opened' || messageDialog.opacity == 1)?1:0
|
||||
Behavior on opacity { NumberAnimation { duration: Config.slowTransition } }
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
if (contextMenu.state == 'opened') {
|
||||
contextMenu.close()
|
||||
} else if (messageDialog.opacity == 1) {
|
||||
messageDialog.opacity = 0
|
||||
} else {
|
||||
nowPlayingThrobber.opened = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: 'black'
|
||||
opacity: .7
|
||||
}
|
||||
|
||||
Image {
|
||||
anchors.fill: parent
|
||||
source: 'artwork/mask.png'
|
||||
}
|
||||
}
|
||||
|
||||
NowPlayingThrobber {
|
||||
z: 3
|
||||
|
||||
property bool shouldAppear: ((contextMenu.state != 'opened') && (mediaPlayer.episode !== undefined))
|
||||
|
||||
id: nowPlayingThrobber
|
||||
|
@ -181,11 +216,12 @@ Rectangle {
|
|||
id: mediaPlayer
|
||||
visible: nowPlayingThrobber.opened
|
||||
|
||||
z: 3
|
||||
|
||||
anchors.top: parent.bottom
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: nowPlayingThrobber.opened?-100:0
|
||||
anchors.topMargin: nowPlayingThrobber.opened?-height:0
|
||||
|
||||
Behavior on anchors.topMargin { PropertyAnimation { duration: Config.slowTransition } }
|
||||
}
|
||||
|
@ -348,11 +384,6 @@ Rectangle {
|
|||
|
||||
Behavior on opacity { PropertyAnimation { } }
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: '#ee000000'
|
||||
}
|
||||
|
||||
Text {
|
||||
id: messageDialogText
|
||||
anchors.centerIn: parent
|
||||
|
@ -360,11 +391,6 @@ Rectangle {
|
|||
font.pixelSize: 20
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: messageDialog.opacity = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,10 +3,13 @@ import Qt 4.7
|
|||
import QtMultimediaKit 1.1
|
||||
|
||||
import 'config.js' as Config
|
||||
import 'util.js' as Util
|
||||
|
||||
Item {
|
||||
id: mediaPlayer
|
||||
|
||||
height: (Config.largeSpacing * 2) + (150 * Config.scale)
|
||||
|
||||
property variant episode: undefined
|
||||
|
||||
property bool playing: audioPlayer.playing && !audioPlayer.paused
|
||||
|
@ -52,18 +55,91 @@ Item {
|
|||
anchors.fill: mediaPlayer
|
||||
color: 'black'
|
||||
|
||||
Row {
|
||||
spacing: Config.largeSpacing
|
||||
|
||||
anchors {
|
||||
leftMargin: Config.largeSpacing
|
||||
topMargin: Config.largeSpacing
|
||||
left: parent.left
|
||||
top: parent.top
|
||||
}
|
||||
|
||||
Image {
|
||||
id: coverArt
|
||||
source: (episode!==undefined)?Util.formatCoverURL(episode.qpodcast):''
|
||||
width: 150 * Config.scale
|
||||
height: 150 * Config.scale
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: mediaPlayer.togglePlayback(episode)
|
||||
}
|
||||
|
||||
sourceSize.width: width
|
||||
sourceSize.height: height
|
||||
}
|
||||
|
||||
Column {
|
||||
id: textColumn
|
||||
|
||||
spacing: Config.smallSpacing
|
||||
|
||||
Item { height: 1; width: 1 }
|
||||
|
||||
Text {
|
||||
text: episode.qtitle
|
||||
color: 'white'
|
||||
font.pixelSize: 30 * Config.scale
|
||||
}
|
||||
|
||||
Text {
|
||||
text: episode.qpodcast.qtitle
|
||||
color: '#aaa'
|
||||
font.pixelSize: 20 * Config.scale
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PlaybackBar {
|
||||
id: playbackBar
|
||||
|
||||
width: mediaPlayer.width - coverArt.width - 3*Config.largeSpacing
|
||||
x: coverArt.width + 2*Config.largeSpacing
|
||||
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: Config.largeSpacing
|
||||
|
||||
Behavior on opacity { PropertyAnimation { } }
|
||||
|
||||
progress: episode != undefined?(episode.qduration?(episode.qposition / episode.qduration):0):0
|
||||
duration: episode != undefined?episode.qduration:0
|
||||
|
||||
onSetProgress: {
|
||||
audioPlayer.setPosition(progress)
|
||||
audioPlayer.paused = false
|
||||
}
|
||||
|
||||
onForward: {
|
||||
if (episode != undefined && episode.qduration > 0) {
|
||||
var pos = (episode.qposition + 60)/episode.qduration
|
||||
audioPlayer.setPosition(pos)
|
||||
}
|
||||
}
|
||||
|
||||
onBackward: {
|
||||
if (episode != undefined && episode.qduration > 0) {
|
||||
var pos = (episode.qposition - 60)/episode.qduration
|
||||
if (pos < 0) pos = 0
|
||||
audioPlayer.setPosition(pos)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Audio {
|
||||
id: audioPlayer
|
||||
property bool seekLater: false
|
||||
|
||||
/*onPlayingChanged: {
|
||||
if (!playing) {
|
||||
playing = true
|
||||
position = 0
|
||||
paused = true
|
||||
}
|
||||
}*/
|
||||
|
||||
onPositionChanged: {
|
||||
episode.qposition = position/1000
|
||||
}
|
||||
|
@ -90,44 +166,6 @@ Item {
|
|||
audioPlayer.position = position*episode.qduration*1000
|
||||
}
|
||||
}
|
||||
|
||||
PlaybackBar {
|
||||
id: playbackBar
|
||||
|
||||
Behavior on opacity { PropertyAnimation { } }
|
||||
|
||||
progress: episode != undefined?(episode.qduration?(episode.qposition / episode.qduration):0):0
|
||||
duration: episode != undefined?episode.qduration:0
|
||||
paused: audioPlayer.paused
|
||||
onSetProgress: {
|
||||
audioPlayer.setPosition(progress)
|
||||
audioPlayer.paused = false
|
||||
}
|
||||
onForward: {
|
||||
if (episode != undefined && episode.qduration > 0) {
|
||||
var pos = (episode.qposition + 60)/episode.qduration
|
||||
audioPlayer.setPosition(pos)
|
||||
}
|
||||
}
|
||||
onBackward: {
|
||||
if (episode != undefined && episode.qduration > 0) {
|
||||
var pos = (episode.qposition - 60)/episode.qduration
|
||||
if (pos < 0) pos = 0
|
||||
audioPlayer.setPosition(pos)
|
||||
}
|
||||
}
|
||||
onSetPaused: {
|
||||
audioPlayer.paused = !audioPlayer.paused
|
||||
}
|
||||
anchors {
|
||||
bottom: parent.bottom
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
bottomMargin: Config.largeSpacing
|
||||
leftMargin: Config.largeSpacing * 2
|
||||
rightMargin: Config.largeSpacing * 2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -11,7 +11,9 @@ Item {
|
|||
signal clicked
|
||||
|
||||
height: Config.headerHeight
|
||||
width: icon.width + text.width
|
||||
width: icon.width + (opened?0:text.width)
|
||||
|
||||
Behavior on width { NumberAnimation { duration: Config.slowTransition } }
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
|
@ -59,7 +61,7 @@ Item {
|
|||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: 'white'
|
||||
font.pixelSize: 20 * Config.scale
|
||||
text: nowPlayingThrobber.caption
|
||||
text: nowPlayingThrobber.opened?'':nowPlayingThrobber.caption
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,10 +5,8 @@ import 'config.js' as Config
|
|||
Item {
|
||||
id: root
|
||||
property real progress: 0
|
||||
property bool paused: false
|
||||
property int duration: 0
|
||||
signal setProgress(real progress)
|
||||
signal setPaused()
|
||||
signal forward()
|
||||
signal backward()
|
||||
|
||||
|
@ -21,33 +19,6 @@ Item {
|
|||
opacity: .8
|
||||
}
|
||||
|
||||
PlaybackBarButton {
|
||||
id: play
|
||||
source: 'artwork/btn_play.png'
|
||||
anchors.left: parent.left
|
||||
|
||||
states: [
|
||||
State {
|
||||
name: 'play'
|
||||
PropertyChanges {
|
||||
target: play
|
||||
source: 'artwork/btn_play.png'
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: 'pause'
|
||||
PropertyChanges {
|
||||
target: play
|
||||
source: 'artwork/btn_pause.png'
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
state: root.paused?'play':'pause'
|
||||
|
||||
onClicked: root.setPaused()
|
||||
}
|
||||
|
||||
PlaybackBarButton {
|
||||
id: rwnd
|
||||
anchors.left: play.right
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
import Qt 4.7
|
||||
|
||||
import 'config.js' as Config
|
||||
import 'util.js' as Util
|
||||
|
||||
SelectableItem {
|
||||
id: podcastItem
|
||||
|
@ -50,7 +51,7 @@ SelectableItem {
|
|||
}
|
||||
|
||||
Image {
|
||||
source: (modelData.qcoverurl != '')?('image://cover/'+escape(modelData.qcoverfile)+'|'+escape(modelData.qcoverurl)+'|'+escape(modelData.qurl)):''
|
||||
source: Util.formatCoverURL(modelData)
|
||||
asynchronous: true
|
||||
width: parent.width * .85
|
||||
height: parent.height * .85
|
||||
|
|
|
@ -10,3 +10,16 @@ function formatDuration(duration) {
|
|||
return hh + ms
|
||||
}
|
||||
|
||||
function formatCoverURL(podcast) {
|
||||
var cover_file = podcast.qcoverfile
|
||||
var cover_url = podcast.qcoverurl
|
||||
var podcast_url = podcast.qurl
|
||||
|
||||
if (cover_url == '') {
|
||||
return ''
|
||||
}
|
||||
|
||||
return ('image://cover/' + escape(cover_file) + '|' +
|
||||
escape(cover_url) + '|' + escape(podcast_url))
|
||||
}
|
||||
|
||||
|
|
|
@ -1192,12 +1192,15 @@ class Model(object):
|
|||
def podcast_sort_key(cls, podcast):
|
||||
return cls.PodcastClass.sort_key(podcast)
|
||||
|
||||
@staticmethod
|
||||
def sort_episodes_by_pubdate(episodes, reverse=False):
|
||||
@classmethod
|
||||
def episode_sort_key(cls, episode):
|
||||
return episode.published
|
||||
|
||||
@classmethod
|
||||
def sort_episodes_by_pubdate(cls, episodes, reverse=False):
|
||||
"""Sort a list of PodcastEpisode objects chronologically
|
||||
|
||||
Returns a iterable, sorted sequence of the episodes
|
||||
"""
|
||||
get_key = lambda e: e.published
|
||||
return sorted(episodes, key=get_key, reverse=reverse)
|
||||
return sorted(episodes, key=cls.episode_sort_key, reverse=reverse)
|
||||
|
||||
|
|
|
@ -418,8 +418,16 @@ class qtPodder(QObject):
|
|||
return model.QEpisode(self, podcast, episode)
|
||||
|
||||
def select_podcast(self, podcast):
|
||||
wrap = functools.partial(self.wrap_episode, podcast)
|
||||
self.episode_model.set_objects(map(wrap, podcast.get_all_episodes()))
|
||||
if isinstance(podcast, model.QPodcast):
|
||||
# Normal QPodcast instance
|
||||
wrap = functools.partial(self.wrap_episode, podcast)
|
||||
objects = podcast.get_all_episodes()
|
||||
else:
|
||||
# EpisodeSubsetView
|
||||
wrap = lambda args: self.wrap_episode(*args)
|
||||
objects = podcast.get_all_episodes_with_podcast()
|
||||
|
||||
self.episode_model.set_objects(map(wrap, objects))
|
||||
self.main.state = 'episodes'
|
||||
|
||||
def save_pending_data(self):
|
||||
|
|
|
@ -38,8 +38,9 @@ class LocalCachedImageProvider(QDeclarativeImageProvider):
|
|||
self._cache = {}
|
||||
|
||||
def requestImage(self, id, size, requestedSize):
|
||||
if id in self._cache:
|
||||
return self._cache[id]
|
||||
key = (id, requestedSize)
|
||||
if key in self._cache:
|
||||
return self._cache[key]
|
||||
|
||||
filename, cover_url, url = (urllib.unquote(x) for x in id.split('|'))
|
||||
|
||||
|
@ -69,8 +70,8 @@ class LocalCachedImageProvider(QDeclarativeImageProvider):
|
|||
if image.isNull():
|
||||
return image
|
||||
else:
|
||||
self._cache[id] = image.scaled(requestedSize, \
|
||||
self._cache[key] = image.scaled(requestedSize, \
|
||||
Qt.KeepAspectRatioByExpanding, \
|
||||
Qt.SmoothTransformation)
|
||||
return self._cache[id]
|
||||
return self._cache[key]
|
||||
|
||||
|
|
|
@ -90,10 +90,10 @@ class QEpisode(QObject):
|
|||
never_changed = Signal()
|
||||
source_url_changed = Signal()
|
||||
|
||||
def _id(self):
|
||||
return self._episode.id
|
||||
def _podcast(self):
|
||||
return self._podcast
|
||||
|
||||
qid = Property(int, _id, notify=never_changed)
|
||||
qpodcast = Property(QObject, _podcast, notify=never_changed)
|
||||
|
||||
def _title(self):
|
||||
return convert(self._episode.title)
|
||||
|
@ -317,6 +317,19 @@ class EpisodeSubsetView(QObject):
|
|||
self.description = description
|
||||
self.eql = eql
|
||||
|
||||
def get_all_episodes_with_podcast(self):
|
||||
episodes = [(podcast, episode) for podcast in
|
||||
self.podcast_list_model.get_podcasts()
|
||||
for episode in podcast.get_all_episodes()]
|
||||
|
||||
# FIXME: Filter using EQL
|
||||
|
||||
def sort_key(pair):
|
||||
podcast, episode = pair
|
||||
return model.Model.episode_sort_key(episode)
|
||||
|
||||
return sorted(episodes, key=sort_key, reverse=True)
|
||||
|
||||
def get_all_episodes(self):
|
||||
episodes = []
|
||||
for podcast in self.podcast_list_model.get_podcasts():
|
||||
|
|
Loading…
Reference in New Issue