enhancement on tags and player controls
This commit is contained in:
parent
ee716d6c8e
commit
a6f83b71c0
|
@ -331,8 +331,7 @@ abstract class PlaybackController(private val activity: FragmentActivity) {
|
|||
if (media is FeedMedia) {
|
||||
media.setPosition(time)
|
||||
DBWriter.setFeedItem(media.getItem())
|
||||
EventBus.getDefault().post(PlaybackPositionEvent(time,
|
||||
media.getDuration()))
|
||||
EventBus.getDefault().post(PlaybackPositionEvent(time, media.getDuration()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -358,7 +357,7 @@ abstract class PlaybackController(private val activity: FragmentActivity) {
|
|||
|
||||
val audioTracks: List<String>
|
||||
get() {
|
||||
if (playbackService == null || playbackService!!.audioTracks.isEmpty()) {
|
||||
if (playbackService?.audioTracks.isNullOrEmpty()) {
|
||||
return emptyList()
|
||||
}
|
||||
return playbackService!!.audioTracks.filterNotNull().map { it }
|
||||
|
|
|
@ -46,8 +46,8 @@ class ExoPlayerWrapper internal constructor(private val context: Context) {
|
|||
private val bufferingUpdateDisposable: Disposable
|
||||
private lateinit var exoPlayer: ExoPlayer
|
||||
private lateinit var trackSelector: DefaultTrackSelector
|
||||
private var loudnessEnhancer: LoudnessEnhancer? = null
|
||||
|
||||
private var loudnessEnhancer: LoudnessEnhancer? = null
|
||||
private var mediaSource: MediaSource? = null
|
||||
private var audioSeekCompleteListener: Runnable? = null
|
||||
private var audioCompletionListener: Runnable? = null
|
||||
|
@ -61,7 +61,7 @@ class ExoPlayerWrapper internal constructor(private val context: Context) {
|
|||
playbackParameters = exoPlayer.playbackParameters
|
||||
bufferingUpdateDisposable = Observable.interval(bufferUpdateInterval, TimeUnit.SECONDS)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe { tickNumber: Long? ->
|
||||
.subscribe {
|
||||
bufferingUpdateListener?.accept(exoPlayer.bufferedPercentage)
|
||||
}
|
||||
}
|
||||
|
@ -198,7 +198,7 @@ class ExoPlayerWrapper internal constructor(private val context: Context) {
|
|||
// .setUserAgent(ClientConfig.USER_AGENT);
|
||||
val httpDataSourceFactory =
|
||||
OkHttpDataSource.Factory(PodciniHttpClient.getHttpClient() as okhttp3.Call.Factory)
|
||||
.setUserAgent(ac.mdiq.podcini.util.config.ClientConfig.USER_AGENT)
|
||||
.setUserAgent(ClientConfig.USER_AGENT)
|
||||
|
||||
if (!user.isNullOrEmpty() && !password.isNullOrEmpty()) {
|
||||
val requestProperties = HashMap<String, String>()
|
||||
|
|
|
@ -134,7 +134,7 @@ class LocalPSMP(context: Context, callback: PSMPCallback) : PlaybackServiceMedia
|
|||
} else {
|
||||
// stop playback of this episode
|
||||
if (playerStatus == PlayerStatus.PAUSED || playerStatus == PlayerStatus.PLAYING || playerStatus == PlayerStatus.PREPARED) {
|
||||
mediaPlayer!!.stop()
|
||||
mediaPlayer?.stop()
|
||||
}
|
||||
// set temporarily to pause in order to update list with current position
|
||||
if (playerStatus == PlayerStatus.PLAYING) {
|
||||
|
@ -150,31 +150,32 @@ class LocalPSMP(context: Context, callback: PSMPCallback) : PlaybackServiceMedia
|
|||
}
|
||||
}
|
||||
|
||||
this.media = playable
|
||||
media = playable
|
||||
this.stream = stream
|
||||
this.mediaType = media!!.getMediaType()
|
||||
this.videoSize = null
|
||||
mediaType = media!!.getMediaType()
|
||||
videoSize = null
|
||||
createMediaPlayer()
|
||||
this@LocalPSMP.startWhenPrepared.set(startWhenPrepared)
|
||||
setPlayerStatus(PlayerStatus.INITIALIZING, media)
|
||||
try {
|
||||
callback.ensureMediaInfoLoaded(media!!)
|
||||
callback.onMediaChanged(false)
|
||||
// TODO: speed
|
||||
setPlaybackParams(PlaybackSpeedUtils.getCurrentPlaybackSpeed(media), UserPreferences.isSkipSilence)
|
||||
if (stream) {
|
||||
if (media!!.getStreamUrl() != null) {
|
||||
if (playable is FeedMedia && playable.getItem()?.feed?.preferences != null) {
|
||||
val preferences = playable.getItem()!!.feed!!.preferences!!
|
||||
mediaPlayer!!.setDataSource(
|
||||
mediaPlayer?.setDataSource(
|
||||
media!!.getStreamUrl()!!,
|
||||
preferences.username,
|
||||
preferences.password)
|
||||
} else {
|
||||
mediaPlayer!!.setDataSource(media!!.getStreamUrl()!!)
|
||||
mediaPlayer?.setDataSource(media!!.getStreamUrl()!!)
|
||||
}
|
||||
}
|
||||
} else if (media!!.getLocalMediaUrl() != null && File(media!!.getLocalMediaUrl()!!).canRead()) {
|
||||
mediaPlayer!!.setDataSource(media!!.getLocalMediaUrl()!!)
|
||||
mediaPlayer?.setDataSource(media!!.getLocalMediaUrl()!!)
|
||||
} else {
|
||||
throw IOException("Unable to read local file " + media!!.getLocalMediaUrl())
|
||||
}
|
||||
|
@ -185,7 +186,7 @@ class LocalPSMP(context: Context, callback: PSMPCallback) : PlaybackServiceMedia
|
|||
|
||||
if (prepareImmediately) {
|
||||
setPlayerStatus(PlayerStatus.PREPARING, media)
|
||||
mediaPlayer!!.prepare()
|
||||
mediaPlayer?.prepare()
|
||||
onPrepared(startWhenPrepared)
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
|
@ -214,17 +215,17 @@ class LocalPSMP(context: Context, callback: PSMPCallback) : PlaybackServiceMedia
|
|||
Log.d(TAG, "Audiofocus successfully requested")
|
||||
Log.d(TAG, "Resuming/Starting playback")
|
||||
acquireWifiLockIfNecessary()
|
||||
|
||||
// TODO: speed
|
||||
setPlaybackParams(PlaybackSpeedUtils.getCurrentPlaybackSpeed(media), UserPreferences.isSkipSilence)
|
||||
setVolume(1.0f, 1.0f)
|
||||
|
||||
if (playerStatus == PlayerStatus.PREPARED && media!!.getPosition() > 0) {
|
||||
if (media != null && playerStatus == PlayerStatus.PREPARED && media!!.getPosition() > 0) {
|
||||
val newPosition = RewindAfterPauseUtils.calculatePositionWithRewind(
|
||||
media!!.getPosition(),
|
||||
media!!.getLastPlayedTime())
|
||||
seekTo(newPosition)
|
||||
}
|
||||
mediaPlayer!!.start()
|
||||
mediaPlayer?.start()
|
||||
|
||||
setPlayerStatus(PlayerStatus.PLAYING, media)
|
||||
pausedBecauseOfTransientAudiofocusLoss = false
|
||||
|
@ -252,7 +253,7 @@ class LocalPSMP(context: Context, callback: PSMPCallback) : PlaybackServiceMedia
|
|||
releaseWifiLockIfNecessary()
|
||||
if (playerStatus == PlayerStatus.PLAYING) {
|
||||
Log.d(TAG, "Pausing playback.")
|
||||
mediaPlayer!!.pause()
|
||||
mediaPlayer?.pause()
|
||||
setPlayerStatus(PlayerStatus.PAUSED, media, getPosition())
|
||||
|
||||
if (abandonFocus) {
|
||||
|
@ -282,7 +283,7 @@ class LocalPSMP(context: Context, callback: PSMPCallback) : PlaybackServiceMedia
|
|||
if (playerStatus == PlayerStatus.INITIALIZED) {
|
||||
Log.d(TAG, "Preparing media player")
|
||||
setPlayerStatus(PlayerStatus.PREPARING, media)
|
||||
mediaPlayer!!.prepare()
|
||||
mediaPlayer?.prepare()
|
||||
onPrepared(startWhenPrepared.get())
|
||||
}
|
||||
}
|
||||
|
@ -294,18 +295,20 @@ class LocalPSMP(context: Context, callback: PSMPCallback) : PlaybackServiceMedia
|
|||
check(playerStatus == PlayerStatus.PREPARING) { "Player is not in PREPARING state" }
|
||||
Log.d(TAG, "Resource prepared")
|
||||
|
||||
if (mediaType == MediaType.VIDEO) {
|
||||
if (mediaPlayer != null && mediaType == MediaType.VIDEO) {
|
||||
videoSize = Pair(mediaPlayer!!.videoWidth, mediaPlayer!!.videoHeight)
|
||||
}
|
||||
|
||||
// TODO this call has no effect!
|
||||
if (media!!.getPosition() > 0) {
|
||||
seekTo(media!!.getPosition())
|
||||
}
|
||||
if (media != null) {
|
||||
// TODO this call has no effect!
|
||||
if (media!!.getPosition() > 0) {
|
||||
seekTo(media!!.getPosition())
|
||||
}
|
||||
|
||||
if (media!!.getDuration() <= 0) {
|
||||
Log.d(TAG, "Setting duration of media")
|
||||
media!!.setDuration(mediaPlayer!!.duration)
|
||||
if (media!!.getDuration() <= 0) {
|
||||
Log.d(TAG, "Setting duration of media")
|
||||
if (mediaPlayer != null) media!!.setDuration(mediaPlayer!!.duration)
|
||||
}
|
||||
}
|
||||
setPlayerStatus(PlayerStatus.PREPARED, media)
|
||||
|
||||
|
@ -362,9 +365,9 @@ class LocalPSMP(context: Context, callback: PSMPCallback) : PlaybackServiceMedia
|
|||
seekLatch = CountDownLatch(1)
|
||||
statusBeforeSeeking = playerStatus
|
||||
setPlayerStatus(PlayerStatus.SEEKING, media, getPosition())
|
||||
mediaPlayer!!.seekTo(t)
|
||||
mediaPlayer?.seekTo(t)
|
||||
if (statusBeforeSeeking == PlayerStatus.PREPARED) {
|
||||
media!!.setPosition(t)
|
||||
media?.setPosition(t)
|
||||
}
|
||||
try {
|
||||
seekLatch!!.await(3, TimeUnit.SECONDS)
|
||||
|
@ -372,7 +375,7 @@ class LocalPSMP(context: Context, callback: PSMPCallback) : PlaybackServiceMedia
|
|||
Log.e(TAG, Log.getStackTraceString(e))
|
||||
}
|
||||
} else if (playerStatus == PlayerStatus.INITIALIZED) {
|
||||
media!!.setPosition(t)
|
||||
media?.setPosition(t)
|
||||
startWhenPrepared.set(false)
|
||||
prepare()
|
||||
}
|
||||
|
@ -398,7 +401,7 @@ class LocalPSMP(context: Context, callback: PSMPCallback) : PlaybackServiceMedia
|
|||
override fun getDuration(): Int {
|
||||
var retVal = Playable.INVALID_TIME
|
||||
if (playerStatus == PlayerStatus.PLAYING || playerStatus == PlayerStatus.PAUSED || playerStatus == PlayerStatus.PREPARED) {
|
||||
retVal = mediaPlayer!!.duration
|
||||
if (mediaPlayer != null) retVal = mediaPlayer!!.duration
|
||||
}
|
||||
if (retVal <= 0 && media != null && media!!.getDuration() > 0) {
|
||||
retVal = media!!.getDuration()
|
||||
|
@ -412,7 +415,7 @@ class LocalPSMP(context: Context, callback: PSMPCallback) : PlaybackServiceMedia
|
|||
override fun getPosition(): Int {
|
||||
var retVal = Playable.INVALID_TIME
|
||||
if (playerStatus.isAtLeast(PlayerStatus.PREPARED)) {
|
||||
retVal = mediaPlayer!!.currentPosition
|
||||
if (mediaPlayer != null) retVal = mediaPlayer!!.currentPosition
|
||||
}
|
||||
if (retVal <= 0 && media != null && media!!.getPosition() >= 0) {
|
||||
retVal = media!!.getPosition()
|
||||
|
@ -435,7 +438,7 @@ class LocalPSMP(context: Context, callback: PSMPCallback) : PlaybackServiceMedia
|
|||
override fun setPlaybackParams(speed: Float, skipSilence: Boolean) {
|
||||
Log.d(TAG, "Playback speed was set to $speed")
|
||||
EventBus.getDefault().post(SpeedChangedEvent(speed))
|
||||
mediaPlayer!!.setPlaybackParams(speed, skipSilence)
|
||||
mediaPlayer?.setPlaybackParams(speed, skipSilence)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -443,8 +446,8 @@ class LocalPSMP(context: Context, callback: PSMPCallback) : PlaybackServiceMedia
|
|||
*/
|
||||
override fun getPlaybackSpeed(): Float {
|
||||
var retVal = 1f
|
||||
if ((playerStatus == PlayerStatus.PLAYING || playerStatus == PlayerStatus.PAUSED || playerStatus == PlayerStatus.INITIALIZED || playerStatus == PlayerStatus.PREPARED)) {
|
||||
retVal = mediaPlayer!!.currentSpeedMultiplier
|
||||
if (playerStatus == PlayerStatus.PLAYING || playerStatus == PlayerStatus.PAUSED || playerStatus == PlayerStatus.INITIALIZED || playerStatus == PlayerStatus.PREPARED) {
|
||||
if (mediaPlayer != null) retVal = mediaPlayer!!.currentSpeedMultiplier
|
||||
}
|
||||
return retVal
|
||||
}
|
||||
|
@ -466,7 +469,7 @@ class LocalPSMP(context: Context, callback: PSMPCallback) : PlaybackServiceMedia
|
|||
volumeRight *= adaptionFactor
|
||||
}
|
||||
}
|
||||
mediaPlayer!!.setVolume(volumeLeft, volumeRight)
|
||||
mediaPlayer?.setVolume(volumeLeft, volumeRight)
|
||||
Log.d(TAG, "Media player volume was set to $volumeLeft $volumeRight")
|
||||
}
|
||||
|
||||
|
@ -507,7 +510,7 @@ class LocalPSMP(context: Context, callback: PSMPCallback) : PlaybackServiceMedia
|
|||
override fun resetVideoSurface() {
|
||||
if (mediaType == MediaType.VIDEO) {
|
||||
Log.d(TAG, "Resetting video surface")
|
||||
mediaPlayer!!.setDisplay(null)
|
||||
mediaPlayer?.setDisplay(null)
|
||||
reinit()
|
||||
} else {
|
||||
Log.e(TAG, "Resetting video surface for media of Audio type")
|
||||
|
@ -543,21 +546,20 @@ class LocalPSMP(context: Context, callback: PSMPCallback) : PlaybackServiceMedia
|
|||
}
|
||||
|
||||
override fun getAudioTracks(): List<String> {
|
||||
return mediaPlayer!!.audioTracks
|
||||
return mediaPlayer?.audioTracks?: listOf()
|
||||
}
|
||||
|
||||
override fun setAudioTrack(track: Int) {
|
||||
mediaPlayer!!.setAudioTrack(track)
|
||||
if (mediaPlayer != null) mediaPlayer!!.setAudioTrack(track)
|
||||
}
|
||||
|
||||
override fun getSelectedAudioTrack(): Int {
|
||||
return mediaPlayer!!.selectedAudioTrack
|
||||
return mediaPlayer?.selectedAudioTrack?:0
|
||||
}
|
||||
|
||||
private fun createMediaPlayer() {
|
||||
if (mediaPlayer != null) {
|
||||
mediaPlayer!!.release()
|
||||
}
|
||||
mediaPlayer?.release()
|
||||
|
||||
if (media == null) {
|
||||
mediaPlayer = null
|
||||
playerStatus = PlayerStatus.STOPPED
|
||||
|
@ -573,50 +575,53 @@ class LocalPSMP(context: Context, callback: PSMPCallback) : PlaybackServiceMedia
|
|||
if (isShutDown) {
|
||||
return@OnAudioFocusChangeListener
|
||||
}
|
||||
if (!PlaybackService.isRunning) {
|
||||
abandonAudioFocus()
|
||||
Log.d(TAG, "onAudioFocusChange: PlaybackService is no longer running")
|
||||
return@OnAudioFocusChangeListener
|
||||
}
|
||||
if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
|
||||
Log.d(TAG, "Lost audio focus")
|
||||
pause(true, false)
|
||||
callback.shouldStop()
|
||||
} else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK
|
||||
&& !UserPreferences.shouldPauseForFocusLoss()) {
|
||||
if (playerStatus == PlayerStatus.PLAYING) {
|
||||
Log.d(TAG, "Lost audio focus temporarily. Ducking...")
|
||||
setVolume(0.25f, 0.25f)
|
||||
when {
|
||||
!PlaybackService.isRunning -> {
|
||||
abandonAudioFocus()
|
||||
Log.d(TAG, "onAudioFocusChange: PlaybackService is no longer running")
|
||||
return@OnAudioFocusChangeListener
|
||||
}
|
||||
focusChange == AudioManager.AUDIOFOCUS_LOSS -> {
|
||||
Log.d(TAG, "Lost audio focus")
|
||||
pause(true, false)
|
||||
callback.shouldStop()
|
||||
}
|
||||
focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK
|
||||
&& !UserPreferences.shouldPauseForFocusLoss() -> {
|
||||
if (playerStatus == PlayerStatus.PLAYING) {
|
||||
Log.d(TAG, "Lost audio focus temporarily. Ducking...")
|
||||
setVolume(0.25f, 0.25f)
|
||||
pausedBecauseOfTransientAudiofocusLoss = false
|
||||
}
|
||||
}
|
||||
focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT || focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> {
|
||||
if (playerStatus == PlayerStatus.PLAYING) {
|
||||
Log.d(TAG, "Lost audio focus temporarily. Pausing...")
|
||||
mediaPlayer?.pause() // Pause without telling the PlaybackService
|
||||
pausedBecauseOfTransientAudiofocusLoss = true
|
||||
|
||||
audioFocusCanceller.removeCallbacksAndMessages(null)
|
||||
audioFocusCanceller.postDelayed({
|
||||
if (pausedBecauseOfTransientAudiofocusLoss) {
|
||||
// Still did not get back the audio focus. Now actually pause.
|
||||
pause(true, false)
|
||||
}
|
||||
}, 30000)
|
||||
}
|
||||
}
|
||||
focusChange == AudioManager.AUDIOFOCUS_GAIN -> {
|
||||
Log.d(TAG, "Gained audio focus")
|
||||
audioFocusCanceller.removeCallbacksAndMessages(null)
|
||||
if (pausedBecauseOfTransientAudiofocusLoss) { // we paused => play now
|
||||
mediaPlayer?.start()
|
||||
} else { // we ducked => raise audio level back
|
||||
setVolume(1.0f, 1.0f)
|
||||
}
|
||||
pausedBecauseOfTransientAudiofocusLoss = false
|
||||
}
|
||||
} else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT
|
||||
|| focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) {
|
||||
if (playerStatus == PlayerStatus.PLAYING) {
|
||||
Log.d(TAG, "Lost audio focus temporarily. Pausing...")
|
||||
mediaPlayer!!.pause() // Pause without telling the PlaybackService
|
||||
pausedBecauseOfTransientAudiofocusLoss = true
|
||||
|
||||
audioFocusCanceller.removeCallbacksAndMessages(null)
|
||||
audioFocusCanceller.postDelayed({
|
||||
if (pausedBecauseOfTransientAudiofocusLoss) {
|
||||
// Still did not get back the audio focus. Now actually pause.
|
||||
pause(true, false)
|
||||
}
|
||||
}, 30000)
|
||||
}
|
||||
} else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
|
||||
Log.d(TAG, "Gained audio focus")
|
||||
audioFocusCanceller.removeCallbacksAndMessages(null)
|
||||
if (pausedBecauseOfTransientAudiofocusLoss) { // we paused => play now
|
||||
mediaPlayer!!.start()
|
||||
} else { // we ducked => raise audio level back
|
||||
setVolume(1.0f, 1.0f)
|
||||
}
|
||||
pausedBecauseOfTransientAudiofocusLoss = false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
init {
|
||||
mediaType = MediaType.UNKNOWN
|
||||
|
||||
|
@ -639,16 +644,12 @@ class LocalPSMP(context: Context, callback: PSMPCallback) : PlaybackServiceMedia
|
|||
val isPlaying = playerStatus == PlayerStatus.PLAYING
|
||||
|
||||
// we're relying on the position stored in the Playable object for post-playback processing
|
||||
if (media != null) {
|
||||
val position = getPosition()
|
||||
if (position >= 0) {
|
||||
media!!.setPosition(position)
|
||||
}
|
||||
val position = getPosition()
|
||||
if (position >= 0) {
|
||||
media?.setPosition(position)
|
||||
}
|
||||
|
||||
if (mediaPlayer != null) {
|
||||
mediaPlayer!!.reset()
|
||||
}
|
||||
mediaPlayer?.reset()
|
||||
|
||||
abandonAudioFocus()
|
||||
|
||||
|
@ -726,21 +727,21 @@ class LocalPSMP(context: Context, callback: PSMPCallback) : PlaybackServiceMedia
|
|||
}
|
||||
|
||||
private fun clearMediaPlayerListeners() {
|
||||
if (mediaPlayer == null) return
|
||||
mediaPlayer!!.setOnCompletionListener {}
|
||||
mediaPlayer!!.setOnSeekCompleteListener {}
|
||||
mediaPlayer!!.setOnBufferingUpdateListener { percent: Int? -> }
|
||||
mediaPlayer!!.setOnBufferingUpdateListener { }
|
||||
mediaPlayer!!.setOnErrorListener { x: String? -> }
|
||||
}
|
||||
|
||||
private fun genericSeekCompleteListener() {
|
||||
Log.d(TAG, "genericSeekCompleteListener")
|
||||
if (seekLatch != null) {
|
||||
seekLatch!!.countDown()
|
||||
}
|
||||
seekLatch?.countDown()
|
||||
|
||||
if (playerStatus == PlayerStatus.PLAYING) {
|
||||
callback.onPlaybackStart(media!!, getPosition())
|
||||
if (media != null) callback.onPlaybackStart(media!!, getPosition())
|
||||
}
|
||||
if (playerStatus == PlayerStatus.SEEKING) {
|
||||
if (playerStatus == PlayerStatus.SEEKING && statusBeforeSeeking != null) {
|
||||
setPlayerStatus(statusBeforeSeeking!!, media, getPosition())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1517,6 +1517,7 @@ class PlaybackService : MediaBrowserServiceCompat() {
|
|||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
@Suppress("unused")
|
||||
fun speedPresetChanged(event: SpeedPresetChangedEvent) {
|
||||
// TODO: speed
|
||||
if (playable is FeedMedia) {
|
||||
if ((playable as FeedMedia).getItem()?.feed?.id == event.feedId) {
|
||||
if (event.speed == FeedPreferences.SPEED_USE_GLOBAL) {
|
||||
|
|
|
@ -18,6 +18,7 @@ import ac.mdiq.podcini.storage.database.mapper.DownloadResultCursorMapper.conver
|
|||
import ac.mdiq.podcini.preferences.UserPreferences
|
||||
import ac.mdiq.podcini.preferences.UserPreferences.feedCounterSetting
|
||||
import ac.mdiq.podcini.preferences.UserPreferences.feedOrder
|
||||
import ac.mdiq.podcini.storage.model.feed.FeedPreferences.Companion.TAG_ROOT
|
||||
import java.util.*
|
||||
import kotlin.math.min
|
||||
|
||||
|
@ -60,7 +61,8 @@ object DBReader {
|
|||
fun updateFeedList(adapter: PodDBAdapter) {
|
||||
synchronized(feedListLock) {
|
||||
adapter.allFeedsCursor.use { cursor ->
|
||||
feeds = ArrayList(cursor.count)
|
||||
// feeds = ArrayList(cursor.count)
|
||||
feeds.clear()
|
||||
while (cursor.moveToNext()) {
|
||||
val feed = extractFeedFromCursorRow(cursor)
|
||||
feeds.add(feed)
|
||||
|
@ -70,14 +72,16 @@ object DBReader {
|
|||
}
|
||||
}
|
||||
|
||||
private fun buildTags() {
|
||||
fun buildTags() {
|
||||
val tagsSet = mutableSetOf<String>()
|
||||
for (feed in feeds) {
|
||||
for (tag in feed.preferences!!.getTags()) {
|
||||
tagsSet.add(tag)
|
||||
if (tag != TAG_ROOT) tagsSet.add(tag)
|
||||
}
|
||||
}
|
||||
tags = tagsSet.toMutableList()
|
||||
tags.clear()
|
||||
tags.addAll(tagsSet)
|
||||
tags.sort()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
|
@ -841,7 +845,8 @@ object DBReader {
|
|||
val feedCounters: Map<Long, Int> = adapter.getFeedCounters(feedCounterSetting)
|
||||
// getFeedList(adapter)
|
||||
|
||||
if (subscriptionsFilter != null) {
|
||||
// TODO:
|
||||
if (false || subscriptionsFilter != null) {
|
||||
feeds = subscriptionsFilter.filter(feeds, feedCounters as Map<Long?, Int>).toMutableList()
|
||||
}
|
||||
|
||||
|
|
|
@ -108,7 +108,7 @@ import java.util.concurrent.TimeUnit
|
|||
// Local feed
|
||||
val documentFile = DocumentFile.fromSingleUri(context, Uri.parse(media.getFile_url()))
|
||||
if (documentFile == null || !documentFile.exists() || !documentFile.delete()) {
|
||||
EventBus.getDefault().post(ac.mdiq.podcini.util.event.MessageEvent(context.getString(R.string.delete_local_failed)))
|
||||
EventBus.getDefault().post(MessageEvent(context.getString(R.string.delete_local_failed)))
|
||||
return false
|
||||
}
|
||||
media.setFile_url(null)
|
||||
|
@ -117,7 +117,7 @@ import java.util.concurrent.TimeUnit
|
|||
// delete downloaded media file
|
||||
val mediaFile = File(media.getFile_url()!!)
|
||||
if (mediaFile.exists() && !mediaFile.delete()) {
|
||||
val evt = ac.mdiq.podcini.util.event.MessageEvent(context.getString(R.string.delete_failed))
|
||||
val evt = MessageEvent(context.getString(R.string.delete_failed))
|
||||
EventBus.getDefault().post(evt)
|
||||
return false
|
||||
}
|
||||
|
@ -180,7 +180,7 @@ import java.util.concurrent.TimeUnit
|
|||
if (!feed.isLocalFeed && feed.download_url != null) {
|
||||
SynchronizationQueueSink.enqueueFeedRemovedIfSynchronizationIsActive(context, feed.download_url!!)
|
||||
}
|
||||
EventBus.getDefault().post(ac.mdiq.podcini.util.event.FeedListUpdateEvent(feed))
|
||||
EventBus.getDefault().post(FeedListUpdateEvent(feed))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -233,7 +233,7 @@ import java.util.concurrent.TimeUnit
|
|||
// we assume we also removed download log entries for the feed or its media files.
|
||||
// especially important if download or refresh failed, as the user should not be able
|
||||
// to retry these
|
||||
EventBus.getDefault().post(ac.mdiq.podcini.util.event.DownloadLogEvent.listUpdated())
|
||||
EventBus.getDefault().post(DownloadLogEvent.listUpdated())
|
||||
|
||||
val backupManager = BackupManager(context)
|
||||
backupManager.dataChanged()
|
||||
|
@ -261,7 +261,7 @@ import java.util.concurrent.TimeUnit
|
|||
adapter.open()
|
||||
adapter.clearDownloadLog()
|
||||
adapter.close()
|
||||
EventBus.getDefault().post(ac.mdiq.podcini.util.event.DownloadLogEvent.listUpdated())
|
||||
EventBus.getDefault().post(DownloadLogEvent.listUpdated())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -309,7 +309,7 @@ import java.util.concurrent.TimeUnit
|
|||
adapter.open()
|
||||
adapter.setDownloadStatus(status!!)
|
||||
adapter.close()
|
||||
EventBus.getDefault().post(ac.mdiq.podcini.util.event.DownloadLogEvent.listUpdated())
|
||||
EventBus.getDefault().post(DownloadLogEvent.listUpdated())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -406,11 +406,11 @@ import java.util.concurrent.TimeUnit
|
|||
|
||||
var queueModified = false
|
||||
val markAsUnplayedIds = LongList()
|
||||
val events: MutableList<ac.mdiq.podcini.util.event.QueueEvent> = ArrayList()
|
||||
val events: MutableList<QueueEvent> = ArrayList()
|
||||
val updatedItems: MutableList<FeedItem> = ArrayList()
|
||||
val positionCalculator =
|
||||
ItemEnqueuePositionCalculator(enqueueLocation)
|
||||
val currentlyPlaying = createInstanceFromPreferences(context!!)
|
||||
val currentlyPlaying = createInstanceFromPreferences(context)
|
||||
var insertPosition = positionCalculator.calcPosition(queue, currentlyPlaying)
|
||||
for (itemId in itemIds) {
|
||||
if (!itemListContains(queue, itemId)) {
|
||||
|
@ -454,7 +454,7 @@ import java.util.concurrent.TimeUnit
|
|||
* @param queue The queue to be sorted.
|
||||
* @param events Replaces the events by a single SORT event if the list has to be sorted automatically.
|
||||
*/
|
||||
private fun applySortOrder(queue: MutableList<FeedItem>, events: MutableList<ac.mdiq.podcini.util.event.QueueEvent>) {
|
||||
private fun applySortOrder(queue: MutableList<FeedItem>, events: MutableList<QueueEvent>) {
|
||||
if (!isQueueKeepSorted) {
|
||||
// queue is not in keep sorted mode, there's nothing to do
|
||||
return
|
||||
|
@ -471,7 +471,7 @@ import java.util.concurrent.TimeUnit
|
|||
|
||||
// Replace ADDED events by a single SORTED event
|
||||
events.clear()
|
||||
events.add(ac.mdiq.podcini.util.event.QueueEvent.sorted(queue))
|
||||
events.add(QueueEvent.sorted(queue))
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -521,7 +521,7 @@ import java.util.concurrent.TimeUnit
|
|||
val queue = getQueue(adapter).toMutableList()
|
||||
|
||||
var queueModified = false
|
||||
val events: MutableList<ac.mdiq.podcini.util.event.QueueEvent> = ArrayList()
|
||||
val events: MutableList<QueueEvent> = ArrayList()
|
||||
val updatedItems: MutableList<FeedItem> = ArrayList()
|
||||
for (itemId in itemIds) {
|
||||
val position = indexInItemList(queue, itemId)
|
||||
|
@ -712,7 +712,7 @@ import java.util.concurrent.TimeUnit
|
|||
adapter.setFeedItemRead(played, *itemIds)
|
||||
adapter.close()
|
||||
if (broadcastUpdate) {
|
||||
EventBus.getDefault().post(ac.mdiq.podcini.util.event.UnreadItemsUpdateEvent())
|
||||
EventBus.getDefault().post(UnreadItemsUpdateEvent())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -741,7 +741,7 @@ import java.util.concurrent.TimeUnit
|
|||
adapter.setFeedItemRead(played, itemId, mediaId,
|
||||
resetMediaPosition)
|
||||
adapter.close()
|
||||
EventBus.getDefault().post(ac.mdiq.podcini.util.event.UnreadItemsUpdateEvent())
|
||||
EventBus.getDefault().post(UnreadItemsUpdateEvent())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -756,7 +756,7 @@ import java.util.concurrent.TimeUnit
|
|||
adapter.open()
|
||||
adapter.setFeedItems(FeedItem.NEW, FeedItem.UNPLAYED, feedId)
|
||||
adapter.close()
|
||||
EventBus.getDefault().post(ac.mdiq.podcini.util.event.UnreadItemsUpdateEvent())
|
||||
EventBus.getDefault().post(UnreadItemsUpdateEvent())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -770,7 +770,7 @@ import java.util.concurrent.TimeUnit
|
|||
adapter.open()
|
||||
adapter.setFeedItems(FeedItem.NEW, FeedItem.UNPLAYED)
|
||||
adapter.close()
|
||||
EventBus.getDefault().post(ac.mdiq.podcini.util.event.UnreadItemsUpdateEvent())
|
||||
EventBus.getDefault().post(UnreadItemsUpdateEvent())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -882,7 +882,7 @@ import java.util.concurrent.TimeUnit
|
|||
adapter.open()
|
||||
adapter.setFeedPreferences(preferences)
|
||||
adapter.close()
|
||||
EventBus.getDefault().post(ac.mdiq.podcini.util.event.FeedListUpdateEvent(preferences.feedID))
|
||||
EventBus.getDefault().post(FeedListUpdateEvent(preferences.feedID))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -913,7 +913,7 @@ import java.util.concurrent.TimeUnit
|
|||
adapter.open()
|
||||
adapter.setFeedLastUpdateFailed(feedId, lastUpdateFailed)
|
||||
adapter.close()
|
||||
EventBus.getDefault().post(ac.mdiq.podcini.util.event.FeedListUpdateEvent(feedId))
|
||||
EventBus.getDefault().post(FeedListUpdateEvent(feedId))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -923,7 +923,7 @@ import java.util.concurrent.TimeUnit
|
|||
adapter.open()
|
||||
adapter.setFeedCustomTitle(feed.id, feed.getCustomTitle())
|
||||
adapter.close()
|
||||
EventBus.getDefault().post(ac.mdiq.podcini.util.event.FeedListUpdateEvent(feed))
|
||||
EventBus.getDefault().post(FeedListUpdateEvent(feed))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -948,7 +948,7 @@ import java.util.concurrent.TimeUnit
|
|||
permutor.reorder(queue)
|
||||
adapter.setQueue(queue)
|
||||
if (broadcastUpdate) {
|
||||
EventBus.getDefault().post(ac.mdiq.podcini.util.event.QueueEvent.sorted(queue))
|
||||
EventBus.getDefault().post(QueueEvent.sorted(queue))
|
||||
}
|
||||
adapter.close()
|
||||
}
|
||||
|
|
|
@ -40,8 +40,7 @@ class FeedMedia : FeedFile, Playable {
|
|||
var itemId: Long = 0
|
||||
private set
|
||||
|
||||
constructor(i: FeedItem?, download_url: String?, size: Long,
|
||||
mime_type: String?
|
||||
constructor(i: FeedItem?, download_url: String?, size: Long, mime_type: String?
|
||||
) : super(null, download_url, false) {
|
||||
this.item = i
|
||||
this.size = size
|
||||
|
@ -268,10 +267,7 @@ class FeedMedia : FeedFile, Playable {
|
|||
}
|
||||
|
||||
override fun getChapters(): List<Chapter> {
|
||||
if (item?.chapters == null) {
|
||||
return listOf()
|
||||
}
|
||||
return item!!.chapters!!
|
||||
return item?.chapters?:listOf()
|
||||
}
|
||||
|
||||
override fun chaptersLoaded(): Boolean {
|
||||
|
@ -279,17 +275,11 @@ class FeedMedia : FeedFile, Playable {
|
|||
}
|
||||
|
||||
override fun getWebsiteLink(): String? {
|
||||
if (item == null) {
|
||||
return null
|
||||
}
|
||||
return item!!.link
|
||||
return item?.link
|
||||
}
|
||||
|
||||
override fun getFeedTitle(): String {
|
||||
if (item?.feed?.title == null) {
|
||||
return ""
|
||||
}
|
||||
return item!!.feed!!.title!!
|
||||
return item?.feed?.title?:""
|
||||
}
|
||||
|
||||
override fun getIdentifier(): Any {
|
||||
|
|
|
@ -547,9 +547,8 @@ class MainActivity : CastEnabledActivity() {
|
|||
intent.hasExtra(MainActivityStarter.EXTRA_FRAGMENT_TAG) -> {
|
||||
val tag = intent.getStringExtra(MainActivityStarter.EXTRA_FRAGMENT_TAG)
|
||||
val args = intent.getBundleExtra(MainActivityStarter.EXTRA_FRAGMENT_ARGS)
|
||||
if (tag != null) {
|
||||
loadFragment(tag, args)
|
||||
}
|
||||
if (tag != null) loadFragment(tag, args)
|
||||
|
||||
bottomSheet.setState(BottomSheetBehavior.STATE_COLLAPSED)
|
||||
}
|
||||
intent.getBooleanExtra(MainActivityStarter.EXTRA_OPEN_PLAYER, false) -> {
|
||||
|
|
|
@ -68,17 +68,13 @@ class SelectSubscriptionActivity : AppCompatActivity() {
|
|||
}
|
||||
}
|
||||
|
||||
fun getFeedItems(items: List<NavDrawerData.DrawerItem?>, result: MutableList<Feed>): List<Feed> {
|
||||
fun getFeedItems(items: List<NavDrawerData.FeedDrawerItem?>, result: MutableList<Feed>): List<Feed> {
|
||||
for (item in items) {
|
||||
if (item == null) continue
|
||||
// if (item.type == NavDrawerData.DrawerItem.Type.TAG) {
|
||||
// getFeedItems((item as NavDrawerData.TagDrawerItem).children, result)
|
||||
// } else {
|
||||
val feed: Feed = (item as NavDrawerData.FeedDrawerItem).feed
|
||||
val feed: Feed = item.feed
|
||||
if (!result.contains(feed)) {
|
||||
result.add(feed)
|
||||
}
|
||||
// }
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@ import ac.mdiq.podcini.storage.NavDrawerData
|
|||
import ac.mdiq.podcini.storage.model.feed.Feed
|
||||
import ac.mdiq.podcini.ui.activity.MainActivity
|
||||
import ac.mdiq.podcini.ui.fragment.FeedItemlistFragment
|
||||
import ac.mdiq.podcini.ui.fragment.SubscriptionFragment
|
||||
import android.content.Context
|
||||
import android.graphics.Rect
|
||||
import android.graphics.drawable.Drawable
|
||||
|
@ -30,8 +29,8 @@ open class SubscriptionsRecyclerAdapter(mainActivity: MainActivity) :
|
|||
View.OnCreateContextMenuListener {
|
||||
|
||||
private val mainActivityRef: WeakReference<MainActivity> = WeakReference<MainActivity>(mainActivity)
|
||||
private var listItems: List<NavDrawerData.DrawerItem>
|
||||
private var selectedItem: NavDrawerData.DrawerItem? = null
|
||||
private var listItems: List<NavDrawerData.FeedDrawerItem>
|
||||
private var selectedItem: NavDrawerData.FeedDrawerItem? = null
|
||||
private var longPressedPosition: Int = 0 // used to init actionMode
|
||||
|
||||
init {
|
||||
|
@ -43,7 +42,7 @@ open class SubscriptionsRecyclerAdapter(mainActivity: MainActivity) :
|
|||
return listItems[position]
|
||||
}
|
||||
|
||||
fun getSelectedItem(): NavDrawerData.DrawerItem? {
|
||||
fun getSelectedItem(): NavDrawerData.FeedDrawerItem? {
|
||||
return selectedItem
|
||||
}
|
||||
|
||||
|
@ -53,15 +52,13 @@ open class SubscriptionsRecyclerAdapter(mainActivity: MainActivity) :
|
|||
}
|
||||
|
||||
@UnstableApi override fun onBindViewHolder(holder: SubscriptionViewHolder, position: Int) {
|
||||
val drawerItem: NavDrawerData.DrawerItem = listItems[position]
|
||||
val isFeed = drawerItem.type == NavDrawerData.DrawerItem.Type.FEED
|
||||
val drawerItem: NavDrawerData.FeedDrawerItem = listItems[position]
|
||||
holder.bind(drawerItem)
|
||||
holder.itemView.setOnCreateContextMenuListener(this)
|
||||
if (inActionMode()) {
|
||||
if (isFeed) {
|
||||
holder.selectCheckbox.visibility = View.VISIBLE
|
||||
holder.selectView.visibility = View.VISIBLE
|
||||
}
|
||||
holder.selectCheckbox.visibility = View.VISIBLE
|
||||
holder.selectView.visibility = View.VISIBLE
|
||||
|
||||
holder.selectCheckbox.setChecked((isSelected(position)))
|
||||
holder.selectCheckbox.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
|
||||
setSelected(holder.bindingAdapterPosition,
|
||||
|
@ -76,9 +73,7 @@ open class SubscriptionsRecyclerAdapter(mainActivity: MainActivity) :
|
|||
|
||||
holder.itemView.setOnLongClickListener {
|
||||
if (!inActionMode()) {
|
||||
if (isFeed) {
|
||||
longPressedPosition = holder.bindingAdapterPosition
|
||||
}
|
||||
longPressedPosition = holder.bindingAdapterPosition
|
||||
selectedItem = drawerItem
|
||||
}
|
||||
false
|
||||
|
@ -89,9 +84,7 @@ open class SubscriptionsRecyclerAdapter(mainActivity: MainActivity) :
|
|||
if (e.isFromSource(InputDevice.SOURCE_MOUSE)
|
||||
&& e.buttonState == MotionEvent.BUTTON_SECONDARY) {
|
||||
if (!inActionMode()) {
|
||||
if (isFeed) {
|
||||
longPressedPosition = holder.bindingAdapterPosition
|
||||
}
|
||||
longPressedPosition = holder.bindingAdapterPosition
|
||||
selectedItem = drawerItem
|
||||
}
|
||||
}
|
||||
|
@ -99,16 +92,11 @@ open class SubscriptionsRecyclerAdapter(mainActivity: MainActivity) :
|
|||
false
|
||||
}
|
||||
holder.itemView.setOnClickListener {
|
||||
if (isFeed) {
|
||||
if (inActionMode()) {
|
||||
holder.selectCheckbox.setChecked(!isSelected(holder.bindingAdapterPosition))
|
||||
} else {
|
||||
val fragment: Fragment = FeedItemlistFragment
|
||||
.newInstance((drawerItem as NavDrawerData.FeedDrawerItem).feed.id)
|
||||
mainActivityRef.get()?.loadChildFragment(fragment)
|
||||
}
|
||||
} else if (!inActionMode()) {
|
||||
val fragment: Fragment = SubscriptionFragment.newInstance(drawerItem.title)
|
||||
if (inActionMode()) {
|
||||
holder.selectCheckbox.setChecked(!isSelected(holder.bindingAdapterPosition))
|
||||
} else {
|
||||
val fragment: Fragment = FeedItemlistFragment
|
||||
.newInstance(drawerItem.feed.id)
|
||||
mainActivityRef.get()?.loadChildFragment(fragment)
|
||||
}
|
||||
}
|
||||
|
@ -130,12 +118,8 @@ open class SubscriptionsRecyclerAdapter(mainActivity: MainActivity) :
|
|||
return
|
||||
}
|
||||
val inflater: MenuInflater = mainActivityRef.get()!!.menuInflater
|
||||
if (selectedItem?.type == NavDrawerData.DrawerItem.Type.FEED) {
|
||||
inflater.inflate(R.menu.nav_feed_context, menu)
|
||||
menu.findItem(R.id.multi_select).setVisible(true)
|
||||
} else {
|
||||
inflater.inflate(R.menu.nav_folder_context, menu)
|
||||
}
|
||||
inflater.inflate(R.menu.nav_feed_context, menu)
|
||||
menu.findItem(R.id.multi_select).setVisible(true)
|
||||
menu.setHeaderTitle(selectedItem?.title)
|
||||
}
|
||||
|
||||
|
@ -152,17 +136,15 @@ open class SubscriptionsRecyclerAdapter(mainActivity: MainActivity) :
|
|||
val items = ArrayList<Feed>()
|
||||
for (i in 0 until itemCount) {
|
||||
if (isSelected(i)) {
|
||||
val drawerItem: NavDrawerData.DrawerItem = listItems[i]
|
||||
if (drawerItem.type == NavDrawerData.DrawerItem.Type.FEED) {
|
||||
val feed: Feed = (drawerItem as NavDrawerData.FeedDrawerItem).feed
|
||||
items.add(feed)
|
||||
}
|
||||
val drawerItem: NavDrawerData.FeedDrawerItem = listItems[i]
|
||||
val feed: Feed = drawerItem.feed
|
||||
items.add(feed)
|
||||
}
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
fun setItems(listItems: List<NavDrawerData.DrawerItem>) {
|
||||
fun setItems(listItems: List<NavDrawerData.FeedDrawerItem>) {
|
||||
this.listItems = listItems
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
@ -180,28 +162,24 @@ open class SubscriptionsRecyclerAdapter(mainActivity: MainActivity) :
|
|||
|
||||
private val errorIcon: View = binding.errorIcon
|
||||
|
||||
fun bind(drawerItem: NavDrawerData.DrawerItem) {
|
||||
fun bind(drawerItem: NavDrawerData.FeedDrawerItem) {
|
||||
val drawable: Drawable? = AppCompatResources.getDrawable(selectView.context, R.drawable.ic_checkbox_background)
|
||||
selectView.background = drawable // Setting this in XML crashes API <= 21
|
||||
title.text = drawerItem.title
|
||||
producer.text = drawerItem.producer
|
||||
coverImage.contentDescription = drawerItem.title
|
||||
if (drawerItem.counter > 0) {
|
||||
count.text = NumberFormat.getInstance().format(drawerItem.counter.toLong()) + " episodes"
|
||||
count.text = NumberFormat.getInstance().format(drawerItem.feed.items.size.toLong()) + " episodes"
|
||||
count.visibility = View.VISIBLE
|
||||
} else {
|
||||
count.visibility = View.GONE
|
||||
}
|
||||
|
||||
val coverLoader = CoverLoader(mainActivityRef.get()!!)
|
||||
if (drawerItem.type == NavDrawerData.DrawerItem.Type.FEED) {
|
||||
val feed: Feed = (drawerItem as NavDrawerData.FeedDrawerItem).feed
|
||||
coverLoader.withUri(feed.imageUrl)
|
||||
errorIcon.visibility = if (feed.hasLastUpdateFailed()) View.VISIBLE else View.GONE
|
||||
} else {
|
||||
coverLoader.withResource(R.drawable.ic_tag)
|
||||
errorIcon.visibility = View.GONE
|
||||
}
|
||||
val feed: Feed = drawerItem.feed
|
||||
coverLoader.withUri(feed.imageUrl)
|
||||
errorIcon.visibility = if (feed.hasLastUpdateFailed()) View.VISIBLE else View.GONE
|
||||
|
||||
coverLoader.withCoverView(coverImage)
|
||||
coverLoader.load()
|
||||
|
||||
|
|
|
@ -1,27 +1,27 @@
|
|||
package ac.mdiq.podcini.ui.dialog
|
||||
|
||||
import ac.mdiq.podcini.R
|
||||
import ac.mdiq.podcini.databinding.EditTagsDialogBinding
|
||||
import ac.mdiq.podcini.storage.DBReader
|
||||
import ac.mdiq.podcini.storage.DBWriter
|
||||
import ac.mdiq.podcini.storage.model.feed.FeedPreferences
|
||||
import ac.mdiq.podcini.ui.adapter.SimpleChipAdapter
|
||||
import ac.mdiq.podcini.ui.view.ItemOffsetDecoration
|
||||
import android.app.Dialog
|
||||
import android.content.DialogInterface
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.widget.ArrayAdapter
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import ac.mdiq.podcini.R
|
||||
import ac.mdiq.podcini.ui.adapter.SimpleChipAdapter
|
||||
import ac.mdiq.podcini.storage.DBReader
|
||||
import ac.mdiq.podcini.storage.DBWriter
|
||||
import ac.mdiq.podcini.storage.NavDrawerData.DrawerItem
|
||||
import ac.mdiq.podcini.databinding.EditTagsDialogBinding
|
||||
import ac.mdiq.podcini.storage.model.feed.FeedPreferences
|
||||
import ac.mdiq.podcini.ui.view.ItemOffsetDecoration
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import java.io.Serializable
|
||||
|
||||
class TagSettingsDialog : DialogFragment() {
|
||||
private lateinit var displayedTags: MutableList<String>
|
||||
|
@ -29,10 +29,10 @@ class TagSettingsDialog : DialogFragment() {
|
|||
private lateinit var adapter: SimpleChipAdapter
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val feedPreferencesList =
|
||||
requireArguments().getSerializable(ARG_FEED_PREFERENCES) as? ArrayList<FeedPreferences>
|
||||
val commonTags: MutableSet<String> = HashSet(
|
||||
feedPreferencesList!![0].getTags())
|
||||
val serializedData: Serializable? = requireArguments().getSerializable(ARG_FEED_PREFERENCES)
|
||||
val feedPreferencesList = if (serializedData is ArrayList<*>) serializedData.filterIsInstance<FeedPreferences>() else listOf()
|
||||
// val feedPreferencesList = serializedData as? List<FeedPreferences> ?: listOf()
|
||||
val commonTags: MutableSet<String> = if (feedPreferencesList.isEmpty()) mutableSetOf() else HashSet(feedPreferencesList[0].getTags())
|
||||
|
||||
for (preference in feedPreferencesList) {
|
||||
commonTags.retainAll(preference.getTags())
|
||||
|
@ -54,11 +54,10 @@ class TagSettingsDialog : DialogFragment() {
|
|||
}
|
||||
}
|
||||
viewBinding.tagsRecycler.adapter = adapter
|
||||
viewBinding.rootFolderCheckbox.isChecked = commonTags.contains(FeedPreferences.TAG_ROOT)
|
||||
// viewBinding.rootFolderCheckbox.isChecked = commonTags.contains(FeedPreferences.TAG_ROOT)
|
||||
|
||||
viewBinding.newTagTextInput.setEndIconOnClickListener {
|
||||
addTag(
|
||||
viewBinding.newTagEditText.text.toString().trim { it <= ' ' })
|
||||
addTag(viewBinding.newTagEditText.text.toString().trim { it <= ' ' })
|
||||
}
|
||||
|
||||
loadTags()
|
||||
|
@ -79,30 +78,21 @@ class TagSettingsDialog : DialogFragment() {
|
|||
dialog.setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int ->
|
||||
addTag(viewBinding.newTagEditText.text.toString().trim { it <= ' ' })
|
||||
updatePreferencesTags(feedPreferencesList, commonTags)
|
||||
DBReader.buildTags()
|
||||
}
|
||||
dialog.setNegativeButton(R.string.cancel_label, null)
|
||||
return dialog.create()
|
||||
}
|
||||
|
||||
private fun loadTags() {
|
||||
Observable.fromCallable<List<String>> {
|
||||
// val data = DBReader.getNavDrawerData(null)
|
||||
// val items = data.items
|
||||
val folders: MutableList<String> = ArrayList()
|
||||
// for (item in items) {
|
||||
// if (item.type == DrawerItem.Type.TAG) {
|
||||
// if (item.title != null) folders.add(item.title!!)
|
||||
// }
|
||||
// }
|
||||
folders
|
||||
Observable.fromCallable {
|
||||
DBReader.getTags()
|
||||
}
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{ result: List<String> ->
|
||||
val acAdapter = ArrayAdapter(
|
||||
requireContext(),
|
||||
R.layout.single_tag_text_view, result)
|
||||
val acAdapter = ArrayAdapter(requireContext(), R.layout.single_tag_text_view, result)
|
||||
viewBinding.newTagEditText.setAdapter(acAdapter)
|
||||
}, { error: Throwable? ->
|
||||
Log.e(TAG, Log.getStackTraceString(error))
|
||||
|
@ -118,11 +108,11 @@ class TagSettingsDialog : DialogFragment() {
|
|||
adapter.notifyDataSetChanged()
|
||||
}
|
||||
|
||||
@OptIn(UnstableApi::class) private fun updatePreferencesTags(feedPreferencesList: List<FeedPreferences>?, commonTags: Set<String?>) {
|
||||
if (viewBinding.rootFolderCheckbox.isChecked) {
|
||||
displayedTags.add(FeedPreferences.TAG_ROOT)
|
||||
}
|
||||
for (preferences in feedPreferencesList!!) {
|
||||
@OptIn(UnstableApi::class) private fun updatePreferencesTags(feedPreferencesList: List<FeedPreferences>, commonTags: Set<String>) {
|
||||
// if (viewBinding.rootFolderCheckbox.isChecked) {
|
||||
// displayedTags.add(FeedPreferences.TAG_ROOT)
|
||||
// }
|
||||
for (preferences in feedPreferencesList) {
|
||||
preferences.getTags().removeAll(commonTags)
|
||||
preferences.getTags().addAll(displayedTags)
|
||||
DBWriter.setFeedPreferences(preferences)
|
||||
|
|
|
@ -72,8 +72,8 @@ open class VariableSpeedDialog : BottomSheetDialogFragment() {
|
|||
val binding = SpeedSelectDialogBinding.inflate(inflater)
|
||||
// val root = View.inflate(context, R.layout.speed_select_dialog, null)
|
||||
speedSeekBar = binding.speedSeekBar
|
||||
speedSeekBar.setProgressChangedListener { multiplier: Float? ->
|
||||
controller?.setPlaybackSpeed(multiplier!!)
|
||||
speedSeekBar.setProgressChangedListener { multiplier: Float ->
|
||||
controller?.setPlaybackSpeed(multiplier)
|
||||
}
|
||||
val selectedSpeedsGrid = binding.selectedSpeedsGrid
|
||||
selectedSpeedsGrid.layoutManager = GridLayoutManager(context, 3)
|
||||
|
|
|
@ -44,6 +44,9 @@ import ac.mdiq.podcini.preferences.UserPreferences
|
|||
import ac.mdiq.podcini.ui.common.PlaybackSpeedIndicatorView
|
||||
import ac.mdiq.podcini.ui.view.ChapterSeekBar
|
||||
import ac.mdiq.podcini.ui.view.PlayButton
|
||||
import ac.mdiq.podcini.util.Converter
|
||||
import ac.mdiq.podcini.util.event.PlayerErrorEvent
|
||||
import ac.mdiq.podcini.util.event.UnreadItemsUpdateEvent
|
||||
import io.reactivex.Maybe
|
||||
import io.reactivex.MaybeEmitter
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
|
@ -105,7 +108,7 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
|
|||
toolbar = viewBinding.toolbar
|
||||
toolbar.title = ""
|
||||
toolbar.setNavigationOnClickListener {
|
||||
(activity as MainActivity).bottomSheet.setState(BottomSheetBehavior.STATE_COLLAPSED)
|
||||
(activity as MainActivity).bottomSheet.setState(BottomSheetBehavior.STATE_EXPANDED)
|
||||
}
|
||||
toolbar.setOnMenuItemClickListener(this)
|
||||
|
||||
|
@ -136,8 +139,7 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
|
|||
setupLengthTextView()
|
||||
setupControlButtons()
|
||||
butPlaybackSpeed.setOnClickListener {
|
||||
VariableSpeedDialog().show(
|
||||
childFragmentManager, null)
|
||||
VariableSpeedDialog().show(childFragmentManager, null)
|
||||
}
|
||||
sbPosition.setOnSeekBarChangeListener(this)
|
||||
|
||||
|
@ -204,10 +206,8 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
|
|||
true
|
||||
}
|
||||
butPlay.setOnClickListener {
|
||||
if (controller != null) {
|
||||
controller!!.init()
|
||||
controller!!.playPause()
|
||||
}
|
||||
controller?.init()
|
||||
controller?.playPause()
|
||||
}
|
||||
butFF.setOnClickListener {
|
||||
if (controller != null) {
|
||||
|
@ -227,18 +227,17 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
|
|||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onUnreadItemsUpdate(event: ac.mdiq.podcini.util.event.UnreadItemsUpdateEvent?) {
|
||||
fun onUnreadItemsUpdate(event: UnreadItemsUpdateEvent?) {
|
||||
if (controller == null) {
|
||||
return
|
||||
}
|
||||
updatePosition(PlaybackPositionEvent(controller!!.position,
|
||||
controller!!.duration))
|
||||
updatePosition(PlaybackPositionEvent(controller!!.position, controller!!.duration))
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onPlaybackServiceChanged(event: PlaybackServiceEvent) {
|
||||
if (event.action == PlaybackServiceEvent.Action.SERVICE_SHUT_DOWN) {
|
||||
(activity as MainActivity).bottomSheet.state = BottomSheetBehavior.STATE_COLLAPSED
|
||||
(activity as MainActivity).bottomSheet.state = BottomSheetBehavior.STATE_EXPANDED
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -264,7 +263,6 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
|
|||
|
||||
private fun loadMediaInfo(includingChapters: Boolean) {
|
||||
disposable?.dispose()
|
||||
|
||||
disposable = Maybe.create<Playable> { emitter: MaybeEmitter<Playable?> ->
|
||||
val media: Playable? = controller?.getMedia()
|
||||
if (media != null) {
|
||||
|
@ -298,7 +296,7 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
|
|||
}
|
||||
|
||||
override fun onPlaybackEnd() {
|
||||
(activity as MainActivity).bottomSheet.state = BottomSheetBehavior.STATE_COLLAPSED
|
||||
(activity as MainActivity).bottomSheet.state = BottomSheetBehavior.STATE_EXPANDED
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -308,10 +306,8 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
|
|||
if (media == null) {
|
||||
return
|
||||
}
|
||||
updatePosition(PlaybackPositionEvent(media.getPosition(),
|
||||
media.getDuration()))
|
||||
updatePlaybackSpeedButton(SpeedChangedEvent(PlaybackSpeedUtils.getCurrentPlaybackSpeed(
|
||||
media)))
|
||||
updatePosition(PlaybackPositionEvent(media.getPosition(), media.getDuration()))
|
||||
updatePlaybackSpeedButton(SpeedChangedEvent(PlaybackSpeedUtils.getCurrentPlaybackSpeed(media)))
|
||||
setChapterDividers(media)
|
||||
setupOptionsMenu(media)
|
||||
}
|
||||
|
@ -370,18 +366,18 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
|
|||
Log.w(TAG, "Could not react to position observer update because of invalid time")
|
||||
return
|
||||
}
|
||||
txtvPosition.text = ac.mdiq.podcini.util.Converter.getDurationStringLong(currentPosition)
|
||||
txtvPosition.text = Converter.getDurationStringLong(currentPosition)
|
||||
txtvPosition.setContentDescription(getString(R.string.position,
|
||||
ac.mdiq.podcini.util.Converter.getDurationStringLocalized(requireContext(), currentPosition.toLong())))
|
||||
Converter.getDurationStringLocalized(requireContext(), currentPosition.toLong())))
|
||||
showTimeLeft = UserPreferences.shouldShowRemainingTime()
|
||||
if (showTimeLeft) {
|
||||
txtvLength.setContentDescription(getString(R.string.remaining_time,
|
||||
ac.mdiq.podcini.util.Converter.getDurationStringLocalized(requireContext(), remainingTime.toLong())))
|
||||
txtvLength.text = (if ((remainingTime > 0)) "-" else "") + ac.mdiq.podcini.util.Converter.getDurationStringLong(remainingTime)
|
||||
Converter.getDurationStringLocalized(requireContext(), remainingTime.toLong())))
|
||||
txtvLength.text = (if ((remainingTime > 0)) "-" else "") + Converter.getDurationStringLong(remainingTime)
|
||||
} else {
|
||||
txtvLength.setContentDescription(getString(R.string.chapter_duration,
|
||||
ac.mdiq.podcini.util.Converter.getDurationStringLocalized(requireContext(), duration.toLong())))
|
||||
txtvLength.text = ac.mdiq.podcini.util.Converter.getDurationStringLong(duration)
|
||||
Converter.getDurationStringLocalized(requireContext(), duration.toLong())))
|
||||
txtvLength.text = Converter.getDurationStringLong(duration)
|
||||
}
|
||||
|
||||
if (!sbPosition.isPressed) {
|
||||
|
@ -396,7 +392,7 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
|
|||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun mediaPlayerError(event: ac.mdiq.podcini.util.event.PlayerErrorEvent) {
|
||||
fun mediaPlayerError(event: PlayerErrorEvent) {
|
||||
MediaPlayerErrorDialog.show(activity as Activity, event)
|
||||
}
|
||||
|
||||
|
@ -419,9 +415,9 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
|
|||
sbPosition.highlightCurrentChapter()
|
||||
}
|
||||
txtvSeek.text = controller!!.getMedia()?.getChapters()?.get(newChapterIndex)?.title ?: (""
|
||||
+ "\n" + ac.mdiq.podcini.util.Converter.getDurationStringLong(position))
|
||||
+ "\n" + Converter.getDurationStringLong(position))
|
||||
} else {
|
||||
txtvSeek.text = ac.mdiq.podcini.util.Converter.getDurationStringLong(position)
|
||||
txtvSeek.text = Converter.getDurationStringLong(position)
|
||||
}
|
||||
} else if (duration != controller!!.duration) {
|
||||
updateUi(controller!!.getMedia())
|
||||
|
|
|
@ -1,32 +1,44 @@
|
|||
package ac.mdiq.podcini.ui.fragment
|
||||
|
||||
import ac.mdiq.podcini.R
|
||||
import ac.mdiq.podcini.databinding.ExternalPlayerFragmentBinding
|
||||
import ac.mdiq.podcini.feed.util.ImageResourceUtils.getEpisodeListImageLocation
|
||||
import ac.mdiq.podcini.feed.util.ImageResourceUtils.getFallbackImageLocation
|
||||
import ac.mdiq.podcini.feed.util.PlaybackSpeedUtils
|
||||
import ac.mdiq.podcini.playback.PlaybackController
|
||||
import ac.mdiq.podcini.playback.base.PlayerStatus
|
||||
import ac.mdiq.podcini.playback.event.PlaybackPositionEvent
|
||||
import ac.mdiq.podcini.playback.event.PlaybackServiceEvent
|
||||
import ac.mdiq.podcini.playback.event.SpeedChangedEvent
|
||||
import ac.mdiq.podcini.preferences.UserPreferences
|
||||
import ac.mdiq.podcini.receiver.MediaButtonReceiver
|
||||
import ac.mdiq.podcini.service.playback.PlaybackService.Companion.getPlayerActivityIntent
|
||||
import ac.mdiq.podcini.storage.model.playback.MediaType
|
||||
import ac.mdiq.podcini.storage.model.playback.Playable
|
||||
import ac.mdiq.podcini.ui.activity.MainActivity
|
||||
import ac.mdiq.podcini.ui.common.PlaybackSpeedIndicatorView
|
||||
import ac.mdiq.podcini.ui.dialog.SkipPreferenceDialog
|
||||
import ac.mdiq.podcini.ui.dialog.VariableSpeedDialog
|
||||
import ac.mdiq.podcini.ui.view.ChapterSeekBar
|
||||
import ac.mdiq.podcini.ui.view.PlayButton
|
||||
import ac.mdiq.podcini.util.Converter
|
||||
import ac.mdiq.podcini.util.TimeSpeedConverter
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.KeyEvent
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageButton
|
||||
import android.widget.ImageView
|
||||
import android.widget.ProgressBar
|
||||
import android.widget.SeekBar
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.request.RequestOptions
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import ac.mdiq.podcini.R
|
||||
import ac.mdiq.podcini.databinding.ExternalPlayerFragmentBinding
|
||||
import ac.mdiq.podcini.feed.util.ImageResourceUtils.getEpisodeListImageLocation
|
||||
import ac.mdiq.podcini.feed.util.ImageResourceUtils.getFallbackImageLocation
|
||||
import ac.mdiq.podcini.service.playback.PlaybackService.Companion.getPlayerActivityIntent
|
||||
import ac.mdiq.podcini.playback.PlaybackController
|
||||
import ac.mdiq.podcini.playback.event.PlaybackPositionEvent
|
||||
import ac.mdiq.podcini.playback.event.PlaybackServiceEvent
|
||||
import ac.mdiq.podcini.storage.model.playback.MediaType
|
||||
import ac.mdiq.podcini.storage.model.playback.Playable
|
||||
import ac.mdiq.podcini.playback.base.PlayerStatus
|
||||
import ac.mdiq.podcini.ui.view.PlayButton
|
||||
import androidx.annotation.OptIn
|
||||
import io.reactivex.Maybe
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.disposables.Disposable
|
||||
|
@ -34,32 +46,60 @@ import io.reactivex.schedulers.Schedulers
|
|||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import org.greenrobot.eventbus.ThreadMode
|
||||
import java.text.DecimalFormat
|
||||
import java.text.NumberFormat
|
||||
import kotlin.math.max
|
||||
|
||||
/**
|
||||
* Fragment which is supposed to be displayed outside of the MediaplayerActivity.
|
||||
*/
|
||||
class ExternalPlayerFragment : Fragment() {
|
||||
class ExternalPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener {
|
||||
private lateinit var imgvCover: ImageView
|
||||
private lateinit var txtvTitle: TextView
|
||||
private lateinit var butPlay: PlayButton
|
||||
private lateinit var feedName: TextView
|
||||
private lateinit var progressBar: ProgressBar
|
||||
|
||||
lateinit var butPlaybackSpeed: PlaybackSpeedIndicatorView
|
||||
lateinit var txtvPlaybackSpeed: TextView
|
||||
|
||||
private lateinit var butRev: ImageButton
|
||||
private lateinit var txtvRev: TextView
|
||||
private lateinit var butFF: ImageButton
|
||||
private lateinit var txtvFF: TextView
|
||||
private lateinit var butSkip: ImageButton
|
||||
|
||||
private lateinit var txtvPosition: TextView
|
||||
private lateinit var txtvLength: TextView
|
||||
private lateinit var sbPosition: ChapterSeekBar
|
||||
|
||||
private var showTimeLeft = false
|
||||
|
||||
private var controller: PlaybackController? = null
|
||||
private var disposable: Disposable? = null
|
||||
|
||||
@UnstableApi
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
savedInstanceState: Bundle?): View {
|
||||
val viewBinding = ExternalPlayerFragmentBinding.inflate(inflater)
|
||||
|
||||
Log.d(TAG, "fragment onCreateView")
|
||||
|
||||
butPlaybackSpeed = viewBinding.butPlaybackSpeed
|
||||
txtvPlaybackSpeed = viewBinding.txtvPlaybackSpeed
|
||||
imgvCover = viewBinding.imgvCover
|
||||
txtvTitle = viewBinding.txtvTitle
|
||||
butPlay = viewBinding.butPlay
|
||||
feedName = viewBinding.txtvAuthor
|
||||
progressBar = viewBinding.episodeProgress
|
||||
butRev = viewBinding.butRev
|
||||
txtvRev = viewBinding.txtvRev
|
||||
butFF = viewBinding.butFF
|
||||
txtvFF = viewBinding.txtvFF
|
||||
butSkip = viewBinding.butSkip
|
||||
sbPosition = viewBinding.sbPosition
|
||||
txtvPosition = viewBinding.txtvPosition
|
||||
txtvLength = viewBinding.txtvLength
|
||||
|
||||
setupLengthTextView()
|
||||
setupControlButtons()
|
||||
butPlaybackSpeed.setOnClickListener {
|
||||
VariableSpeedDialog().show(childFragmentManager, null)
|
||||
}
|
||||
sbPosition.setOnSeekBarChangeListener(this)
|
||||
|
||||
viewBinding.externalPlayerFragment.setOnClickListener {
|
||||
Log.d(TAG, "externalPlayerFragment was clicked")
|
||||
|
@ -73,6 +113,7 @@ class ExternalPlayerFragment : Fragment() {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
controller = setupPlaybackController()
|
||||
controller!!.init()
|
||||
loadMediaInfo()
|
||||
|
@ -86,6 +127,7 @@ class ExternalPlayerFragment : Fragment() {
|
|||
controller = null
|
||||
EventBus.getDefault().unregister(this)
|
||||
}
|
||||
|
||||
@UnstableApi
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
@ -104,6 +146,39 @@ class ExternalPlayerFragment : Fragment() {
|
|||
loadMediaInfo()
|
||||
}
|
||||
|
||||
@OptIn(UnstableApi::class) private fun setupControlButtons() {
|
||||
butRev.setOnClickListener {
|
||||
if (controller != null) {
|
||||
val curr: Int = controller!!.position
|
||||
controller!!.seekTo(curr - UserPreferences.rewindSecs * 1000)
|
||||
}
|
||||
}
|
||||
butRev.setOnLongClickListener {
|
||||
SkipPreferenceDialog.showSkipPreference(requireContext(),
|
||||
SkipPreferenceDialog.SkipDirection.SKIP_REWIND, txtvRev)
|
||||
true
|
||||
}
|
||||
butPlay.setOnClickListener {
|
||||
controller?.init()
|
||||
controller?.playPause()
|
||||
}
|
||||
butFF.setOnClickListener {
|
||||
if (controller != null) {
|
||||
val curr: Int = controller!!.position
|
||||
controller!!.seekTo(curr + UserPreferences.fastForwardSecs * 1000)
|
||||
}
|
||||
}
|
||||
butFF.setOnLongClickListener {
|
||||
SkipPreferenceDialog.showSkipPreference(requireContext(),
|
||||
SkipPreferenceDialog.SkipDirection.SKIP_FORWARD, txtvFF)
|
||||
false
|
||||
}
|
||||
butSkip.setOnClickListener {
|
||||
activity?.sendBroadcast(
|
||||
MediaButtonReceiver.createIntent(requireContext(), KeyEvent.KEYCODE_MEDIA_NEXT))
|
||||
}
|
||||
}
|
||||
|
||||
@UnstableApi
|
||||
private fun setupPlaybackController(): PlaybackController {
|
||||
return object : PlaybackController(requireActivity()) {
|
||||
|
@ -121,15 +196,58 @@ class ExternalPlayerFragment : Fragment() {
|
|||
}
|
||||
}
|
||||
|
||||
@OptIn(UnstableApi::class) private fun setupLengthTextView() {
|
||||
showTimeLeft = UserPreferences.shouldShowRemainingTime()
|
||||
txtvLength.setOnClickListener(View.OnClickListener {
|
||||
if (controller == null) {
|
||||
return@OnClickListener
|
||||
}
|
||||
showTimeLeft = !showTimeLeft
|
||||
UserPreferences.setShowRemainTimeSetting(showTimeLeft)
|
||||
onPositionObserverUpdate(PlaybackPositionEvent(controller!!.position, controller!!.duration))
|
||||
})
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun updatePlaybackSpeedButton(event: SpeedChangedEvent) {
|
||||
val speedStr: String = DecimalFormat("0.00").format(event.newSpeed.toDouble())
|
||||
txtvPlaybackSpeed.text = speedStr
|
||||
butPlaybackSpeed.setSpeed(event.newSpeed)
|
||||
}
|
||||
|
||||
@UnstableApi
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onPositionObserverUpdate(event: PlaybackPositionEvent?) {
|
||||
if (controller == null) {
|
||||
return
|
||||
} else if (controller!!.position == Playable.INVALID_TIME || controller!!.duration == Playable.INVALID_TIME) {
|
||||
fun onPositionObserverUpdate(event: PlaybackPositionEvent) {
|
||||
if (controller == null || controller!!.position == Playable.INVALID_TIME || controller!!.duration == Playable.INVALID_TIME) {
|
||||
return
|
||||
}
|
||||
progressBar.progress = (controller!!.position.toDouble() / controller!!.duration * 100).toInt()
|
||||
val converter = TimeSpeedConverter(controller!!.currentPlaybackSpeedMultiplier)
|
||||
val currentPosition: Int = converter.convert(event.position)
|
||||
val duration: Int = converter.convert(event.duration)
|
||||
val remainingTime: Int = converter.convert(max((event.duration - event.position).toDouble(), 0.0).toInt())
|
||||
if (currentPosition == Playable.INVALID_TIME || duration == Playable.INVALID_TIME) {
|
||||
Log.w(AudioPlayerFragment.TAG, "Could not react to position observer update because of invalid time")
|
||||
return
|
||||
}
|
||||
|
||||
txtvPosition.text = Converter.getDurationStringLong(currentPosition)
|
||||
txtvPosition.setContentDescription(getString(R.string.position,
|
||||
Converter.getDurationStringLocalized(requireContext(), currentPosition.toLong())))
|
||||
val showTimeLeft = UserPreferences.shouldShowRemainingTime()
|
||||
if (showTimeLeft) {
|
||||
txtvLength.setContentDescription(getString(R.string.remaining_time,
|
||||
Converter.getDurationStringLocalized(requireContext(), remainingTime.toLong())))
|
||||
txtvLength.text = (if ((remainingTime > 0)) "-" else "") + Converter.getDurationStringLong(remainingTime)
|
||||
} else {
|
||||
txtvLength.setContentDescription(getString(R.string.chapter_duration,
|
||||
Converter.getDurationStringLocalized(requireContext(), duration.toLong())))
|
||||
txtvLength.text = Converter.getDurationStringLong(duration)
|
||||
}
|
||||
|
||||
if (!sbPosition.isPressed) {
|
||||
val progress: Float = (event.position.toFloat()) / event.duration
|
||||
sbPosition.progress = (progress * sbPosition.max).toInt()
|
||||
}
|
||||
}
|
||||
|
||||
@UnstableApi @Subscribe(threadMode = ThreadMode.MAIN)
|
||||
|
@ -145,6 +263,15 @@ class ExternalPlayerFragment : Fragment() {
|
|||
disposable?.dispose()
|
||||
}
|
||||
|
||||
@OptIn(UnstableApi::class) override fun onStart() {
|
||||
super.onStart()
|
||||
txtvRev.text = NumberFormat.getInstance().format(UserPreferences.rewindSecs.toLong())
|
||||
txtvFF.text = NumberFormat.getInstance().format(UserPreferences.fastForwardSecs.toLong())
|
||||
val media = controller?.getMedia()
|
||||
if (media != null) updatePlaybackSpeedButton(SpeedChangedEvent(PlaybackSpeedUtils.getCurrentPlaybackSpeed(media)))
|
||||
|
||||
}
|
||||
|
||||
@UnstableApi
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
|
@ -160,7 +287,9 @@ class ExternalPlayerFragment : Fragment() {
|
|||
}
|
||||
|
||||
disposable?.dispose()
|
||||
disposable = Maybe.fromCallable<Playable?> { controller?.getMedia() }
|
||||
disposable = Maybe.fromCallable<Playable?> {
|
||||
controller?.getMedia()
|
||||
}
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe({ media: Playable? -> this.updateUi(media) },
|
||||
|
@ -168,14 +297,67 @@ class ExternalPlayerFragment : Fragment() {
|
|||
{ (activity as MainActivity).setPlayerVisible(false) })
|
||||
}
|
||||
|
||||
@OptIn(UnstableApi::class) override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
|
||||
if (controller == null) return
|
||||
|
||||
// if (fromUser) {
|
||||
// val prog: Float = progress / (seekBar.max.toFloat())
|
||||
// val converter = TimeSpeedConverter(controller!!.currentPlaybackSpeedMultiplier)
|
||||
// var position: Int = converter.convert((prog * controller!!.duration).toInt())
|
||||
// val newChapterIndex: Int = ChapterUtils.getCurrentChapterIndex(controller!!.getMedia(), position)
|
||||
// if (newChapterIndex > -1) {
|
||||
// if (!sbPosition.isPressed && currentChapterIndex != newChapterIndex) {
|
||||
// currentChapterIndex = newChapterIndex
|
||||
// val media = controller!!.getMedia()
|
||||
// position = media?.getChapters()?.get(currentChapterIndex)?.start?.toInt() ?: 0
|
||||
// seekedToChapterStart = true
|
||||
// controller!!.seekTo(position)
|
||||
// updateUi(controller!!.getMedia())
|
||||
// sbPosition.highlightCurrentChapter()
|
||||
// }
|
||||
//// txtvSeek.text = controller!!.getMedia()?.getChapters()?.get(newChapterIndex)?.title ?: (""
|
||||
//// + "\n" + Converter.getDurationStringLong(position))
|
||||
// } else {
|
||||
//// txtvSeek.text = Converter.getDurationStringLong(position)
|
||||
// }
|
||||
// } else if (duration != controller!!.duration) {
|
||||
// updateUi(controller!!.getMedia())
|
||||
// }
|
||||
}
|
||||
|
||||
override fun onStartTrackingTouch(seekBar: SeekBar) {
|
||||
// interrupt position Observer, restart later
|
||||
// cardViewSeek.scaleX = .8f
|
||||
// cardViewSeek.scaleY = .8f
|
||||
// cardViewSeek.animate()
|
||||
// ?.setInterpolator(FastOutSlowInInterpolator())
|
||||
// ?.alpha(1f)?.scaleX(1f)?.scaleY(1f)
|
||||
// ?.setDuration(200)
|
||||
// ?.start()
|
||||
}
|
||||
|
||||
@OptIn(UnstableApi::class) override fun onStopTrackingTouch(seekBar: SeekBar) {
|
||||
if (controller != null) {
|
||||
val prog: Float = seekBar.progress / (seekBar.max.toFloat())
|
||||
controller!!.seekTo((prog * controller!!.duration).toInt())
|
||||
}
|
||||
// cardViewSeek.scaleX = 1f
|
||||
// cardViewSeek.scaleY = 1f
|
||||
// cardViewSeek.animate()
|
||||
// ?.setInterpolator(FastOutSlowInInterpolator())
|
||||
// ?.alpha(0f)?.scaleX(.8f)?.scaleY(.8f)
|
||||
// ?.setDuration(200)
|
||||
// ?.start()
|
||||
}
|
||||
|
||||
@UnstableApi
|
||||
private fun updateUi(media: Playable?) {
|
||||
if (media == null) {
|
||||
return
|
||||
}
|
||||
(activity as MainActivity).setPlayerVisible(true)
|
||||
txtvTitle.text = media.getEpisodeTitle()
|
||||
feedName.text = media.getFeedTitle()
|
||||
// txtvTitle.text = media.getEpisodeTitle()
|
||||
// feedName.text = media.getFeedTitle()
|
||||
onPositionObserverUpdate(PlaybackPositionEvent(media.getPosition(), media.getDuration()))
|
||||
|
||||
val options = RequestOptions()
|
||||
|
|
|
@ -97,6 +97,7 @@ class ItemFragment : Fragment() {
|
|||
@UnstableApi override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
super.onCreateView(inflater, container, savedInstanceState)
|
||||
val viewBinding = FeeditemFragmentBinding.inflate(inflater)
|
||||
root = viewBinding.root
|
||||
|
||||
Log.d(TAG, "fragment onCreateView")
|
||||
txtvPodcast = viewBinding.txtvPodcast
|
||||
|
@ -110,8 +111,8 @@ class ItemFragment : Fragment() {
|
|||
txtvTitle.ellipsize = TextUtils.TruncateAt.END
|
||||
webvDescription = viewBinding.webvDescription
|
||||
webvDescription.setTimecodeSelectedListener { time: Int? ->
|
||||
if (controller != null && item != null && item!!.media != null && controller!!.getMedia() != null &&
|
||||
item!!.media!!.getIdentifier() == controller!!.getMedia()!!.getIdentifier()) {
|
||||
val cMedia = controller?.getMedia()
|
||||
if (item?.media?.getIdentifier() == cMedia?.getIdentifier()) {
|
||||
controller!!.seekTo(time ?: 0)
|
||||
} else {
|
||||
(activity as MainActivity).showSnackbarAbovePlayer(R.string.play_this_to_seek_position,
|
||||
|
@ -378,8 +379,7 @@ class ItemFragment : Fragment() {
|
|||
itemsLoaded = true
|
||||
},
|
||||
{ error: Throwable? ->
|
||||
Log.e(TAG,
|
||||
Log.getStackTraceString(error))
|
||||
Log.e(TAG, Log.getStackTraceString(error))
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -387,7 +387,7 @@ class ItemFragment : Fragment() {
|
|||
val feedItem: FeedItem? = DBReader.getFeedItem(itemId)
|
||||
val context = context
|
||||
if (feedItem != null && context != null) {
|
||||
val duration = if (feedItem.media != null) feedItem.media!!.getDuration() else Int.MAX_VALUE
|
||||
val duration = feedItem.media?.getDuration()?: Int.MAX_VALUE
|
||||
DBReader.loadDescriptionOfFeedItem(feedItem)
|
||||
val t = ShownotesCleaner(context, feedItem.description?:"", duration)
|
||||
webviewData = t.processShownotes()
|
||||
|
|
|
@ -52,8 +52,8 @@ import kotlin.math.max
|
|||
|
||||
class NavDrawerFragment : Fragment(), SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
private var navDrawerData: NavDrawerData? = null
|
||||
private var flatItemList: List<NavDrawerData.DrawerItem>? = null
|
||||
private var contextPressedItem: NavDrawerData.DrawerItem? = null
|
||||
private var flatItemList: List<NavDrawerData.FeedDrawerItem>? = null
|
||||
private var contextPressedItem: NavDrawerData.FeedDrawerItem? = null
|
||||
private var disposable: Disposable? = null
|
||||
|
||||
private lateinit var navAdapter: NavListAdapter
|
||||
|
@ -140,28 +140,20 @@ class NavDrawerFragment : Fragment(), SharedPreferences.OnSharedPreferenceChange
|
|||
val inflater: MenuInflater = requireActivity().menuInflater
|
||||
if (contextPressedItem != null) {
|
||||
menu.setHeaderTitle(contextPressedItem!!.title)
|
||||
if (contextPressedItem!!.type == NavDrawerData.DrawerItem.Type.FEED) {
|
||||
inflater.inflate(R.menu.nav_feed_context, menu)
|
||||
// episodes are not loaded, so we cannot check if the podcast has new or unplayed ones!
|
||||
} else {
|
||||
inflater.inflate(R.menu.nav_folder_context, menu)
|
||||
}
|
||||
inflater.inflate(R.menu.nav_feed_context, menu)
|
||||
// episodes are not loaded, so we cannot check if the podcast has new or unplayed ones!
|
||||
}
|
||||
MenuItemUtils.setOnClickListeners(menu
|
||||
) { item: MenuItem -> this.onContextItemSelected(item) }
|
||||
}
|
||||
|
||||
override fun onContextItemSelected(item: MenuItem): Boolean {
|
||||
val pressedItem: NavDrawerData.DrawerItem? = contextPressedItem
|
||||
val pressedItem: NavDrawerData.FeedDrawerItem? = contextPressedItem
|
||||
contextPressedItem = null
|
||||
if (pressedItem == null) {
|
||||
return false
|
||||
}
|
||||
return if (pressedItem.type == NavDrawerData.DrawerItem.Type.FEED) {
|
||||
onFeedContextMenuClicked((pressedItem as NavDrawerData.FeedDrawerItem).feed, item)
|
||||
} else {
|
||||
onTagContextMenuClicked(pressedItem, item)
|
||||
}
|
||||
return onFeedContextMenuClicked(pressedItem.feed, item)
|
||||
}
|
||||
|
||||
@OptIn(UnstableApi::class) private fun onFeedContextMenuClicked(feed: Feed, item: MenuItem): Boolean {
|
||||
|
@ -202,7 +194,7 @@ class NavDrawerFragment : Fragment(), SharedPreferences.OnSharedPreferenceChange
|
|||
}
|
||||
}
|
||||
|
||||
private fun onTagContextMenuClicked(drawerItem: NavDrawerData.DrawerItem?, item: MenuItem): Boolean {
|
||||
private fun onTagContextMenuClicked(drawerItem: NavDrawerData.FeedDrawerItem?, item: MenuItem): Boolean {
|
||||
val itemId = item.itemId
|
||||
if (itemId == R.id.rename_folder_item) {
|
||||
RenameItemDialog(activity as Activity, drawerItem).show()
|
||||
|
@ -245,7 +237,7 @@ class NavDrawerFragment : Fragment(), SharedPreferences.OnSharedPreferenceChange
|
|||
0
|
||||
}
|
||||
|
||||
override fun getItem(position: Int): NavDrawerData.DrawerItem? {
|
||||
override fun getItem(position: Int): NavDrawerData.FeedDrawerItem? {
|
||||
return if (flatItemList != null && 0 <= position && position < flatItemList!!.size) {
|
||||
flatItemList!![position]
|
||||
} else {
|
||||
|
@ -260,11 +252,9 @@ class NavDrawerFragment : Fragment(), SharedPreferences.OnSharedPreferenceChange
|
|||
} else if (StringUtils.isNumeric(lastNavFragment)) { // last fragment was not a list, but a feed
|
||||
val feedId = lastNavFragment.toLong()
|
||||
if (navDrawerData != null) {
|
||||
val itemToCheck: NavDrawerData.DrawerItem = flatItemList!![position - navAdapter.subscriptionOffset]
|
||||
if (itemToCheck.type == NavDrawerData.DrawerItem.Type.FEED) {
|
||||
// When the same feed is displayed multiple times, it should be highlighted multiple times.
|
||||
return (itemToCheck as NavDrawerData.FeedDrawerItem).feed.id == feedId
|
||||
}
|
||||
val itemToCheck: NavDrawerData.FeedDrawerItem = flatItemList!![position - navAdapter.subscriptionOffset]
|
||||
// When the same feed is displayed multiple times, it should be highlighted multiple times.
|
||||
return itemToCheck.feed.id == feedId
|
||||
}
|
||||
}
|
||||
return false
|
||||
|
@ -303,35 +293,10 @@ class NavDrawerFragment : Fragment(), SharedPreferences.OnSharedPreferenceChange
|
|||
(activity as MainActivity).bottomSheet.setState(BottomSheetBehavior.STATE_COLLAPSED)
|
||||
} else {
|
||||
val pos: Int = position - navAdapter.subscriptionOffset
|
||||
val clickedItem: NavDrawerData.DrawerItem = flatItemList!![pos]
|
||||
|
||||
if (clickedItem.type == NavDrawerData.DrawerItem.Type.FEED) {
|
||||
val feedId: Long = (clickedItem as NavDrawerData.FeedDrawerItem).feed.id
|
||||
(activity as MainActivity).loadFeedFragmentById(feedId, null)
|
||||
(activity as MainActivity).bottomSheet.setState(BottomSheetBehavior.STATE_COLLAPSED)
|
||||
} else {
|
||||
// val folder: NavDrawerData.TagDrawerItem = (clickedItem as NavDrawerData.TagDrawerItem)
|
||||
// if (openFolders.contains(folder.name)) {
|
||||
// openFolders.remove(folder.name)
|
||||
// } else {
|
||||
// openFolders.add(folder.name)
|
||||
// }
|
||||
//
|
||||
// context!!.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
|
||||
// .edit()
|
||||
// .putStringSet(PREF_OPEN_FOLDERS, openFolders)
|
||||
// .apply()
|
||||
//
|
||||
// disposable =
|
||||
// Observable.fromCallable { makeFlatDrawerData(navDrawerData!!.items, 0) }
|
||||
// .subscribeOn(Schedulers.computation())
|
||||
// .observeOn(AndroidSchedulers.mainThread())
|
||||
// .subscribe(
|
||||
// { result: List<NavDrawerData.DrawerItem>? ->
|
||||
// flatItemList = result
|
||||
// navAdapter.notifyDataSetChanged()
|
||||
// }, { error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) })
|
||||
}
|
||||
val clickedItem: NavDrawerData.FeedDrawerItem = flatItemList!![pos]
|
||||
val feedId: Long = clickedItem.feed.id
|
||||
(activity as MainActivity).loadFeedFragmentById(feedId, null)
|
||||
(activity as MainActivity).bottomSheet.setState(BottomSheetBehavior.STATE_COLLAPSED)
|
||||
}
|
||||
} else if (UserPreferences.subscriptionsFilter.isEnabled
|
||||
&& navAdapter.showSubscriptionList) {
|
||||
|
@ -370,7 +335,7 @@ class NavDrawerFragment : Fragment(), SharedPreferences.OnSharedPreferenceChange
|
|||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{ result: Pair<NavDrawerData, List<NavDrawerData.DrawerItem>> ->
|
||||
{ result: Pair<NavDrawerData, List<NavDrawerData.FeedDrawerItem>> ->
|
||||
navDrawerData = result.first
|
||||
flatItemList = result.second
|
||||
navAdapter.notifyDataSetChanged()
|
||||
|
@ -381,18 +346,11 @@ class NavDrawerFragment : Fragment(), SharedPreferences.OnSharedPreferenceChange
|
|||
})
|
||||
}
|
||||
|
||||
private fun makeFlatDrawerData(items: List<NavDrawerData.DrawerItem>, layer: Int): List<NavDrawerData.DrawerItem> {
|
||||
val flatItems: MutableList<NavDrawerData.DrawerItem> = ArrayList()
|
||||
private fun makeFlatDrawerData(items: List<NavDrawerData.FeedDrawerItem>, layer: Int): List<NavDrawerData.FeedDrawerItem> {
|
||||
val flatItems: MutableList<NavDrawerData.FeedDrawerItem> = ArrayList()
|
||||
for (item in items) {
|
||||
item.layer = layer
|
||||
flatItems.add(item)
|
||||
// if (item.type == NavDrawerData.DrawerItem.Type.TAG) {
|
||||
// val folder: NavDrawerData.TagDrawerItem = (item as NavDrawerData.TagDrawerItem)
|
||||
// folder.isOpen = openFolders.contains(folder.name)
|
||||
// if (folder.isOpen) {
|
||||
// flatItems.addAll(makeFlatDrawerData(item.children, layer + 1))
|
||||
// }
|
||||
// }
|
||||
}
|
||||
return flatItems
|
||||
}
|
||||
|
|
|
@ -124,7 +124,7 @@ class SubscriptionFragment : Fragment(), Toolbar.OnMenuItemClickListener, Select
|
|||
subscriptionRecycler.adapter = subscriptionAdapter
|
||||
setupEmptyView()
|
||||
|
||||
tags.add("None")
|
||||
tags.add("Untagged")
|
||||
tags.add("All")
|
||||
tags.addAll(DBReader.getTags())
|
||||
val adapter = ArrayAdapter(requireContext(), android.R.layout.simple_spinner_item, tags)
|
||||
|
@ -315,10 +315,10 @@ class SubscriptionFragment : Fragment(), Toolbar.OnMenuItemClickListener, Select
|
|||
}
|
||||
|
||||
override fun onContextItemSelected(item: MenuItem): Boolean {
|
||||
val drawerItem: NavDrawerData.DrawerItem = subscriptionAdapter.getSelectedItem() ?: return false
|
||||
val drawerItem: NavDrawerData.FeedDrawerItem = subscriptionAdapter.getSelectedItem() ?: return false
|
||||
val itemId = item.itemId
|
||||
|
||||
val feed: Feed = (drawerItem as NavDrawerData.FeedDrawerItem).feed
|
||||
val feed: Feed = drawerItem.feed
|
||||
if (itemId == R.id.multi_select) {
|
||||
speedDialView.visibility = View.VISIBLE
|
||||
return subscriptionAdapter.onContextItemSelected(item)
|
||||
|
@ -328,6 +328,7 @@ class SubscriptionFragment : Fragment(), Toolbar.OnMenuItemClickListener, Select
|
|||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onFeedListChanged(event: FeedListUpdateEvent?) {
|
||||
DBReader.updateFeedList()
|
||||
loadSubscriptions()
|
||||
}
|
||||
|
||||
|
@ -343,7 +344,7 @@ class SubscriptionFragment : Fragment(), Toolbar.OnMenuItemClickListener, Select
|
|||
}
|
||||
|
||||
override fun onStartSelectMode() {
|
||||
val feedsOnly: MutableList<NavDrawerData.DrawerItem> = ArrayList<NavDrawerData.DrawerItem>()
|
||||
val feedsOnly: MutableList<NavDrawerData.FeedDrawerItem> = ArrayList<NavDrawerData.FeedDrawerItem>()
|
||||
for (item in feedListFiltered) {
|
||||
feedsOnly.add(item)
|
||||
}
|
||||
|
|
|
@ -1,15 +1,14 @@
|
|||
package ac.mdiq.podcini.ui.view
|
||||
|
||||
import ac.mdiq.podcini.databinding.PlaybackSpeedSeekBarBinding
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.SeekBar
|
||||
import android.widget.SeekBar.OnSeekBarChangeListener
|
||||
import androidx.core.util.Consumer
|
||||
import ac.mdiq.podcini.R
|
||||
import ac.mdiq.podcini.databinding.PlaybackSpeedSeekBarBinding
|
||||
import android.view.LayoutInflater
|
||||
|
||||
class PlaybackSpeedSeekBar : FrameLayout {
|
||||
private lateinit var binding: PlaybackSpeedSeekBarBinding
|
||||
|
@ -30,25 +29,20 @@ class PlaybackSpeedSeekBar : FrameLayout {
|
|||
}
|
||||
|
||||
private fun setup() {
|
||||
val inflater = LayoutInflater.from(context)
|
||||
binding = PlaybackSpeedSeekBarBinding.inflate(inflater, this, false)
|
||||
binding = PlaybackSpeedSeekBarBinding.inflate(LayoutInflater.from(context), this, true)
|
||||
seekBar = binding.playbackSpeed
|
||||
binding.butDecSpeed.setOnClickListener { v: View? -> seekBar.progress -= 2 }
|
||||
binding.butIncSpeed.setOnClickListener { v: View? -> seekBar.progress += 2 }
|
||||
binding.butDecSpeed.setOnClickListener { seekBar.progress -= 2 }
|
||||
binding.butIncSpeed.setOnClickListener { seekBar.progress += 2 }
|
||||
|
||||
seekBar.setOnSeekBarChangeListener(object : OnSeekBarChangeListener {
|
||||
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
|
||||
val playbackSpeed = (progress + 10) / 20.0f
|
||||
if (progressChangedListener != null) {
|
||||
progressChangedListener!!.accept(playbackSpeed)
|
||||
}
|
||||
progressChangedListener?.accept(playbackSpeed)
|
||||
}
|
||||
|
||||
override fun onStartTrackingTouch(seekBar: SeekBar) {
|
||||
}
|
||||
override fun onStartTrackingTouch(seekBar: SeekBar) {}
|
||||
|
||||
override fun onStopTrackingTouch(seekBar: SeekBar) {
|
||||
}
|
||||
override fun onStopTrackingTouch(seekBar: SeekBar) {}
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -31,6 +31,7 @@ import ac.mdiq.podcini.storage.model.playback.MediaType
|
|||
import ac.mdiq.podcini.storage.model.playback.Playable
|
||||
import ac.mdiq.podcini.net.download.serviceinterface.DownloadServiceInterface
|
||||
import ac.mdiq.podcini.preferences.UserPreferences
|
||||
import ac.mdiq.podcini.ui.adapter.actionbutton.ItemActionButton
|
||||
import ac.mdiq.podcini.ui.common.CircularProgressBar
|
||||
import ac.mdiq.podcini.ui.common.ThemeUtils
|
||||
import io.reactivex.functions.Consumer
|
||||
|
@ -42,6 +43,7 @@ import kotlin.math.max
|
|||
@UnstableApi
|
||||
class EpisodeItemViewHolder(private val activity: MainActivity, parent: ViewGroup?) :
|
||||
RecyclerView.ViewHolder(LayoutInflater.from(activity).inflate(R.layout.feeditemlist_item, parent, false)) {
|
||||
|
||||
val binding: FeeditemlistItemBinding = FeeditemlistItemBinding.bind(itemView)
|
||||
|
||||
private val container: View = binding.container
|
||||
|
@ -110,7 +112,7 @@ class EpisodeItemViewHolder(private val activity: MainActivity, parent: ViewGrou
|
|||
isInQueue.visibility = if (item.isTagged(FeedItem.TAG_QUEUE)) View.VISIBLE else View.GONE
|
||||
container.alpha = if (item.isPlayed()) 0.5f else 1.0f
|
||||
|
||||
val actionButton: ac.mdiq.podcini.ui.adapter.actionbutton.ItemActionButton = ac.mdiq.podcini.ui.adapter.actionbutton.ItemActionButton.forItem(item)
|
||||
val actionButton: ItemActionButton = ItemActionButton.forItem(item)
|
||||
actionButton.configure(secondaryActionButton, secondaryActionIcon, activity)
|
||||
secondaryActionButton.isFocusable = false
|
||||
|
||||
|
|
|
@ -32,10 +32,10 @@ object ChapterUtils {
|
|||
|
||||
@JvmStatic
|
||||
fun getCurrentChapterIndex(media: Playable?, position: Int): Int {
|
||||
if (media?.getChapters() == null || media.getChapters().isEmpty()) {
|
||||
val chapters = media?.getChapters()
|
||||
if (chapters.isNullOrEmpty()) {
|
||||
return -1
|
||||
}
|
||||
val chapters = media.getChapters()
|
||||
for (i in chapters.indices) {
|
||||
if (chapters[i].start > position) {
|
||||
return i - 1
|
||||
|
@ -154,7 +154,7 @@ object ChapterUtils {
|
|||
val request: Request = Builder().url(url).cacheControl(cacheControl).build()
|
||||
response = getHttpClient().newCall(request).execute()
|
||||
if (response.isSuccessful && response.body != null) {
|
||||
return ac.mdiq.podcini.feed.parser.PodcastIndexChapterParser.parse(response.body!!.string())
|
||||
return PodcastIndexChapterParser.parse(response.body!!.string())
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
|
|
|
@ -44,10 +44,9 @@
|
|||
<ImageView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="8dp"
|
||||
android:layout_alignBottom="@id/pager"
|
||||
android:importantForAccessibility="no"
|
||||
app:srcCompat="@drawable/bg_gradient"
|
||||
app:tint="?android:attr/colorBackground" />
|
||||
app:tint="?android:attr/colorBackground"/>
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/cardViewSeek"
|
||||
|
@ -115,7 +114,7 @@
|
|||
android:layout_marginLeft="16dp"
|
||||
android:text="@string/position_default_label"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="@dimen/text_size_micro" />
|
||||
android:textSize="@dimen/text_size_micro"/>
|
||||
|
||||
<ac.mdiq.podcini.ui.view.NoRelayoutTextView
|
||||
android:id="@+id/txtvLength"
|
||||
|
@ -129,7 +128,7 @@
|
|||
android:background="?android:attr/selectableItemBackground"
|
||||
android:text="@string/position_default_label"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="@dimen/text_size_micro" />
|
||||
android:textSize="@dimen/text_size_micro"/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
|
|
|
@ -7,11 +7,11 @@
|
|||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/rootFolderCheckbox"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/feed_folders_include_root" />
|
||||
<!-- <CheckBox-->
|
||||
<!-- android:id="@+id/rootFolderCheckbox"-->
|
||||
<!-- android:layout_width="match_parent"-->
|
||||
<!-- android:layout_height="wrap_content"-->
|
||||
<!-- android:text="@string/feed_folders_include_root" />-->
|
||||
|
||||
<com.joanzapata.iconify.widget.IconTextView
|
||||
android:id="@+id/commonTagsInfo"
|
||||
|
|
|
@ -9,71 +9,190 @@
|
|||
android:background="?attr/selectableItemBackground"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ac.mdiq.podcini.ui.view.ChapterSeekBar
|
||||
android:id="@+id/sbPosition"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="5.dp"
|
||||
android:clickable="true"
|
||||
android:max="500"
|
||||
tools:progress="100" />
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<ac.mdiq.podcini.ui.view.NoRelayoutTextView
|
||||
android:id="@+id/txtvPosition"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_marginStart="5dp"
|
||||
android:layout_marginLeft="5dp"
|
||||
android:text="@string/position_default_label"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="@dimen/text_size_micro"/>
|
||||
|
||||
<ac.mdiq.podcini.ui.view.NoRelayoutTextView
|
||||
android:id="@+id/txtvLength"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:layout_marginRight="5dp"
|
||||
android:textAlignment="textEnd"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:text="@string/position_default_label"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="@dimen/text_size_micro"/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_weight="1">
|
||||
android:layout_weight="1"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imgvCover"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="70dp"
|
||||
android:layout_height="match_parent"
|
||||
android:adjustViewBounds="true"
|
||||
android:cropToPadding="true"
|
||||
android:maxWidth="96dp"
|
||||
android:scaleType="fitCenter"
|
||||
android:background="@color/non_square_icon_background"
|
||||
tools:src="@tools:sample/avatars" />
|
||||
|
||||
<LinearLayout
|
||||
<RelativeLayout
|
||||
android:id="@+id/player_control"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginStart="16dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtvTitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
style="@style/Base.TextAppearance.AppCompat.Body1"
|
||||
tools:text="Episode title that is too long and will cause the text to wrap" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtvAuthor"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
style="@style/TextAppearance.AppCompat.Body1"
|
||||
tools:text="Episode author that is too long and will cause the text to wrap" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<ac.mdiq.podcini.ui.view.PlayButton
|
||||
android:id="@+id/butPlay"
|
||||
android:layout_width="52dp"
|
||||
android:layout_height="match_parent"
|
||||
android:contentDescription="@string/pause_label"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:scaleType="fitCenter"
|
||||
android:padding="8dp"
|
||||
app:srcCompat="@drawable/ic_play_48dp"
|
||||
tools:src="@drawable/ic_play_48dp" />
|
||||
android:layout_weight="1">
|
||||
|
||||
<ac.mdiq.podcini.ui.view.PlayButton
|
||||
android:id="@+id/butPlay"
|
||||
android:layout_width="@dimen/audioplayer_playercontrols_length_big"
|
||||
android:layout_height="@dimen/audioplayer_playercontrols_length_big"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_centerVertical="false"
|
||||
android:layout_marginStart="@dimen/audioplayer_playercontrols_margin"
|
||||
android:layout_marginLeft="@dimen/audioplayer_playercontrols_margin"
|
||||
android:layout_marginEnd="@dimen/audioplayer_playercontrols_margin"
|
||||
android:layout_marginRight="@dimen/audioplayer_playercontrols_margin"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="@string/pause_label"
|
||||
android:padding="8dp"
|
||||
android:scaleType="fitCenter"
|
||||
app:srcCompat="@drawable/ic_play_48dp"
|
||||
tools:srcCompat="@drawable/ic_play_48dp" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/butRev"
|
||||
android:layout_width="@dimen/audioplayer_playercontrols_length"
|
||||
android:layout_height="@dimen/audioplayer_playercontrols_length"
|
||||
android:layout_centerVertical="false"
|
||||
android:layout_marginStart="@dimen/audioplayer_playercontrols_margin"
|
||||
android:layout_marginLeft="@dimen/audioplayer_playercontrols_margin"
|
||||
android:layout_toStartOf="@id/butPlay"
|
||||
android:layout_toLeftOf="@id/butPlay"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="@string/rewind_label"
|
||||
android:scaleType="fitCenter"
|
||||
app:srcCompat="@drawable/ic_fast_rewind"
|
||||
tools:srcCompat="@drawable/ic_fast_rewind" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtvRev"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/butRev"
|
||||
android:layout_alignStart="@id/butRev"
|
||||
android:layout_alignLeft="@id/butRev"
|
||||
android:layout_alignEnd="@id/butRev"
|
||||
android:layout_alignRight="@id/butRev"
|
||||
android:clickable="false"
|
||||
android:gravity="center"
|
||||
android:text="30"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="12sp" />
|
||||
|
||||
<ac.mdiq.podcini.ui.common.PlaybackSpeedIndicatorView
|
||||
android:id="@+id/butPlaybackSpeed"
|
||||
android:layout_width="@dimen/audioplayer_playercontrols_length"
|
||||
android:layout_height="@dimen/audioplayer_playercontrols_length"
|
||||
android:layout_centerVertical="false"
|
||||
android:layout_marginEnd="@dimen/audioplayer_playercontrols_margin"
|
||||
android:layout_marginRight="@dimen/audioplayer_playercontrols_margin"
|
||||
android:layout_toStartOf="@id/butRev"
|
||||
android:layout_toLeftOf="@id/butRev"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="@string/playback_speed"
|
||||
app:foregroundColor="?attr/action_icon_color"
|
||||
tools:srcCompat="@drawable/ic_playback_speed" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtvPlaybackSpeed"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/butPlaybackSpeed"
|
||||
android:layout_alignStart="@id/butPlaybackSpeed"
|
||||
android:layout_alignLeft="@id/butPlaybackSpeed"
|
||||
android:layout_alignEnd="@id/butPlaybackSpeed"
|
||||
android:layout_alignRight="@id/butPlaybackSpeed"
|
||||
android:clickable="false"
|
||||
android:gravity="center"
|
||||
android:text="1.00"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="12sp" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/butFF"
|
||||
android:layout_width="@dimen/audioplayer_playercontrols_length"
|
||||
android:layout_height="@dimen/audioplayer_playercontrols_length"
|
||||
android:layout_centerVertical="false"
|
||||
android:layout_marginEnd="@dimen/audioplayer_playercontrols_margin"
|
||||
android:layout_marginRight="@dimen/audioplayer_playercontrols_margin"
|
||||
android:layout_toEndOf="@id/butPlay"
|
||||
android:layout_toRightOf="@id/butPlay"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="@string/fast_forward_label"
|
||||
android:scaleType="fitCenter"
|
||||
app:srcCompat="@drawable/ic_fast_forward"
|
||||
tools:srcCompat="@drawable/ic_fast_forward" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtvFF"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/butFF"
|
||||
android:layout_alignStart="@id/butFF"
|
||||
android:layout_alignLeft="@id/butFF"
|
||||
android:layout_alignEnd="@id/butFF"
|
||||
android:layout_alignRight="@id/butFF"
|
||||
android:clickable="false"
|
||||
android:gravity="center"
|
||||
android:text="30"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="12sp" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/butSkip"
|
||||
android:layout_width="@dimen/audioplayer_playercontrols_length"
|
||||
android:layout_height="@dimen/audioplayer_playercontrols_length"
|
||||
android:layout_centerVertical="false"
|
||||
android:layout_marginStart="@dimen/audioplayer_playercontrols_margin"
|
||||
android:layout_marginLeft="@dimen/audioplayer_playercontrols_margin"
|
||||
android:layout_toEndOf="@id/butFF"
|
||||
android:layout_toRightOf="@id/butFF"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="@string/skip_episode_label"
|
||||
android:scaleType="fitCenter"
|
||||
app:srcCompat="@drawable/ic_skip_48dp"
|
||||
tools:srcCompat="@drawable/ic_skip_48dp" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/episodeProgress"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="4dp"
|
||||
android:indeterminate="false"
|
||||
style="?android:attr/progressBarStyleHorizontal"
|
||||
tools:progress="100" />
|
||||
|
||||
</LinearLayout>
|
||||
|
|
|
@ -31,6 +31,33 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp" />
|
||||
|
||||
<RadioGroup
|
||||
android:id="@+id/propertyOf"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:visibility="gone">
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/for_episode"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/Episode"
|
||||
android:checked="true"/>
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/for_podcast"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/Podcast" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/for_all"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/All" />
|
||||
</RadioGroup>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<dimen name="drawer_corner_size">16dp</dimen>
|
||||
<dimen name="widget_margin">0dp</dimen>
|
||||
<dimen name="widget_inner_radius">4dp</dimen>
|
||||
<dimen name="external_player_height">64dp</dimen>
|
||||
<dimen name="external_player_height">100dp</dimen>
|
||||
<dimen name="text_size_micro">12sp</dimen>
|
||||
<dimen name="text_size_small">14sp</dimen>
|
||||
<dimen name="text_size_navdrawer">16sp</dimen>
|
||||
|
@ -27,7 +27,7 @@
|
|||
|
||||
<dimen name="audioplayer_playercontrols_length">48dp</dimen>
|
||||
<dimen name="audioplayer_playercontrols_length_big">64dp</dimen>
|
||||
<dimen name="audioplayer_playercontrols_margin">12dp</dimen>
|
||||
<dimen name="audioplayer_playercontrols_margin">4dp</dimen>
|
||||
|
||||
<dimen name="nav_drawer_max_screen_size">480dp</dimen>
|
||||
</resources>
|
||||
|
|
|
@ -824,4 +824,7 @@
|
|||
<string name="shortcut_subscription_label">Subscription shortcut</string>
|
||||
<string name="shortcut_select_subscription">Select subscription</string>
|
||||
<string name="add_shortcut">Add shortcut</string>
|
||||
<string name="Episode">Episode</string>
|
||||
<string name="Podcast">Podcast</string>
|
||||
<string name="All">All</string>
|
||||
</resources>
|
||||
|
|
Loading…
Reference in New Issue