6.1.4 commit

This commit is contained in:
Xilin Jia 2024-07-23 22:09:20 +01:00
parent fab805b3f1
commit 08dfdf8bb1
18 changed files with 288 additions and 77 deletions

View File

@ -126,8 +126,8 @@ android {
buildConfig true
}
defaultConfig {
versionCode 3020217
versionName "6.1.3"
versionCode 3020218
versionName "6.1.4"
applicationId "ac.mdiq.podcini.R"
def commit = ""

View File

@ -221,7 +221,7 @@ abstract class MediaPlayerBase protected constructor(protected val context: Cont
*
* @return a Future, just for the purpose of tracking its execution.
*/
protected abstract fun endPlayback(hasEnded: Boolean, wasSkipped: Boolean, shouldContinue: Boolean, toStoppedState: Boolean)
internal abstract fun endPlayback(hasEnded: Boolean, wasSkipped: Boolean, shouldContinue: Boolean, toStoppedState: Boolean)
/**
* @return `true` if the WifiLock feature should be used, `false` otherwise.

View File

@ -6,6 +6,7 @@ import ac.mdiq.podcini.net.utils.NetworkUtils.isAllowMobileStreaming
import ac.mdiq.podcini.net.utils.NetworkUtils.isStreamingAllowed
import ac.mdiq.podcini.playback.PlaybackServiceStarter
import ac.mdiq.podcini.playback.base.InTheatre
import ac.mdiq.podcini.playback.base.InTheatre.curEpisode
import ac.mdiq.podcini.playback.base.InTheatre.curMedia
import ac.mdiq.podcini.playback.base.InTheatre.curQueue
import ac.mdiq.podcini.playback.base.InTheatre.curState
@ -37,8 +38,8 @@ import ac.mdiq.podcini.storage.database.Episodes.shouldDeleteRemoveFromQueue
import ac.mdiq.podcini.storage.database.Feeds.shouldAutoDeleteItem
import ac.mdiq.podcini.storage.database.Queues.addToQueue
import ac.mdiq.podcini.storage.database.Queues.removeFromQueueSync
import ac.mdiq.podcini.storage.database.RealmDB.realm
import ac.mdiq.podcini.storage.database.RealmDB.runOnIOScope
import ac.mdiq.podcini.storage.database.RealmDB.unmanaged
import ac.mdiq.podcini.storage.database.RealmDB.upsertBlk
import ac.mdiq.podcini.storage.model.*
import ac.mdiq.podcini.storage.model.CurrentState.Companion.NO_MEDIA_PLAYING
@ -81,7 +82,6 @@ import androidx.work.impl.utils.futures.SettableFuture
import com.google.common.collect.ImmutableList
import com.google.common.util.concurrent.Futures
import com.google.common.util.concurrent.ListenableFuture
import io.realm.kotlin.ext.isManaged
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.collectLatest
import java.util.*
@ -361,8 +361,17 @@ class PlaybackService : MediaSessionService() {
writeNoMediaPlaying()
return null
}
val nextItem = getNextInQueue(item)
if (nextItem?.media == null) {
// val nextItem = getNextInQueue(item)
if (curQueue.episodes.isEmpty()) {
Logd(TAG, "getNextInQueue queue is empty")
writeNoMediaPlaying()
return null
}
val i = curQueue.episodes.indexOf(item)
var j = 0
if (i >= 0 && i < curQueue.episodes.size-1) j = i+1
val nextItem = unmanaged(curQueue.episodes[j])
if (nextItem.media == null) {
Logd(TAG, "getNextInQueue nextItem: $nextItem media is null")
writeNoMediaPlaying()
return null
@ -384,17 +393,17 @@ class PlaybackService : MediaSessionService() {
return nextItem.media
}
private fun getNextInQueue(episode: Episode): Episode? {
Logd(TAG, "getNextInQueue() with: itemId ${episode.id}")
if (curQueue.episodes.isEmpty()) return null
val i = curQueue.episodes.indexOf(episode)
var j = 0
if (i >= 0 && i < curQueue.episodes.size-1) j = i+1
val itemNew = curQueue.episodes[j]
return if (itemNew.isManaged()) realm.copyFromRealm(itemNew) else itemNew
}
// private fun getNextInQueue(episode: Episode): Episode? {
// Logd(TAG, "getNextInQueue() with: itemId ${episode.id}")
// if (curQueue.episodes.isEmpty()) return null
//
// val i = curQueue.episodes.indexOf(episode)
// var j = 0
// if (i >= 0 && i < curQueue.episodes.size-1) j = i+1
//
// val itemNew = curQueue.episodes[j]
// return unmanaged(itemNew)
// }
override fun findMedia(url: String): Playable? {
val item = getEpisodeByGuidOrUrl(null, url)
@ -918,6 +927,7 @@ class PlaybackService : MediaSessionService() {
EventFlow.events.collectLatest { event ->
Logd(TAG, "Received event: ${event.TAG}")
when (event) {
is FlowEvent.QueueEvent -> onQueueEvent(event)
is FlowEvent.PlayerErrorEvent -> onPlayerError(event)
is FlowEvent.BufferUpdateEvent -> onBufferUpdate(event)
is FlowEvent.SleepTimerUpdatedEvent -> onSleepTimerUpdate(event)
@ -925,12 +935,53 @@ class PlaybackService : MediaSessionService() {
is FlowEvent.FeedPrefsChangeEvent -> onFeedPrefsChanged(event)
// is FlowEvent.SkipIntroEndingChangedEvent -> skipIntroEndingPresetChanged(event)
is FlowEvent.PlayEvent -> currentitem = event.episode
is FlowEvent.EpisodeMediaEvent -> onEpisodeMediaEvent(event)
else -> {}
}
}
}
}
private fun onEpisodeMediaEvent(event: FlowEvent.EpisodeMediaEvent) {
if (event.action == FlowEvent.EpisodeMediaEvent.Action.REMOVED) {
for (e in event.episodes) {
if (e.id == curEpisode?.id) {
curEpisode = unmanaged(e)
curMedia = curEpisode!!.media
mPlayer?.endPlayback(hasEnded = false, wasSkipped = true, shouldContinue = true, toStoppedState = true)
break
}
}
}
}
private fun onQueueEvent(event: FlowEvent.QueueEvent) {
if (event.action == FlowEvent.QueueEvent.Action.REMOVED) {
for (e in event.episodes) {
if (e.id == curEpisode?.id) {
mPlayer?.endPlayback(hasEnded = false, wasSkipped = true, shouldContinue = true, toStoppedState = true)
break
}
}
}
}
// private fun onVolumeAdaptionChanged(event: FlowEvent.VolumeAdaptionChangedEvent) {
// if (mPlayer != null) updateVolumeIfNecessary(mPlayer!!, event.feedId, event.volumeAdaptionSetting)
// }
private fun onFeedPrefsChanged(event: FlowEvent.FeedPrefsChangeEvent) {
val item = (curMedia as? EpisodeMedia)?.episode ?: currentitem
if (item?.feed?.id == event.feed.id) {
item.feed = null
// seems no need to pause??
// if (MediaPlayerBase.status == PlayerStatus.PLAYING) {
// mPlayer?.pause(abandonFocus = false, reinit = false)
// mPlayer?.resume()
// }
}
}
private fun onPlayerError(event: FlowEvent.PlayerErrorEvent) {
if (MediaPlayerBase.status == PlayerStatus.PLAYING || MediaPlayerBase.status == PlayerStatus.FALLBACK)
mPlayer!!.pause(abandonFocus = true, reinit = false)
@ -1062,22 +1113,6 @@ class PlaybackService : MediaSessionService() {
}
}
// private fun onVolumeAdaptionChanged(event: FlowEvent.VolumeAdaptionChangedEvent) {
// if (mPlayer != null) updateVolumeIfNecessary(mPlayer!!, event.feedId, event.volumeAdaptionSetting)
// }
private fun onFeedPrefsChanged(event: FlowEvent.FeedPrefsChangeEvent) {
val item = (curMedia as? EpisodeMedia)?.episode ?: currentitem
if (item?.feed?.id == event.feed.id) {
item.feed = null
// seems no need to pause??
// if (MediaPlayerBase.status == PlayerStatus.PLAYING) {
// mPlayer?.pause(abandonFocus = false, reinit = false)
// mPlayer?.resume()
// }
}
}
enum class NotificationCustomButton(val customAction: String, val commandButton: CommandButton) {
SKIP(
customAction = CUSTOM_COMMAND_SKIP_ACTION_ID,

View File

@ -164,7 +164,7 @@ object Episodes {
val action = EpisodeAction.Builder(episode, EpisodeAction.DELETE).currentTimestamp().build()
SynchronizationQueueSink.enqueueEpisodeActionIfSyncActive(context, action)
}
EventFlow.postEvent(FlowEvent.EpisodeEvent.updated(episode))
EventFlow.postEvent(FlowEvent.EpisodeMediaEvent.removed(episode))
}
return episode
}
@ -223,7 +223,7 @@ object Episodes {
if (episode != null) {
episode.media = media
episode = upsert(episode) {}
EventFlow.postEvent(FlowEvent.EpisodeEvent.updated(episode))
EventFlow.postEvent(FlowEvent.EpisodeMediaEvent.updated(episode))
} else Log.e(TAG, "persistEpisodeMedia media.episode is null")
}
}

View File

@ -344,7 +344,7 @@ class EpisodeMedia: EmbeddedRealmObject, Playable {
hasEmbeddedPicture = false
}
}
upsertBlk(episode!!) {}
if (episode != null) upsertBlk(episode!!) {}
}
override fun equals(other: Any?): Boolean {

View File

@ -22,6 +22,7 @@ import android.app.Activity
import android.content.DialogInterface
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.widget.RadioButton
import androidx.annotation.PluralsRes
import androidx.media3.common.util.UnstableApi
@ -118,6 +119,8 @@ class EpisodeMultiSelectHandler(private val activity: MainActivity, private val
fun show() {
val activity = activityRef.get() ?: return
val binding = SelectQueueDialogBinding.inflate(LayoutInflater.from(activity))
binding.removeCheckbox.visibility = View.VISIBLE
val queues = realm.query(PlayQueue::class).find()
for (i in queues.indices) {
val radioButton = RadioButton(activity)
@ -137,6 +140,7 @@ class EpisodeMultiSelectHandler(private val activity: MainActivity, private val
.setTitle(R.string.put_in_queue_label)
.setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int ->
val queues = realm.query(PlayQueue::class).find()
if (binding.removeCheckbox.isChecked) {
val toRemove = mutableSetOf<Long>()
val toRemoveCur = mutableListOf<Episode>()
items.forEach { e ->
@ -152,6 +156,7 @@ class EpisodeMultiSelectHandler(private val activity: MainActivity, private val
}
if (toRemove.isNotEmpty()) runBlocking { removeFromAllQueuesQuiet(toRemove.toList()) }
if (toRemoveCur.isNotEmpty()) EventFlow.postEvent(FlowEvent.QueueEvent.removed(toRemoveCur))
}
items.forEach { e ->
runBlocking { addToQueueSync(false, e, toQueue) }
}

View File

@ -63,7 +63,7 @@ class PlayActionButton(item: Episode) : EpisodeActionButton(item) {
episode.media?.downloaded = false
episode.media?.fileUrl = null
upsertBlk(episode) {}
EventFlow.postEvent(FlowEvent.EpisodeEvent.updated(episode))
EventFlow.postEvent(FlowEvent.EpisodeMediaEvent.removed(episode))
}
EventFlow.postEvent(FlowEvent.MessageEvent(context.getString(R.string.error_file_not_found)))
}

View File

@ -279,6 +279,7 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
override fun onResume() {
Logd(TAG, "onResume() isCollapsed: $isCollapsed")
super.onResume()
loadMediaInfo(false)
}
override fun onStart() {

View File

@ -329,11 +329,30 @@ import kotlinx.coroutines.flow.collectLatest
for (item in event.episodes) {
val pos: Int = EpisodeUtil.indexOfItemWithId(episodes, item.id)
if (pos >= 0) {
episodes.removeAt(pos)
if (getFilter().matches(item)) {
episodes.add(pos, item)
episodes[pos] = item
adapter.notifyItemChangedCompat(pos)
} else adapter.notifyItemRemoved(pos)
} else {
episodes.removeAt(pos)
adapter.notifyItemRemoved(pos)
}
}
}
}
private fun onEpisodeMediaEvent(event: FlowEvent.EpisodeMediaEvent) {
// Logd(TAG, "onEventMainThread() called with ${event.TAG}")
for (item in event.episodes) {
val pos: Int = EpisodeUtil.indexOfItemWithId(episodes, item.id)
if (pos >= 0) {
episodes[pos] = unmanaged(episodes[pos])
episodes[pos].media = item.media
if (getFilter().matches(item)) {
adapter.notifyItemChangedCompat(pos)
} else {
// episodes.removeAt(pos)
// adapter.notifyItemRemoved(pos)
}
}
}
}
@ -387,6 +406,7 @@ import kotlinx.coroutines.flow.collectLatest
is FlowEvent.FeedListEvent, is FlowEvent.EpisodePlayedEvent, is FlowEvent.PlayerSettingsEvent, is FlowEvent.FavoritesEvent -> loadItems()
is FlowEvent.PlaybackPositionEvent -> onPlaybackPositionEvent(event)
is FlowEvent.EpisodeEvent -> onEpisodeEvent(event)
is FlowEvent.EpisodeMediaEvent -> onEpisodeMediaEvent(event)
else -> {}
}
}

View File

@ -218,6 +218,7 @@ import java.util.*
Logd(TAG, "Received event: ${event.TAG}")
when (event) {
is FlowEvent.EpisodeEvent -> onEpisodeEvent(event)
is FlowEvent.EpisodeMediaEvent -> onEpisodeMediaEvent(event)
is FlowEvent.PlaybackPositionEvent -> onPlaybackPositionEvent(event)
is FlowEvent.FavoritesEvent -> onFavoriteEvent(event)
is FlowEvent.PlayerSettingsEvent -> loadItems()
@ -290,13 +291,29 @@ import java.util.*
if (pos >= 0) {
episodes.removeAt(pos)
val media = item.media
if (media != null && media.downloaded) {
episodes.add(pos, item)
// adapter.notifyItemChangedCompat(pos)
} else {
// adapter.notifyItemRemoved(pos)
if (media != null && media.downloaded) episodes.add(pos, item)
}
}
// have to do this as adapter.notifyItemRemoved(pos) when pos == 0 causes crash
if (size > 0) {
// adapter.setDummyViews(0)
adapter.updateItems(episodes)
}
refreshInfoBar()
}
private fun onEpisodeMediaEvent(event: FlowEvent.EpisodeMediaEvent) {
// Logd(TAG, "onEpisodeEvent() called with ${event.TAG}")
var i = 0
val size: Int = event.episodes.size
while (i < size) {
val item: Episode = event.episodes[i++]
val pos = EpisodeUtil.indexOfItemWithId(episodes, item.id)
if (pos >= 0) {
episodes.removeAt(pos)
val media = item.media
if (media != null && media.downloaded) episodes.add(pos, item)
}
}
// have to do this as adapter.notifyItemRemoved(pos) when pos == 0 causes crash
if (size > 0) {

View File

@ -335,6 +335,7 @@ import java.util.concurrent.Semaphore
var i = 0
val size: Int = event.episodes.size
val poses: MutableList<Int> = mutableListOf()
while (i < size) {
val item = event.episodes[i++]
if (item.feedId != feed!!.id) continue
@ -342,9 +343,43 @@ import java.util.concurrent.Semaphore
if (pos >= 0) {
Logd(TAG, "episode event: ${item.title} ${item.playState} ${item.isDownloaded}")
episodes[pos] = item
adapter.notifyItemChangedCompat(pos)
poses.add(pos)
}
}
if (poses.size == 1) {
if (filterOutEpisode(episodes[poses[0]])) adapter.updateItems(episodes)
else adapter.notifyItemChangedCompat(poses[0])
} else if (poses.size > 1) {
redoFilter()
adapter.notifyDataSetChanged()
}
}
private fun onEpisodeMediaEvent(event: FlowEvent.EpisodeMediaEvent) {
// Logd(TAG, "onEpisodeEvent() called with ${event.TAG}")
if (feed == null || episodes.isEmpty()) return
var i = 0
val size: Int = event.episodes.size
val poses: MutableList<Int> = mutableListOf()
while (i < size) {
val item = event.episodes[i++]
if (item.feedId != feed!!.id) continue
val pos: Int = EpisodeUtil.indexOfItemWithId(episodes, item.id)
if (pos >= 0) {
Logd(TAG, "episode event: ${item.title} ${item.playState} ${item.isDownloaded}")
episodes[pos] = unmanaged(episodes[pos])
episodes[pos].media = item.media
poses.add(pos)
}
}
if (poses.size == 1) {
if (filterOutEpisode(episodes[poses[0]])) adapter.updateItems(episodes)
else adapter.notifyItemChangedCompat(poses[0])
} else if (poses.size > 1) {
redoFilter()
adapter.notifyDataSetChanged()
}
}
private fun onQueueEvent(event: FlowEvent.QueueEvent) {
@ -363,7 +398,10 @@ import java.util.concurrent.Semaphore
Logd(TAG, "onPlayEvent ${event.episode.title}")
if (feed != null) {
val pos: Int = EpisodeUtil.indexOfItemWithId(episodes, event.episode.id)
if (pos >= 0) adapter.notifyItemChangedCompat(pos)
if (pos >= 0) {
if (filterOutEpisode(event.episode)) adapter.updateItems(episodes)
else adapter.notifyItemChangedCompat(pos)
}
}
}
@ -373,15 +411,11 @@ import java.util.concurrent.Semaphore
val pos: Int = EpisodeUtil.indexOfItemWithId(episodes, item.id)
if (pos >= 0) {
Logd(TAG, "played item: ${item.title} ${item.playState}")
if (enableFilter && ((feed!!.episodeFilter.showUnplayed && item.isPlayed()) || feed!!.episodeFilter.showPlayed && !item.isPlayed())) {
episodes.removeAt(pos)
adapter.updateItems(episodes)
} else {
if (filterOutEpisode(item)) adapter.updateItems(episodes)
else {
episodes[pos] = item
adapter.notifyItemChangedCompat(pos)
}
// episodes[pos].playState = item.playState
// adapter.notifyItemChangedCompat(pos)
}
}
@ -391,8 +425,8 @@ import java.util.concurrent.Semaphore
if (pos >= 0) {
episodes[pos] = unmanaged(episodes[pos])
episodes[pos].isFavorite = item.isFavorite
// episodes[pos] = item
adapter.notifyItemChangedCompat(pos)
if (filterOutEpisode(item)) adapter.updateItems(episodes)
else adapter.notifyItemChangedCompat(pos)
}
}
@ -404,7 +438,8 @@ import java.util.concurrent.Semaphore
val pos: Int = EpisodeUtil.indexOfItemWithDownloadUrl(episodes, downloadUrl)
if (pos >= 0) {
// TODO: need a better way
adapter.notifyItemChangedCompat(pos)
if (filterOutEpisode(episodes[pos])) adapter.updateItems(episodes)
else adapter.notifyItemChangedCompat(pos)
}
}
}
@ -445,6 +480,7 @@ import java.util.concurrent.Semaphore
is FlowEvent.PlaybackPositionEvent -> onPlaybackPositionEvent(event)
is FlowEvent.FeedPrefsChangeEvent -> if (feed?.id == event.feed.id) loadItems()
is FlowEvent.EpisodeEvent -> onEpisodeEvent(event)
is FlowEvent.EpisodeMediaEvent -> onEpisodeMediaEvent(event)
is FlowEvent.PlayerSettingsEvent -> loadItems()
is FlowEvent.EpisodePlayedEvent -> onEpisodePlayedEvent(event)
is FlowEvent.FeedListEvent -> if (feed != null && event.contains(feed!!)) loadItems()
@ -614,6 +650,22 @@ import java.util.concurrent.Semaphore
while (loadItemsRunning) Thread.sleep(50)
}
private fun filterOutEpisode(episode: Episode): Boolean {
if (enableFilter && !feed?.preferences?.filterString.isNullOrEmpty() && !feed!!.episodeFilter.matches(episode)) {
episodes.remove(episode)
return true
}
return false
}
private fun redoFilter() {
if (enableFilter && !feed?.preferences?.filterString.isNullOrEmpty()) {
val episodes_ = episodes.toList()
episodes.clear()
episodes.addAll(episodes_.filter { feed!!.episodeFilter.matches(it) })
}
}
@UnstableApi
private fun loadItems() {
if (!loadItemsRunning) {

View File

@ -355,7 +355,7 @@ class FeedSettingsFragment : Fragment() {
}
}
private fun setupAutoDownloadGlobalPreference() {
if (!isEnableAutodownload) {
if (!isEnableAutodownload || feedPrefs?.autoDownload != true) {
val autodl = findPreference<SwitchPreferenceCompat>(Prefs.autoDownload.name)
autodl!!.isChecked = false
autodl.isEnabled = false

View File

@ -228,6 +228,7 @@ import java.util.*
when (event) {
is FlowEvent.QueueEvent -> onQueueEvent(event)
is FlowEvent.EpisodeEvent -> onEpisodeEvent(event)
is FlowEvent.EpisodeMediaEvent -> onEpisodeMediaEvent(event)
is FlowEvent.FavoritesEvent -> onFavoriteEvent(event)
is FlowEvent.PlayEvent -> onPlayEvent(event)
is FlowEvent.PlaybackPositionEvent -> onPlaybackPositionEvent(event)
@ -320,15 +321,45 @@ import java.util.*
}
var i = 0
val size: Int = event.episodes.size
val poses: MutableList<Int> = mutableListOf()
while (i < size) {
val item: Episode = event.episodes[i++]
val pos: Int = EpisodeUtil.indexOfItemWithId(queueItems, item.id)
if (pos >= 0) {
queueItems[pos] = item
adapter?.notifyItemChangedCompat(pos)
poses.add(pos)
}
}
if (poses.isNotEmpty()) {
if (poses.size == 1) adapter?.notifyItemChangedCompat(poses[0])
else adapter?.notifyDataSetChanged()
refreshInfoBar()
}
}
private fun onEpisodeMediaEvent(event: FlowEvent.EpisodeMediaEvent) {
// Logd(TAG, "onEventMainThread() called with ${event.TAG}")
if (adapter == null) {
loadItems(true)
return
}
var i = 0
val size: Int = event.episodes.size
val poses: MutableList<Int> = mutableListOf()
while (i < size) {
val item: Episode = event.episodes[i++]
val pos: Int = EpisodeUtil.indexOfItemWithId(queueItems, item.id)
if (pos >= 0) {
queueItems[pos] = unmanaged(queueItems[pos])
queueItems[pos].media = item.media
poses.add(pos)
}
}
if (poses.isNotEmpty()) {
if (poses.size == 1) adapter?.notifyItemChangedCompat(poses[0])
else adapter?.notifyDataSetChanged()
refreshInfoBar()
}
}
private fun onPlayEvent(event: FlowEvent.PlayEvent) {

View File

@ -7,6 +7,7 @@ import ac.mdiq.podcini.storage.model.Episode
import ac.mdiq.podcini.storage.model.Playable
import ac.mdiq.podcini.storage.model.EpisodeSortOrder
import ac.mdiq.podcini.util.Logd
import ac.mdiq.podcini.util.event.FlowEvent.FeedListEvent.Action
import android.content.Context
import android.view.KeyEvent
import androidx.core.util.Consumer
@ -173,6 +174,24 @@ sealed class FlowEvent {
}
}
data class EpisodeMediaEvent(val action: Action, val episodes: List<Episode>) : FlowEvent() {
enum class Action { ADDED, REMOVED, UPDATED, ERROR, UNKNOWN }
companion object {
fun added(vararg episodes: Episode): EpisodeMediaEvent {
return EpisodeMediaEvent(Action.ADDED, listOf(*episodes))
}
fun updated(episodes: List<Episode>): EpisodeMediaEvent {
return EpisodeMediaEvent(Action.UPDATED, episodes)
}
fun updated(vararg episodes: Episode): EpisodeMediaEvent {
return EpisodeMediaEvent(Action.UPDATED, listOf(*episodes))
}
fun removed(vararg episodes: Episode): EpisodeMediaEvent {
return EpisodeMediaEvent(Action.REMOVED, listOf(*episodes))
}
}
}
data class FeedTagsChangedEvent(val dummy: Unit = Unit) : FlowEvent()
data class FeedUpdatingEvent(val isRunning: Boolean) : FlowEvent()

View File

@ -11,7 +11,15 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="30dp">
</RadioGroup>
<CheckBox
android:id="@+id/remove_checkbox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:checked="true"
android:visibility="gone"
android:text="@string/remove_from_other_queues" />
</LinearLayout>

View File

@ -146,7 +146,8 @@
<string name="feed_auto_download_newer">Newest unplayed</string>
<string name="feed_auto_download_older">Oldest unplayed</string>
<string name="put_in_queue_label">Put in queue</string>
<string name="put_in_queue_label">Add to queue...</string>
<string name="remove_from_other_queues">Remove from other queues</string>
<string name="feed_new_episodes_action_nothing">Nothing</string>
<string name="episode_cleanup_never">Never</string>
@ -262,12 +263,12 @@
<item quantity="one">%d episode marked as unplayed.</item>
<item quantity="other">%d episodes marked as unplayed.</item>
</plurals>
<string name="add_to_queue_label">Add to queue</string>
<string name="add_to_queue_label">Add to active queue</string>
<plurals name="added_to_queue_batch_label">
<item quantity="one">%d episode added to queue.</item>
<item quantity="other">%d episodes added to queue.</item>
</plurals>
<string name="remove_from_queue_label">Remove from queue</string>
<string name="remove_from_queue_label">Remove from active queue</string>
<plurals name="removed_from_queue_batch_label">
<item quantity="one">%d episode removed from queue.</item>
<item quantity="other">%d episodes removed from queue.</item>

View File

@ -1,3 +1,14 @@
# 6.1.4
* fixed issue of "mark excluded episodes played" checkbox not being reflected from the setting
* in FeedSetting, fixed issue of auto-download options being enabled even if auto-download is not enabled
* "Put in queue" changed to "Add to queue..." and added checkbox for removing from other queues
* refined some handling on events
* fixed issue of deleted episodes not being correctly handled in some lists
* fixed issue of current media kept being played when removed or when removed from queue
* in FeedEpisodes view, fixed (mostly) issue of not being promptly filtered when an episode state changes
* when screen is turned back on during playback, PlayerUI is promptly updated
# 6.1.3
* added feed setting in the header of FeedInfo view

View File

@ -0,0 +1,11 @@
Version 6.1.4 brings several changes:
* fixed issue of "mark excluded episodes played" checkbox not being reflected from the setting
* in FeedSetting, fixed issue of auto-download options being enabled even if auto-download is not enabled
* "Put in queue" changed to "Add to queue..." and added checkbox for removing from other queues
* refined some handling on events
* fixed issue of deleted episodes not being correctly handled in some lists
* fixed issue of current media kept being played when removed or when removed from queue
* in FeedEpisodes view, fixed (mostly) issue of not being promptly filtered when an episode state changes
* when screen is turned back on during playback, PlayerUI is promptly updated