6.13.2 commit

This commit is contained in:
Xilin Jia 2024-10-31 08:52:45 +01:00
parent fc902569cd
commit 6c86c2ee48
49 changed files with 302 additions and 260 deletions

View File

@ -31,8 +31,8 @@ android {
// testApplicationId "ac.mdiq.podcini.tests" // testApplicationId "ac.mdiq.podcini.tests"
// testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" // testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
versionCode 3020288 versionCode 3020289
versionName "6.13.1" versionName "6.13.2"
applicationId "ac.mdiq.podcini.R" applicationId "ac.mdiq.podcini.R"
def commit = "" def commit = ""
@ -167,6 +167,13 @@ android {
androidResources { androidResources {
additionalParameters "--no-version-vectors" additionalParameters "--no-version-vectors"
} }
dependenciesInfo {
// Disables dependency metadata when building APKs.
includeInApk = false
// Disables dependency metadata when building Android App Bundles.
includeInBundle = false
}
} }
dependencies { dependencies {

View File

@ -7,12 +7,15 @@ import ac.mdiq.podcini.net.sync.SynchronizationSettings.isProviderConnected
import ac.mdiq.podcini.net.sync.model.EpisodeAction import ac.mdiq.podcini.net.sync.model.EpisodeAction
import ac.mdiq.podcini.net.sync.queue.SynchronizationQueueSink import ac.mdiq.podcini.net.sync.queue.SynchronizationQueueSink
import ac.mdiq.podcini.net.utils.NetworkUtils.isAllowMobileEpisodeDownload import ac.mdiq.podcini.net.utils.NetworkUtils.isAllowMobileEpisodeDownload
import ac.mdiq.podcini.playback.base.InTheatre.curQueue
import ac.mdiq.podcini.preferences.UserPreferences import ac.mdiq.podcini.preferences.UserPreferences
import ac.mdiq.podcini.preferences.UserPreferences.appPrefs import ac.mdiq.podcini.preferences.UserPreferences.appPrefs
import ac.mdiq.podcini.storage.database.Episodes import ac.mdiq.podcini.storage.database.Episodes
import ac.mdiq.podcini.storage.database.LogsAndStats import ac.mdiq.podcini.storage.database.LogsAndStats
import ac.mdiq.podcini.storage.database.Queues import ac.mdiq.podcini.storage.database.Queues
import ac.mdiq.podcini.storage.database.Queues.removeFromQueueSync
import ac.mdiq.podcini.storage.database.RealmDB.realm import ac.mdiq.podcini.storage.database.RealmDB.realm
import ac.mdiq.podcini.storage.database.RealmDB.runOnIOScope
import ac.mdiq.podcini.storage.database.RealmDB.upsertBlk import ac.mdiq.podcini.storage.database.RealmDB.upsertBlk
import ac.mdiq.podcini.storage.model.DownloadResult import ac.mdiq.podcini.storage.model.DownloadResult
import ac.mdiq.podcini.storage.model.Episode import ac.mdiq.podcini.storage.model.Episode
@ -70,7 +73,7 @@ class DownloadServiceInterfaceImpl : DownloadServiceInterface() {
WorkManager.getInstance(context).enqueueUniqueWork(item.media!!.downloadUrl!!, ExistingWorkPolicy.KEEP, workRequest.build()) WorkManager.getInstance(context).enqueueUniqueWork(item.media!!.downloadUrl!!, ExistingWorkPolicy.KEEP, workRequest.build())
} }
@OptIn(UnstableApi::class) override fun cancel(context: Context, media: EpisodeMedia) { override fun cancel(context: Context, media: EpisodeMedia) {
Logd(TAG, "starting cancel") Logd(TAG, "starting cancel")
// This needs to be done here, not in the worker. Reason: The worker might or might not be running. // This needs to be done here, not in the worker. Reason: The worker might or might not be running.
val item_ = media.episodeOrFetch() val item_ = media.episodeOrFetch()
@ -84,7 +87,7 @@ class DownloadServiceInterfaceImpl : DownloadServiceInterface() {
workInfoList.forEach { workInfo -> workInfoList.forEach { workInfo ->
if (workInfo.tags.contains(WORK_DATA_WAS_QUEUED)) { if (workInfo.tags.contains(WORK_DATA_WAS_QUEUED)) {
val item_ = media.episodeOrFetch() val item_ = media.episodeOrFetch()
if (item_ != null) Queues.removeFromQueue(item_) if (item_ != null) runOnIOScope { removeFromQueueSync(curQueue, item_) }
} }
} }
WorkManager.getInstance(context).cancelAllWorkByTag(tag) WorkManager.getInstance(context).cancelAllWorkByTag(tag)

View File

@ -18,6 +18,7 @@ import ac.mdiq.podcini.net.sync.queue.SynchronizationQueueStorage
import ac.mdiq.podcini.net.utils.NetworkUtils.isAllowMobileFor import ac.mdiq.podcini.net.utils.NetworkUtils.isAllowMobileFor
import ac.mdiq.podcini.net.utils.NetworkUtils.setAllowMobileFor import ac.mdiq.podcini.net.utils.NetworkUtils.setAllowMobileFor
import ac.mdiq.podcini.net.utils.UrlChecker.containsUrl import ac.mdiq.podcini.net.utils.UrlChecker.containsUrl
import ac.mdiq.podcini.playback.base.InTheatre.curQueue
import ac.mdiq.podcini.preferences.UserPreferences import ac.mdiq.podcini.preferences.UserPreferences
import ac.mdiq.podcini.preferences.UserPreferences.appPrefs import ac.mdiq.podcini.preferences.UserPreferences.appPrefs
import ac.mdiq.podcini.storage.database.Episodes.getEpisodeByGuidOrUrl import ac.mdiq.podcini.storage.database.Episodes.getEpisodeByGuidOrUrl
@ -26,7 +27,7 @@ import ac.mdiq.podcini.storage.database.Feeds.deleteFeedSync
import ac.mdiq.podcini.storage.database.Feeds.getFeedList import ac.mdiq.podcini.storage.database.Feeds.getFeedList
import ac.mdiq.podcini.storage.database.Feeds.getFeedListDownloadUrls import ac.mdiq.podcini.storage.database.Feeds.getFeedListDownloadUrls
import ac.mdiq.podcini.storage.database.Feeds.updateFeed import ac.mdiq.podcini.storage.database.Feeds.updateFeed
import ac.mdiq.podcini.storage.database.Queues.removeFromQueue import ac.mdiq.podcini.storage.database.Queues.removeFromQueueSync
import ac.mdiq.podcini.storage.database.RealmDB.runOnIOScope import ac.mdiq.podcini.storage.database.RealmDB.runOnIOScope
import ac.mdiq.podcini.storage.database.RealmDB.upsert import ac.mdiq.podcini.storage.database.RealmDB.upsert
import ac.mdiq.podcini.storage.model.Episode import ac.mdiq.podcini.storage.model.Episode
@ -289,8 +290,10 @@ open class SyncService(context: Context, params: WorkerParameters) : Worker(cont
// if (result.first != null) queueToBeRemoved.add(result.second) // if (result.first != null) queueToBeRemoved.add(result.second)
updatedItems.add(result.second) updatedItems.add(result.second)
} }
removeFromQueue(*updatedItems.toTypedArray()) // removeFromQueue(*updatedItems.toTypedArray())
runOnIOScope { runOnIOScope {
removeFromQueueSync(curQueue, *updatedItems.toTypedArray())
for (episode in updatedItems) { for (episode in updatedItems) {
upsert(episode) {} upsert(episode) {}
} }

View File

@ -34,14 +34,16 @@ import ac.mdiq.podcini.preferences.UserPreferences.rewindSecs
import ac.mdiq.podcini.receiver.MediaButtonReceiver import ac.mdiq.podcini.receiver.MediaButtonReceiver
import ac.mdiq.podcini.storage.database.Episodes.deleteMediaSync import ac.mdiq.podcini.storage.database.Episodes.deleteMediaSync
import ac.mdiq.podcini.storage.database.Episodes.getEpisodeByGuidOrUrl import ac.mdiq.podcini.storage.database.Episodes.getEpisodeByGuidOrUrl
import ac.mdiq.podcini.storage.database.Episodes.setCompletionDate import ac.mdiq.podcini.storage.database.Episodes.prefDeleteRemovesFromQueue
import ac.mdiq.podcini.storage.database.Episodes.prefRemoveFromQueueMarkedPlayed
import ac.mdiq.podcini.storage.database.Episodes.setPlayStateSync import ac.mdiq.podcini.storage.database.Episodes.setPlayStateSync
import ac.mdiq.podcini.storage.database.Episodes.shouldDeleteRemoveFromQueue import ac.mdiq.podcini.storage.database.Feeds.allowForAutoDelete
import ac.mdiq.podcini.storage.database.Feeds.shouldAutoDeleteItem import ac.mdiq.podcini.storage.database.Queues.removeFromAllQueuesSync
import ac.mdiq.podcini.storage.database.Queues.removeFromQueueSync import ac.mdiq.podcini.storage.database.Queues.removeFromQueueSync
import ac.mdiq.podcini.storage.database.RealmDB.realm import ac.mdiq.podcini.storage.database.RealmDB.realm
import ac.mdiq.podcini.storage.database.RealmDB.runOnIOScope import ac.mdiq.podcini.storage.database.RealmDB.runOnIOScope
import ac.mdiq.podcini.storage.database.RealmDB.unmanaged import ac.mdiq.podcini.storage.database.RealmDB.unmanaged
import ac.mdiq.podcini.storage.database.RealmDB.upsert
import ac.mdiq.podcini.storage.database.RealmDB.upsertBlk import ac.mdiq.podcini.storage.database.RealmDB.upsertBlk
import ac.mdiq.podcini.storage.model.* import ac.mdiq.podcini.storage.model.*
import ac.mdiq.podcini.storage.model.CurrentState.Companion.NO_MEDIA_PLAYING import ac.mdiq.podcini.storage.model.CurrentState.Companion.NO_MEDIA_PLAYING
@ -218,7 +220,9 @@ class PlaybackService : MediaLibraryService() {
// sound is about to change, eg. bluetooth -> speaker // sound is about to change, eg. bluetooth -> speaker
Log.d(TAG, "audioBecomingNoisy onReceive called with action: ${intent.action}") Log.d(TAG, "audioBecomingNoisy onReceive called with action: ${intent.action}")
Logd(TAG, "Pausing playback because audio is becoming noisy") Logd(TAG, "Pausing playback because audio is becoming noisy")
pauseIfPauseOnDisconnect() // pauseIfPauseOnDisconnect()
transientPause = (MediaPlayerBase.status == PlayerStatus.PLAYING || MediaPlayerBase.status == PlayerStatus.FALLBACK)
if (isPauseOnHeadsetDisconnect && !isCasting) mPlayer?.pause(!isPersistNotify, false)
} }
} }
@ -271,6 +275,9 @@ class PlaybackService : MediaLibraryService() {
list list
} }
val shouldSkipKeepEpisode by lazy { appPrefs.getBoolean(UserPreferences.Prefs.prefSkipKeepsEpisode.name, true) }
val shouldKeepSuperEpisode by lazy { appPrefs.getBoolean(UserPreferences.Prefs.prefFavoriteKeepsEpisode.name, true) }
private val mediaPlayerCallback: MediaPlayerCallback = object : MediaPlayerCallback { private val mediaPlayerCallback: MediaPlayerCallback = object : MediaPlayerCallback {
override fun statusChanged(newInfo: MediaPlayerInfo?) { override fun statusChanged(newInfo: MediaPlayerInfo?) {
currentMediaType = mPlayer?.mediaType ?: MediaType.UNKNOWN currentMediaType = mPlayer?.mediaType ?: MediaType.UNKNOWN
@ -354,35 +361,31 @@ class PlaybackService : MediaLibraryService() {
} }
} }
if (item != null) { if (item != null) {
// fun shouldSkipKeepEpisode(): Boolean {
// return appPrefs.getBoolean(UserPreferences.Prefs.prefSkipKeepsEpisode.name, true)
// }
// fun shouldFavoriteKeepEpisode(): Boolean {
// return appPrefs.getBoolean(UserPreferences.Prefs.prefFavoriteKeepsEpisode.name, true)
// }
runOnIOScope { runOnIOScope {
val shouldSkipKeepEpisode = appPrefs.getBoolean(UserPreferences.Prefs.prefSkipKeepsEpisode.name, true)
val shouldFavoriteKeepEpisode = appPrefs.getBoolean(UserPreferences.Prefs.prefFavoriteKeepsEpisode.name, true)
if (ended || smartMarkAsPlayed || autoSkipped || (skipped && !shouldSkipKeepEpisode)) { if (ended || smartMarkAsPlayed || autoSkipped || (skipped && !shouldSkipKeepEpisode)) {
Logd(TAG, "onPostPlayback ended: $ended smartMarkAsPlayed: $smartMarkAsPlayed autoSkipped: $autoSkipped skipped: $skipped") Logd(TAG, "onPostPlayback ended: $ended smartMarkAsPlayed: $smartMarkAsPlayed autoSkipped: $autoSkipped skipped: $skipped")
// only mark the item as played if we're not keeping it anyways // only mark the item as played if we're not keeping it anyways
item = setPlayStateSync(PlayState.PLAYED.code, ended || (skipped && smartMarkAsPlayed), item!!) item = setPlayStateSync(PlayState.PLAYED.code, item!!, ended || (skipped && smartMarkAsPlayed), false)
if (playable is EpisodeMedia && (ended || skipped || playingNext)) {
item = upsert(item!!) { it.media?.playbackCompletionDate = Date() }
EventFlow.postEvent(FlowEvent.HistoryEvent())
}
val action = item?.feed?.preferences?.autoDeleteAction val action = item?.feed?.preferences?.autoDeleteAction
val shouldAutoDelete = (action == AutoDeleteAction.ALWAYS || val shouldAutoDelete = (action == AutoDeleteAction.ALWAYS ||
(action == AutoDeleteAction.GLOBAL && item?.feed != null && shouldAutoDeleteItem(item!!.feed!!))) (action == AutoDeleteAction.GLOBAL && item?.feed != null && allowForAutoDelete(item!!.feed!!)))
if (playable is EpisodeMedia && shouldAutoDelete && (item?.isSUPER != true || !shouldFavoriteKeepEpisode)) { val isItemdeletable = (!shouldKeepSuperEpisode || (item?.isSUPER != true && item?.playState != PlayState.AGAIN.code && item?.playState != PlayState.FOREVER.code))
if (playable is EpisodeMedia && shouldAutoDelete && isItemdeletable) {
if (playable.localFileAvailable()) item = deleteMediaSync(this@PlaybackService, item!!) if (playable.localFileAvailable()) item = deleteMediaSync(this@PlaybackService, item!!)
if (shouldDeleteRemoveFromQueue()) removeFromQueueSync(null, item!!) if (prefDeleteRemovesFromQueue) removeFromQueueSync(null, item!!)
} else if (prefRemoveFromQueueMarkedPlayed) removeFromAllQueuesSync(item!!)
} }
} }
if (playable is EpisodeMedia && (ended || skipped || playingNext)) setCompletionDate(item!!)
}
} }
} }
override fun onPlaybackStart(playable: Playable, position: Int) { override fun onPlaybackStart(playable: Playable, position: Int) {
Logd(TAG, "onPlaybackStart position: $position")
val delayInterval = positionUpdateInterval(playable.getDuration()) val delayInterval = positionUpdateInterval(playable.getDuration())
Logd(TAG, "onPlaybackStart position: $position delayInterval: $delayInterval")
taskManager.startWidgetUpdater(delayInterval) taskManager.startWidgetUpdater(delayInterval)
// if (position != Playable.INVALID_TIME) playable.setPosition(position + (delayInterval/2).toInt()) // if (position != Playable.INVALID_TIME) playable.setPosition(position + (delayInterval/2).toInt())
if (position != Playable.INVALID_TIME) playable.setPosition(position) if (position != Playable.INVALID_TIME) playable.setPosition(position)
@ -453,7 +456,7 @@ class PlaybackService : MediaLibraryService() {
writeNoMediaPlaying() writeNoMediaPlaying()
return null return null
} }
// EventFlow.postEvent(FlowEvent.PlayEvent(nextItem)) EventFlow.postEvent(FlowEvent.PlayEvent(nextItem))
return if (nextItem.media == null) null else unmanaged(nextItem.media!!) return if (nextItem.media == null) null else unmanaged(nextItem.media!!)
} }
override fun findMedia(url: String): Playable? { override fun findMedia(url: String): Playable? {
@ -1183,11 +1186,11 @@ class PlaybackService : MediaLibraryService() {
} }
} }
private fun pauseIfPauseOnDisconnect() { // private fun pauseIfPauseOnDisconnect() {
Logd(TAG, "pauseIfPauseOnDisconnect()") // Logd(TAG, "pauseIfPauseOnDisconnect()")
transientPause = (MediaPlayerBase.status == PlayerStatus.PLAYING || MediaPlayerBase.status == PlayerStatus.FALLBACK) // transientPause = (MediaPlayerBase.status == PlayerStatus.PLAYING || MediaPlayerBase.status == PlayerStatus.FALLBACK)
if (isPauseOnHeadsetDisconnect && !isCasting) mPlayer?.pause(!isPersistNotify, false) // if (isPauseOnHeadsetDisconnect && !isCasting) mPlayer?.pause(!isPersistNotify, false)
} // }
/** /**
* @param bluetooth true if the event for unpausing came from bluetooth * @param bluetooth true if the event for unpausing came from bluetooth
@ -1318,9 +1321,6 @@ class PlaybackService : MediaLibraryService() {
if (!isPositionSaverActive) { if (!isPositionSaverActive) {
var positionSaver = Runnable { callback.positionSaverTick() } var positionSaver = Runnable { callback.positionSaverTick() }
positionSaver = useMainThreadIfNecessary(positionSaver) positionSaver = useMainThreadIfNecessary(positionSaver)
// val delayInterval = positionUpdateInterval(duration)
// positionSaverFuture = schedExecutor.scheduleWithFixedDelay(
// positionSaver, POSITION_SAVER_WAITING_INTERVAL.toLong(), POSITION_SAVER_WAITING_INTERVAL.toLong(), TimeUnit.MILLISECONDS)
positionSaverFuture = schedExecutor.scheduleWithFixedDelay(positionSaver, delayInterval, delayInterval, TimeUnit.MILLISECONDS) positionSaverFuture = schedExecutor.scheduleWithFixedDelay(positionSaver, delayInterval, delayInterval, TimeUnit.MILLISECONDS)
Logd(TAG, "Started PositionSaver") Logd(TAG, "Started PositionSaver")
} else Logd(TAG, "Call to startPositionSaver was ignored.") } else Logd(TAG, "Call to startPositionSaver was ignored.")
@ -1493,7 +1493,6 @@ class PlaybackService : MediaLibraryService() {
Logd(TAG, "Sleep timer expired") Logd(TAG, "Sleep timer expired")
shakeListener?.pause() shakeListener?.pause()
shakeListener = null shakeListener = null
hasVibrated = false hasVibrated = false
} }
} }
@ -1547,7 +1546,6 @@ class PlaybackService : MediaLibraryService() {
val gX = event.values[0] / SensorManager.GRAVITY_EARTH val gX = event.values[0] / SensorManager.GRAVITY_EARTH
val gY = event.values[1] / SensorManager.GRAVITY_EARTH val gY = event.values[1] / SensorManager.GRAVITY_EARTH
val gZ = event.values[2] / SensorManager.GRAVITY_EARTH val gZ = event.values[2] / SensorManager.GRAVITY_EARTH
val gForce = sqrt((gX * gX + gY * gY + gZ * gZ).toDouble()) val gForce = sqrt((gX * gX + gY * gY + gZ * gZ).toDouble())
if (gForce > 2.25) { if (gForce > 2.25) {
Logd(TAG, "Detected shake $gForce") Logd(TAG, "Detected shake $gForce")
@ -1562,13 +1560,12 @@ class PlaybackService : MediaLibraryService() {
private const val SCHED_EX_POOL_SIZE = 2 private const val SCHED_EX_POOL_SIZE = 2
private const val SLEEP_TIMER_UPDATE_INTERVAL = 10000L // in millisoconds private const val SLEEP_TIMER_UPDATE_INTERVAL = 10000L // in millisoconds
const val POSITION_SAVER_WAITING_INTERVAL: Int = 5000 // in millisoconds const val MIN_POSITION_SAVER_INTERVAL: Int = 5000 // in millisoconds
// const val WIDGET_UPDATER_NOTIFICATION_INTERVAL: Int = 5000 // in millisoconds
const val NOTIFICATION_THRESHOLD: Long = 10000 // in millisoconds const val NOTIFICATION_THRESHOLD: Long = 10000 // in millisoconds
fun positionUpdateInterval(duration: Int): Long { fun positionUpdateInterval(duration: Int): Long {
return if (prefAdaptiveProgressUpdate) max(POSITION_SAVER_WAITING_INTERVAL, duration/50).toLong() return if (prefAdaptiveProgressUpdate) max(MIN_POSITION_SAVER_INTERVAL, duration/50).toLong()
else POSITION_SAVER_WAITING_INTERVAL.toLong() else MIN_POSITION_SAVER_INTERVAL.toLong()
} }
} }
} }
@ -1622,23 +1619,17 @@ class PlaybackService : MediaLibraryService() {
/** /**
* @return `true` if notifications are persistent, `false` otherwise * @return `true` if notifications are persistent, `false` otherwise
*/ */
val isPersistNotify: Boolean val isPersistNotify: Boolean by lazy { appPrefs.getBoolean(UserPreferences.Prefs.prefPersistNotify.name, true) }
get() = appPrefs.getBoolean(UserPreferences.Prefs.prefPersistNotify.name, true)
val isPauseOnHeadsetDisconnect: Boolean val isPauseOnHeadsetDisconnect: Boolean by lazy { appPrefs.getBoolean(UserPreferences.Prefs.prefPauseOnHeadsetDisconnect.name, true) }
get() = appPrefs.getBoolean(UserPreferences.Prefs.prefPauseOnHeadsetDisconnect.name, true)
val isUnpauseOnHeadsetReconnect: Boolean val isUnpauseOnHeadsetReconnect: Boolean by lazy { appPrefs.getBoolean(UserPreferences.Prefs.prefUnpauseOnHeadsetReconnect.name, true) }
get() = appPrefs.getBoolean(UserPreferences.Prefs.prefUnpauseOnHeadsetReconnect.name, true)
val isUnpauseOnBluetoothReconnect: Boolean val isUnpauseOnBluetoothReconnect: Boolean by lazy { appPrefs.getBoolean(UserPreferences.Prefs.prefUnpauseOnBluetoothReconnect.name, false) }
get() = appPrefs.getBoolean(UserPreferences.Prefs.prefUnpauseOnBluetoothReconnect.name, false)
val hardwareForwardButton: Int val hardwareForwardButton: Int by lazy { appPrefs.getString(UserPreferences.Prefs.prefHardwareForwardButton.name, KeyEvent.KEYCODE_MEDIA_FAST_FORWARD.toString())!!.toInt() }
get() = appPrefs.getString(UserPreferences.Prefs.prefHardwareForwardButton.name, KeyEvent.KEYCODE_MEDIA_FAST_FORWARD.toString())!!.toInt()
val hardwarePreviousButton: Int val hardwarePreviousButton: Int by lazy { appPrefs.getString(UserPreferences.Prefs.prefHardwarePreviousButton.name, KeyEvent.KEYCODE_MEDIA_REWIND.toString())!!.toInt() }
get() = appPrefs.getString(UserPreferences.Prefs.prefHardwarePreviousButton.name, KeyEvent.KEYCODE_MEDIA_REWIND.toString())!!.toInt()
/** /**
* Set to true to enable Continuous Playback * Set to true to enable Continuous Playback

View File

@ -311,7 +311,8 @@ object UserPreferences {
prefFavoriteKeepsEpisode, prefFavoriteKeepsEpisode,
prefAutoDelete, prefAutoDelete,
prefAutoDeleteLocal, prefAutoDeleteLocal,
prefSmartMarkAsPlayedSecs, // prefSmartMarkAsPlayedSecs,
// prefSmartMarkAsPlayedPercent,
prefPlaybackSpeedArray, prefPlaybackSpeedArray,
prefFallbackSpeed, prefFallbackSpeed,
prefPauseForFocusLoss, prefPauseForFocusLoss,

View File

@ -36,7 +36,7 @@ class PlaybackPreferencesFragment : PreferenceFragmentCompat() {
addPreferencesFromResource(R.xml.preferences_playback) addPreferencesFromResource(R.xml.preferences_playback)
setupPlaybackScreen() setupPlaybackScreen()
buildSmartMarkAsPlayedPreference() // buildSmartMarkAsPlayedPreference()
} }
override fun onStart() { override fun onStart() {
@ -111,27 +111,27 @@ class PlaybackPreferencesFragment : PreferenceFragmentCompat() {
return findPreference<T>(key) ?: throw IllegalArgumentException("Preference with key '$key' is not found") return findPreference<T>(key) ?: throw IllegalArgumentException("Preference with key '$key' is not found")
} }
private fun buildSmartMarkAsPlayedPreference() { // private fun buildSmartMarkAsPlayedPreference() {
val res = requireActivity().resources // val res = requireActivity().resources
//
val pref = findPreference<ListPreference>(UserPreferences.Prefs.prefSmartMarkAsPlayedSecs.name) // val pref = findPreference<ListPreference>(UserPreferences.Prefs.prefSmartMarkAsPlayedSecs.name)
val values = res.getStringArray(R.array.smart_mark_as_played_values) // val values = res.getStringArray(R.array.smart_mark_as_played_values)
val entries = arrayOfNulls<String>(values.size) // val entries = arrayOfNulls<String>(values.size)
for (x in values.indices) { // for (x in values.indices) {
if (x == 0) { // if (x == 0) {
entries[x] = res.getString(R.string.pref_smart_mark_as_played_disabled) // entries[x] = res.getString(R.string.pref_smart_mark_as_played_disabled)
} else { // } else {
var v = values[x].toInt() // var v = values[x].toInt()
if (v < 60) { // if (v < 60) {
entries[x] = res.getQuantityString(R.plurals.time_seconds_quantified, v, v) // entries[x] = res.getQuantityString(R.plurals.time_seconds_quantified, v, v)
} else { // } else {
v /= 60 // v /= 60
entries[x] = res.getQuantityString(R.plurals.time_minutes_quantified, v, v) // entries[x] = res.getQuantityString(R.plurals.time_minutes_quantified, v, v)
} // }
} // }
} // }
pref!!.entries = entries // pref!!.entries = entries
} // }
@UnstableApi @UnstableApi
class EditFallbackSpeedDialog(activity: Activity) { class EditFallbackSpeedDialog(activity: Activity) {

View File

@ -96,21 +96,22 @@ object Episodes {
} }
// @JvmStatic is needed because some Runnable blocks call this // @JvmStatic is needed because some Runnable blocks call this
@OptIn(UnstableApi::class) @JvmStatic @JvmStatic
fun deleteEpisodeMedia(context: Context, episode: Episode) : Job { fun deleteEpisodeMedia(context: Context, episode: Episode) : Job {
Logd(TAG, "deleteMediaOfEpisode called ${episode.title}") Logd(TAG, "deleteMediaOfEpisode called ${episode.title}")
return runOnIOScope { return runOnIOScope {
if (episode.media == null) return@runOnIOScope if (episode.media == null) return@runOnIOScope
val episode_ = deleteMediaSync(context, episode) val episode_ = deleteMediaSync(context, episode)
if (shouldDeleteRemoveFromQueue()) removeFromQueueSync(null, episode_) if (prefDeleteRemovesFromQueue) removeFromQueueSync(null, episode_)
} }
} }
fun shouldDeleteRemoveFromQueue(): Boolean { val prefDeleteRemovesFromQueue: Boolean
return appPrefs.getBoolean(Prefs.prefDeleteRemovesFromQueue.name, false) get() = appPrefs.getBoolean(Prefs.prefDeleteRemovesFromQueue.name, false)
} // fun shouldDeleteRemoveFromQueue(): Boolean {
// return appPrefs.getBoolean(Prefs.prefDeleteRemovesFromQueue.name, false)
// }
@OptIn(UnstableApi::class)
fun deleteMediaSync(context: Context, episode: Episode): Episode { fun deleteMediaSync(context: Context, episode: Episode): Episode {
Logd(TAG, "deleteMediaSync called") Logd(TAG, "deleteMediaSync called")
val media = episode.media ?: return episode val media = episode.media ?: return episode
@ -176,6 +177,7 @@ object Episodes {
} }
/** /**
* This is used when the episodes are not listed with the feed.
* Remove the listed episodes and their EpisodeMedia entries. * Remove the listed episodes and their EpisodeMedia entries.
* Deleting media also removes the download log entries. * Deleting media also removes the download log entries.
*/ */
@ -210,31 +212,6 @@ object Episodes {
} }
} }
/**
* This method will set the playback completion date to the current date regardless of the current value.
* @param episode Episode that should be added to the playback history.
* @param date PlaybackCompletionDate for `media`
*/
fun setCompletionDate(episode: Episode, date: Date? = Date()) : Job {
Logd(TAG, "setCompletionDate called played: ${episode.playState}")
return runOnIOScope {
val episode_ = realm.query(Episode::class).query("id == $0", episode.id).first().find()
if (episode_ != null) {
upsert(episode_) { it.media?.playbackCompletionDate = date }
EventFlow.postEvent(FlowEvent.HistoryEvent())
}
}
}
// @JvmStatic
// fun setFavorite(episode: Episode, stat: Boolean?) : Job {
// Logd(TAG, "setFavorite called $stat")
// return runOnIOScope {
// val result = upsert(episode) { it.rating = if (stat ?: !it.isFavorite) Episode.Rating.FAVORITE.code else Episode.Rating.NEUTRAL.code }
// EventFlow.postEvent(FlowEvent.RatingEvent(result, result.rating))
// }
// }
fun setRating(episode: Episode, rating: Int) : Job { fun setRating(episode: Episode, rating: Int) : Job {
Logd(TAG, "setRating called $rating") Logd(TAG, "setRating called $rating")
return runOnIOScope { return runOnIOScope {
@ -254,13 +231,12 @@ object Episodes {
Logd(TAG, "setPlayState called") Logd(TAG, "setPlayState called")
return runOnIOScope { return runOnIOScope {
for (episode in episodes) { for (episode in episodes) {
setPlayStateSync(played, resetMediaPosition, episode) setPlayStateSync(played, episode, resetMediaPosition)
} }
} }
} }
@OptIn(UnstableApi::class) suspend fun setPlayStateSync(played: Int, episode: Episode, resetMediaPosition: Boolean, removeFromQueue: Boolean = true) : Episode {
suspend fun setPlayStateSync(played: Int, resetMediaPosition: Boolean, episode: Episode) : Episode {
Logd(TAG, "setPlayStateSync called played: $played resetMediaPosition: $resetMediaPosition ${episode.title}") Logd(TAG, "setPlayStateSync called played: $played resetMediaPosition: $resetMediaPosition ${episode.title}")
var episode_ = episode var episode_ = episode
if (!episode.isManaged()) episode_ = realm.query(Episode::class).query("id == $0", episode.id).first().find() ?: episode if (!episode.isManaged()) episode_ = realm.query(Episode::class).query("id == $0", episode.id).first().find() ?: episode
@ -273,15 +249,18 @@ object Episodes {
if (resetMediaPosition) it.media?.setPosition(0) if (resetMediaPosition) it.media?.setPosition(0)
} }
Logd(TAG, "setPlayStateSync played0: ${result.playState}") Logd(TAG, "setPlayStateSync played0: ${result.playState}")
if (played == PlayState.PLAYED.code && shouldMarkedPlayedRemoveFromQueues()) removeFromAllQueuesSync(result) if (removeFromQueue && played == PlayState.PLAYED.code && prefRemoveFromQueueMarkedPlayed) removeFromAllQueuesSync(result)
Logd(TAG, "setPlayStateSync played1: ${result.playState}") Logd(TAG, "setPlayStateSync played1: ${result.playState}")
EventFlow.postEvent(FlowEvent.EpisodePlayedEvent(result)) EventFlow.postEvent(FlowEvent.EpisodePlayedEvent(result))
return result return result
} }
private fun shouldMarkedPlayedRemoveFromQueues(): Boolean { val prefRemoveFromQueueMarkedPlayed: Boolean
return appPrefs.getBoolean(Prefs.prefRemoveFromQueueMarkedPlayed.name, true) get() = appPrefs.getBoolean(Prefs.prefRemoveFromQueueMarkedPlayed.name, true)
}
// fun shouldMarkedPlayedRemoveFromQueues(): Boolean {
// return appPrefs.getBoolean(Prefs.prefRemoveFromQueueMarkedPlayed.name, true)
// }
fun episodeFromStreamInfoItem(item: StreamInfoItem): Episode { fun episodeFromStreamInfoItem(item: StreamInfoItem): Episode {
val e = Episode() val e = Episode()

View File

@ -435,7 +435,7 @@ object Feeds {
} }
@JvmStatic @JvmStatic
fun shouldAutoDeleteItem(feed: Feed): Boolean { fun allowForAutoDelete(feed: Feed): Boolean {
if (!isAutoDelete) return false if (!isAutoDelete) return false
return !feed.isLocalFeed || isAutoDeleteLocal return !feed.isLocalFeed || isAutoDeleteLocal
} }

View File

@ -6,7 +6,6 @@ import ac.mdiq.podcini.playback.base.InTheatre.curQueue
import ac.mdiq.podcini.preferences.UserPreferences import ac.mdiq.podcini.preferences.UserPreferences
import ac.mdiq.podcini.preferences.UserPreferences.appPrefs import ac.mdiq.podcini.preferences.UserPreferences.appPrefs
import ac.mdiq.podcini.storage.database.Episodes.setPlayState import ac.mdiq.podcini.storage.database.Episodes.setPlayState
import ac.mdiq.podcini.storage.database.Episodes.setPlayStateSync
import ac.mdiq.podcini.storage.database.RealmDB.realm import ac.mdiq.podcini.storage.database.RealmDB.realm
import ac.mdiq.podcini.storage.database.RealmDB.runOnIOScope import ac.mdiq.podcini.storage.database.RealmDB.runOnIOScope
import ac.mdiq.podcini.storage.database.RealmDB.upsert import ac.mdiq.podcini.storage.database.RealmDB.upsert
@ -14,14 +13,10 @@ import ac.mdiq.podcini.storage.database.RealmDB.upsertBlk
import ac.mdiq.podcini.storage.model.* import ac.mdiq.podcini.storage.model.*
import ac.mdiq.podcini.storage.utils.EpisodeUtil.indexOfItemWithId import ac.mdiq.podcini.storage.utils.EpisodeUtil.indexOfItemWithId
import ac.mdiq.podcini.storage.utils.EpisodesPermutors.getPermutor import ac.mdiq.podcini.storage.utils.EpisodesPermutors.getPermutor
import ac.mdiq.podcini.util.Logd
import ac.mdiq.podcini.util.EventFlow import ac.mdiq.podcini.util.EventFlow
import ac.mdiq.podcini.util.FlowEvent import ac.mdiq.podcini.util.FlowEvent
import ac.mdiq.podcini.util.Logd
import android.util.Log import android.util.Log
import androidx.annotation.OptIn
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.media3.common.util.UnstableApi import androidx.media3.common.util.UnstableApi
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import java.util.* import java.util.*
@ -91,10 +86,9 @@ object Queues {
/** /**
* Appends Episode objects to the end of the queue. The 'read'-attribute of all episodes will be set to true. * Appends Episode objects to the end of the queue. The 'read'-attribute of all episodes will be set to true.
* If a Episode is already in the queue, the Episode will not change its position in the queue. * If a Episode is already in the queue, the Episode will not change its position in the queue.
* @param markAsUnplayed true if the episodes should be marked as unplayed when enqueueing
* @param episodes the Episode objects that should be added to the queue. * @param episodes the Episode objects that should be added to the queue.
*/ */
@UnstableApi @JvmStatic @Synchronized @JvmStatic @Synchronized
fun addToQueue(vararg episodes: Episode) : Job { fun addToQueue(vararg episodes: Episode) : Job {
Logd(TAG, "addToQueue( ... ) called") Logd(TAG, "addToQueue( ... ) called")
return runOnIOScope { return runOnIOScope {
@ -130,7 +124,6 @@ object Queues {
it.update() it.update()
} }
for (event in events) EventFlow.postEvent(event) for (event in events) EventFlow.postEvent(event)
setPlayState(PlayState.INQUEUE.code, false, *setInQueue.toTypedArray()) setPlayState(PlayState.INQUEUE.code, false, *setInQueue.toTypedArray())
// if (performAutoDownload) autodownloadEpisodeMedia(context) // if (performAutoDownload) autodownloadEpisodeMedia(context)
} }
@ -199,16 +192,15 @@ object Queues {
} }
} }
/** // /**
* Removes a Episode object from the queue. // * Removes a Episode object from the queue.
* @param episodes FeedItems that should be removed. // * @param episodes FeedItems that should be removed.
*/ // */
@OptIn(UnstableApi::class) @JvmStatic // @JvmStatic
fun removeFromQueue(vararg episodes: Episode) : Job { // fun removeFromQueue(vararg episodes: Episode) : Job {
return runOnIOScope { removeFromQueueSync(curQueue, *episodes) } // return runOnIOScope { removeFromQueueSync(curQueue, *episodes) }
} // }
@OptIn(UnstableApi::class)
fun removeFromAllQueuesSync(vararg episodes: Episode) { fun removeFromAllQueuesSync(vararg episodes: Episode) {
Logd(TAG, "removeFromAllQueuesSync called ") Logd(TAG, "removeFromAllQueuesSync called ")
val queues = realm.query(PlayQueue::class).find() val queues = realm.query(PlayQueue::class).find()
@ -239,7 +231,7 @@ object Queues {
if (indexOfItemWithId(eList, episode.id) >= 0) { if (indexOfItemWithId(eList, episode.id) >= 0) {
Logd(TAG, "removing from queue: ${episode.id} ${episode.title}") Logd(TAG, "removing from queue: ${episode.id} ${episode.title}")
indicesToRemove.add(i) indicesToRemove.add(i)
if (episode.playState < PlayState.SKIPPED.code) setPlayState(PlayState.SKIPPED.code, false, episode) // if (setState && episode.playState < PlayState.SKIPPED.code) setPlayState(PlayState.SKIPPED.code, false, episode)
if (queue.id == curQueue.id) events.add(FlowEvent.QueueEvent.removed(episode)) if (queue.id == curQueue.id) events.add(FlowEvent.QueueEvent.removed(episode))
} }
} }
@ -316,9 +308,9 @@ object Queues {
* false if the caller wants to avoid unexpected updates of the GUI. * false if the caller wants to avoid unexpected updates of the GUI.
* @throws IndexOutOfBoundsException if (to < 0 || to >= queue.size()) || (from < 0 || from >= queue.size()) * @throws IndexOutOfBoundsException if (to < 0 || to >= queue.size()) || (from < 0 || from >= queue.size())
*/ */
fun moveInQueue(from: Int, to: Int, broadcastUpdate: Boolean) : Job { // fun moveInQueue(from: Int, to: Int, broadcastUpdate: Boolean) : Job {
return runOnIOScope { moveInQueueSync(from, to, broadcastUpdate) } // return runOnIOScope { moveInQueueSync(from, to, broadcastUpdate) }
} // }
/** /**
* Changes the position of a Episode in the queue. * Changes the position of a Episode in the queue.

View File

@ -80,6 +80,8 @@ class EpisodeFilter(vararg properties_: String) : Serializable {
if (properties.contains(States.inProgress.name)) stateQuerys.add(" playState == ${PlayState.INPROGRESS.code} ") if (properties.contains(States.inProgress.name)) stateQuerys.add(" playState == ${PlayState.INPROGRESS.code} ")
if (properties.contains(States.skipped.name)) stateQuerys.add(" playState == ${PlayState.SKIPPED.code} ") if (properties.contains(States.skipped.name)) stateQuerys.add(" playState == ${PlayState.SKIPPED.code} ")
if (properties.contains(States.played.name)) stateQuerys.add(" playState == ${PlayState.PLAYED.code} ") if (properties.contains(States.played.name)) stateQuerys.add(" playState == ${PlayState.PLAYED.code} ")
if (properties.contains(States.again.name)) stateQuerys.add(" playState == ${PlayState.AGAIN.code} ")
if (properties.contains(States.forever.name)) stateQuerys.add(" playState == ${PlayState.FOREVER.code} ")
if (properties.contains(States.ignored.name)) stateQuerys.add(" playState == ${PlayState.IGNORED.code} ") if (properties.contains(States.ignored.name)) stateQuerys.add(" playState == ${PlayState.IGNORED.code} ")
if (stateQuerys.isNotEmpty()) { if (stateQuerys.isNotEmpty()) {
val query = StringBuilder(" (" + stateQuerys[0]) val query = StringBuilder(" (" + stateQuerys[0])
@ -147,6 +149,8 @@ class EpisodeFilter(vararg properties_: String) : Serializable {
inProgress, inProgress,
skipped, skipped,
played, played,
again,
forever,
ignored, ignored,
has_chapters, has_chapters,
no_chapters, no_chapters,
@ -197,6 +201,8 @@ class EpisodeFilter(vararg properties_: String) : Serializable {
ItemProperties(R.string.in_progress, States.inProgress.name), ItemProperties(R.string.in_progress, States.inProgress.name),
ItemProperties(R.string.skipped, States.skipped.name), ItemProperties(R.string.skipped, States.skipped.name),
ItemProperties(R.string.played, States.played.name), ItemProperties(R.string.played, States.played.name),
ItemProperties(R.string.again, States.again.name),
ItemProperties(R.string.forever, States.forever.name),
ItemProperties(R.string.ignored, States.ignored.name), ItemProperties(R.string.ignored, States.ignored.name),
), ),
OPINION(R.string.has_comments, ItemProperties(R.string.yes, States.has_comments.name), ItemProperties(R.string.no, States.no_comments.name)), OPINION(R.string.has_comments, ItemProperties(R.string.yes, States.has_comments.name), ItemProperties(R.string.no, States.no_comments.name)),

View File

@ -1,15 +1,14 @@
package ac.mdiq.podcini.storage.utils package ac.mdiq.podcini.storage.utils
import ac.mdiq.podcini.preferences.UserPreferences
import ac.mdiq.podcini.preferences.UserPreferences.Prefs.prefSmartMarkAsPlayedSecs
import ac.mdiq.podcini.preferences.UserPreferences.appPrefs
import ac.mdiq.podcini.storage.model.Episode import ac.mdiq.podcini.storage.model.Episode
import ac.mdiq.podcini.storage.model.Playable import ac.mdiq.podcini.storage.model.Playable
object EpisodeUtil { object EpisodeUtil {
private val TAG: String = EpisodeUtil::class.simpleName ?: "Anonymous" private val TAG: String = EpisodeUtil::class.simpleName ?: "Anonymous"
val smartMarkAsPlayedSecs: Int // val smartMarkAsPlayedSecs: Int
get() = appPrefs.getString(UserPreferences.Prefs.prefSmartMarkAsPlayedSecs.name, "30")!!.toInt() // get() = appPrefs.getString(UserPreferences.Prefs.prefSmartMarkAsPlayedSecs.name, "30")!!.toInt()
private val smartMarkAsPlayedPercent: Int = 95
@JvmStatic @JvmStatic
fun indexOfItemWithId(episodes: List<Episode?>, id: Long): Int { fun indexOfItemWithId(episodes: List<Episode?>, id: Long): Int {
@ -36,6 +35,6 @@ object EpisodeUtil {
@JvmStatic @JvmStatic
fun hasAlmostEnded(media: Playable): Boolean { fun hasAlmostEnded(media: Playable): Boolean {
return media.getDuration() > 0 && media.getPosition() >= media.getDuration() - smartMarkAsPlayedSecs * 1000 return media.getDuration() > 0 && media.getPosition() >= media.getDuration() * smartMarkAsPlayedPercent * 0.01
} }
} }

View File

@ -250,7 +250,7 @@ class PlayActionButton(item: Episode) : EpisodeActionButton(item) {
} else { } else {
PlaybackService.clearCurTempSpeed() PlaybackService.clearCurTempSpeed()
PlaybackServiceStarter(context, media).callEvenIfRunning(true).start() PlaybackServiceStarter(context, media).callEvenIfRunning(true).start()
item = runBlocking { setPlayStateSync(PlayState.INPROGRESS.code, false, item) } if (item.playState < PlayState.INPROGRESS.code) item = runBlocking { setPlayStateSync(PlayState.INPROGRESS.code, item, false) }
EventFlow.postEvent(FlowEvent.PlayEvent(item)) EventFlow.postEvent(FlowEvent.PlayEvent(item))
} }
playVideoIfNeeded(context, media) playVideoIfNeeded(context, media)
@ -425,7 +425,7 @@ class StreamActionButton(item: Episode) : EpisodeActionButton(item) {
if (media !is EpisodeMedia || !InTheatre.isCurMedia(media)) PlaybackService.clearCurTempSpeed() if (media !is EpisodeMedia || !InTheatre.isCurMedia(media)) PlaybackService.clearCurTempSpeed()
PlaybackServiceStarter(context, media).shouldStreamThisTime(true).callEvenIfRunning(true).start() PlaybackServiceStarter(context, media).shouldStreamThisTime(true).callEvenIfRunning(true).start()
if (media is EpisodeMedia && media.episode != null) { if (media is EpisodeMedia && media.episode != null) {
val item = runBlocking { setPlayStateSync(PlayState.INPROGRESS.code, false, media.episode!!) } val item = runBlocking { setPlayStateSync(PlayState.INPROGRESS.code, media.episode!!, false) }
EventFlow.postEvent(FlowEvent.PlayEvent(item)) EventFlow.postEvent(FlowEvent.PlayEvent(item))
} }
playVideoIfNeeded(context, media) playVideoIfNeeded(context, media)
@ -591,7 +591,7 @@ class PlayLocalActionButton(item: Episode) : EpisodeActionButton(item) {
} else { } else {
PlaybackService.clearCurTempSpeed() PlaybackService.clearCurTempSpeed()
PlaybackServiceStarter(context, media).callEvenIfRunning(true).start() PlaybackServiceStarter(context, media).callEvenIfRunning(true).start()
item = runBlocking { setPlayStateSync(PlayState.INPROGRESS.code, false, item) } item = runBlocking { setPlayStateSync(PlayState.INPROGRESS.code, item, false) }
EventFlow.postEvent(FlowEvent.PlayEvent(item)) EventFlow.postEvent(FlowEvent.PlayEvent(item))
} }
if (media.getMediaType() == MediaType.VIDEO) context.startActivity(getPlayerActivityIntent(context, if (media.getMediaType() == MediaType.VIDEO) context.startActivity(getPlayerActivityIntent(context,

View File

@ -1,23 +1,19 @@
package ac.mdiq.podcini.ui.actions package ac.mdiq.podcini.ui.actions
//import ac.mdiq.podcini.ui.dialog.SwipeActionsDialog
import ac.mdiq.podcini.R import ac.mdiq.podcini.R
import ac.mdiq.podcini.playback.base.InTheatre.curQueue import ac.mdiq.podcini.playback.base.InTheatre.curQueue
import ac.mdiq.podcini.storage.database.Episodes.deleteMediaSync
import ac.mdiq.podcini.storage.database.Episodes.setPlayState import ac.mdiq.podcini.storage.database.Episodes.setPlayState
import ac.mdiq.podcini.storage.database.Episodes.shouldDeleteRemoveFromQueue import ac.mdiq.podcini.storage.database.Episodes.setPlayStateSync
import ac.mdiq.podcini.storage.database.Feeds.shouldAutoDeleteItem
import ac.mdiq.podcini.storage.database.Queues.addToQueue import ac.mdiq.podcini.storage.database.Queues.addToQueue
import ac.mdiq.podcini.storage.database.Queues.removeFromQueue
import ac.mdiq.podcini.storage.database.Queues.removeFromQueueSync import ac.mdiq.podcini.storage.database.Queues.removeFromQueueSync
import ac.mdiq.podcini.storage.database.RealmDB.realm import ac.mdiq.podcini.storage.database.RealmDB.realm
import ac.mdiq.podcini.storage.database.RealmDB.runOnIOScope import ac.mdiq.podcini.storage.database.RealmDB.runOnIOScope
import ac.mdiq.podcini.storage.database.RealmDB.upsert import ac.mdiq.podcini.storage.database.RealmDB.upsert
import ac.mdiq.podcini.storage.database.RealmDB.upsertBlk
import ac.mdiq.podcini.storage.model.Episode import ac.mdiq.podcini.storage.model.Episode
import ac.mdiq.podcini.storage.model.EpisodeFilter import ac.mdiq.podcini.storage.model.EpisodeFilter
import ac.mdiq.podcini.storage.model.EpisodeMedia
import ac.mdiq.podcini.storage.model.PlayState import ac.mdiq.podcini.storage.model.PlayState
import ac.mdiq.podcini.storage.utils.EpisodeUtil import ac.mdiq.podcini.storage.utils.EpisodeUtil.hasAlmostEnded
import ac.mdiq.podcini.ui.actions.SwipeAction.ActionTypes import ac.mdiq.podcini.ui.actions.SwipeAction.ActionTypes
import ac.mdiq.podcini.ui.actions.SwipeAction.ActionTypes.NO_ACTION import ac.mdiq.podcini.ui.actions.SwipeAction.ActionTypes.NO_ACTION
import ac.mdiq.podcini.ui.activity.MainActivity import ac.mdiq.podcini.ui.activity.MainActivity
@ -56,10 +52,8 @@ import androidx.lifecycle.LifecycleOwner
import androidx.media3.common.util.UnstableApi import androidx.media3.common.util.UnstableApi
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import java.util.* import java.util.*
import kotlin.math.ceil
open class SwipeActions(private val fragment: Fragment, private val tag: String) : DefaultLifecycleObserver { open class SwipeActions(private val fragment: Fragment, private val tag: String) : DefaultLifecycleObserver {
@ -202,8 +196,15 @@ open class SwipeActions(private val fragment: Fragment, private val tag: String)
return context.getString(R.string.delete_episode_label) return context.getString(R.string.delete_episode_label)
} }
@UnstableApi @UnstableApi
override fun performAction(item: Episode, fragment: Fragment, filter: EpisodeFilter) { override fun performAction(item_: Episode, fragment: Fragment, filter: EpisodeFilter) {
var item = item_
if (!item.isDownloaded && item.feed?.isLocalFeed != true) return if (!item.isDownloaded && item.feed?.isLocalFeed != true) return
val media = item.media
if (media != null) {
val almostEnded = hasAlmostEnded(media)
if (almostEnded && item.playState < PlayState.PLAYED.code) item = runBlocking { setPlayStateSync(PlayState.PLAYED.code, item, almostEnded, false) }
if (almostEnded) item = upsertBlk(item) { it.media?.playbackCompletionDate = Date() }
}
deleteEpisodesWarnLocal(fragment.requireContext(), listOf(item)) deleteEpisodesWarnLocal(fragment.requireContext(), listOf(item))
} }
override fun willRemove(filter: EpisodeFilter, item: Episode): Boolean { override fun willRemove(filter: EpisodeFilter, item: Episode): Boolean {
@ -314,9 +315,18 @@ open class SwipeActions(private val fragment: Fragment, private val tag: String)
return context.getString(R.string.remove_from_queue_label) return context.getString(R.string.remove_from_queue_label)
} }
@OptIn(UnstableApi::class) @OptIn(UnstableApi::class)
override fun performAction(item: Episode, fragment: Fragment, filter: EpisodeFilter) { override fun performAction(item_: Episode, fragment: Fragment, filter: EpisodeFilter) {
val position: Int = curQueue.episodes.indexOf(item) val position: Int = curQueue.episodes.indexOf(item_)
removeFromQueue(item) var item = item_
val media = item.media
if (media != null) {
val almostEnded = hasAlmostEnded(media)
if (almostEnded && item.playState < PlayState.PLAYED.code) item = runBlocking { setPlayStateSync(PlayState.PLAYED.code, item, almostEnded, false) }
if (almostEnded) item = upsertBlk(item) { it.media?.playbackCompletionDate = Date() }
}
if (item.playState < PlayState.SKIPPED.code) item = runBlocking { setPlayStateSync(PlayState.SKIPPED.code, item, false, false) }
// removeFromQueue(item)
runOnIOScope { removeFromQueueSync(curQueue, item) }
if (willRemove(filter, item)) { if (willRemove(filter, item)) {
(fragment.requireActivity() as MainActivity).showSnackbarAbovePlayer(fragment.resources.getQuantityString(R.plurals.removed_from_queue_batch_label, 1, 1), Snackbar.LENGTH_LONG) (fragment.requireActivity() as MainActivity).showSnackbarAbovePlayer(fragment.resources.getQuantityString(R.plurals.removed_from_queue_batch_label, 1, 1), Snackbar.LENGTH_LONG)
.setAction(fragment.getString(R.string.undo)) { .setAction(fragment.getString(R.string.undo)) {
@ -426,15 +436,15 @@ open class SwipeActions(private val fragment: Fragment, private val tag: String)
} }
(fragment.view as? ViewGroup)?.addView(composeView) (fragment.view as? ViewGroup)?.addView(composeView)
} }
private fun delayedExecution(item: Episode, fragment: Fragment, duration: Float) = runBlocking { // private fun delayedExecution(item: Episode, fragment: Fragment, duration: Float) = runBlocking {
delay(ceil((duration * 1.05f).toDouble()).toLong()) // delay(ceil((duration * 1.05f).toDouble()).toLong())
val media: EpisodeMedia? = item.media // val media: EpisodeMedia? = item.media
val shouldAutoDelete = if (item.feed == null) false else shouldAutoDeleteItem(item.feed!!) // val shouldAutoDelete = if (item.feed == null) false else shouldAutoDeleteItem(item.feed!!)
if (media != null && EpisodeUtil.hasAlmostEnded(media) && shouldAutoDelete) { // if (media != null && EpisodeUtil.hasAlmostEnded(media) && shouldAutoDelete) {
// deleteMediaOfEpisode(fragment.requireContext(), item) //// deleteMediaOfEpisode(fragment.requireContext(), item)
val item_ = deleteMediaSync(fragment.requireContext(), item) // val item_ = deleteMediaSync(fragment.requireContext(), item)
if (shouldDeleteRemoveFromQueue()) removeFromQueueSync(null, item_) } // if (prefDeleteRemovesFromQueue) removeFromQueueSync(null, item_) }
} // }
} }
class ShelveSwipeAction : SwipeAction { class ShelveSwipeAction : SwipeAction {

View File

@ -12,19 +12,23 @@ import ac.mdiq.podcini.playback.base.InTheatre
import ac.mdiq.podcini.playback.base.InTheatre.curQueue import ac.mdiq.podcini.playback.base.InTheatre.curQueue
import ac.mdiq.podcini.playback.base.MediaPlayerBase.Companion.status import ac.mdiq.podcini.playback.base.MediaPlayerBase.Companion.status
import ac.mdiq.podcini.storage.database.Episodes import ac.mdiq.podcini.storage.database.Episodes
import ac.mdiq.podcini.storage.database.Episodes.deleteEpisodeMedia
import ac.mdiq.podcini.storage.database.Episodes.deleteMediaSync import ac.mdiq.podcini.storage.database.Episodes.deleteMediaSync
import ac.mdiq.podcini.storage.database.Episodes.episodeFromStreamInfo import ac.mdiq.podcini.storage.database.Episodes.episodeFromStreamInfo
import ac.mdiq.podcini.storage.database.Episodes.prefDeleteRemovesFromQueue
import ac.mdiq.podcini.storage.database.Episodes.prefRemoveFromQueueMarkedPlayed
import ac.mdiq.podcini.storage.database.Episodes.setPlayState
import ac.mdiq.podcini.storage.database.Episodes.setPlayStateSync import ac.mdiq.podcini.storage.database.Episodes.setPlayStateSync
import ac.mdiq.podcini.storage.database.Episodes.shouldDeleteRemoveFromQueue
import ac.mdiq.podcini.storage.database.Feeds.addToMiscSyndicate import ac.mdiq.podcini.storage.database.Feeds.addToMiscSyndicate
import ac.mdiq.podcini.storage.database.Feeds.addToYoutubeSyndicate import ac.mdiq.podcini.storage.database.Feeds.addToYoutubeSyndicate
import ac.mdiq.podcini.storage.database.Feeds.shouldAutoDeleteItem import ac.mdiq.podcini.storage.database.Feeds.allowForAutoDelete
import ac.mdiq.podcini.storage.database.Queues import ac.mdiq.podcini.storage.database.Queues
import ac.mdiq.podcini.storage.database.Queues.addToQueueSync import ac.mdiq.podcini.storage.database.Queues.addToQueueSync
import ac.mdiq.podcini.storage.database.Queues.removeFromAllQueuesQuiet import ac.mdiq.podcini.storage.database.Queues.removeFromAllQueuesQuiet
import ac.mdiq.podcini.storage.database.Queues.removeFromQueue import ac.mdiq.podcini.storage.database.Queues.removeFromAllQueuesSync
import ac.mdiq.podcini.storage.database.Queues.removeFromQueueSync import ac.mdiq.podcini.storage.database.Queues.removeFromQueueSync
import ac.mdiq.podcini.storage.database.RealmDB.realm import ac.mdiq.podcini.storage.database.RealmDB.realm
import ac.mdiq.podcini.storage.database.RealmDB.runOnIOScope
import ac.mdiq.podcini.storage.database.RealmDB.upsert import ac.mdiq.podcini.storage.database.RealmDB.upsert
import ac.mdiq.podcini.storage.database.RealmDB.upsertBlk import ac.mdiq.podcini.storage.database.RealmDB.upsertBlk
import ac.mdiq.podcini.storage.model.* import ac.mdiq.podcini.storage.model.*
@ -40,7 +44,6 @@ import ac.mdiq.podcini.ui.actions.SwipeAction
import ac.mdiq.podcini.ui.activity.MainActivity import ac.mdiq.podcini.ui.activity.MainActivity
import ac.mdiq.podcini.ui.fragment.EpisodeInfoFragment import ac.mdiq.podcini.ui.fragment.EpisodeInfoFragment
import ac.mdiq.podcini.ui.fragment.FeedInfoFragment import ac.mdiq.podcini.ui.fragment.FeedInfoFragment
import ac.mdiq.podcini.ui.utils.LocalDeleteModal
import ac.mdiq.podcini.util.EventFlow import ac.mdiq.podcini.util.EventFlow
import ac.mdiq.podcini.util.FlowEvent import ac.mdiq.podcini.util.FlowEvent
import ac.mdiq.podcini.util.Logd import ac.mdiq.podcini.util.Logd
@ -66,14 +69,12 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField import androidx.compose.foundation.text.BasicTextField
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.AddCircle import androidx.compose.material.icons.filled.AddCircle
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Edit import androidx.compose.material.icons.filled.Edit
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.material3.pulltorefresh.PullToRefreshBox import androidx.compose.material3.pulltorefresh.PullToRefreshBox
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.input.pointer.pointerInput
@ -261,13 +262,9 @@ fun PlayStateDialog(selected: List<Episode>, onDismissRequest: () -> Unit) {
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(4.dp) Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(4.dp)
.clickable { .clickable {
for (item in selected) { for (item in selected) {
var item_ = runBlocking { setPlayStateSync(state.code, false, item) } var media: EpisodeMedia? = item.media
val media: EpisodeMedia? = item_.media val hasAlmostEnded = if (media != null) hasAlmostEnded(media) else false
val shouldAutoDelete = if (item_.feed == null) false else shouldAutoDeleteItem(item_.feed!!) var item_ = runBlocking { setPlayStateSync(state.code, item, hasAlmostEnded, false) }
if (media != null && hasAlmostEnded(media) && shouldAutoDelete) {
item_ = deleteMediaSync(context, item_)
if (shouldDeleteRemoveFromQueue()) removeFromQueueSync(null, item_)
}
when (state) { when (state) {
PlayState.UNPLAYED -> { PlayState.UNPLAYED -> {
if (isProviderConnected && item_.feed?.isLocalFeed != true && item_.media != null) { if (isProviderConnected && item_.feed?.isLocalFeed != true && item_.media != null) {
@ -276,6 +273,13 @@ fun PlayStateDialog(selected: List<Episode>, onDismissRequest: () -> Unit) {
} }
} }
PlayState.PLAYED -> { PlayState.PLAYED -> {
if (hasAlmostEnded) item_ = upsertBlk(item_) { it.media?.playbackCompletionDate = Date() }
val shouldAutoDelete = if (item_.feed == null) false else allowForAutoDelete(item_.feed!!)
media = item_.media
if (media != null && hasAlmostEnded && shouldAutoDelete) {
item_ = deleteMediaSync(context, item_)
if (prefDeleteRemovesFromQueue) removeFromQueueSync(null, item_)
} else if (prefRemoveFromQueueMarkedPlayed) removeFromAllQueuesSync(item_)
if (item_.feed?.isLocalFeed != true && (isProviderConnected || wifiSyncEnabledKey)) { if (item_.feed?.isLocalFeed != true && (isProviderConnected || wifiSyncEnabledKey)) {
val media_: EpisodeMedia? = item_.media val media_: EpisodeMedia? = item_.media
// not all items have media, Gpodder only cares about those that do // not all items have media, Gpodder only cares about those that do
@ -516,7 +520,20 @@ fun EpisodeLazyColumn(activity: MainActivity, vms: MutableList<EpisodeVM>, feed:
isExpanded = false isExpanded = false
selectMode = false selectMode = false
Logd(TAG, "ic_delete: ${selected.size}") Logd(TAG, "ic_delete: ${selected.size}")
LocalDeleteModal.deleteEpisodesWarnLocal(activity, selected) runOnIOScope {
for (item_ in selected) {
var item = item_
if (!item.isDownloaded && item.feed?.isLocalFeed != true) continue
val media = item.media
if (media != null) {
val almostEnded = hasAlmostEnded(media)
if (almostEnded && item.playState < PlayState.PLAYED.code) item = setPlayStateSync(PlayState.PLAYED.code, item, almostEnded, false)
if (almostEnded) item = upsert(item) { it.media?.playbackCompletionDate = Date() }
deleteEpisodeMedia(activity, item)
}
}
}
// LocalDeleteModal.deleteEpisodesWarnLocal(activity, selected)
}, verticalAlignment = Alignment.CenterVertically) { }, verticalAlignment = Alignment.CenterVertically) {
Icon(imageVector = ImageVector.vectorResource(id = R.drawable.ic_delete), "Delete media") Icon(imageVector = ImageVector.vectorResource(id = R.drawable.ic_delete), "Delete media")
Text(stringResource(id = R.string.delete_episode_label)) } }, Text(stringResource(id = R.string.delete_episode_label)) } },
@ -527,9 +544,7 @@ fun EpisodeLazyColumn(activity: MainActivity, vms: MutableList<EpisodeVM>, feed:
selectMode = false selectMode = false
Logd(TAG, "ic_download: ${selected.size}") Logd(TAG, "ic_download: ${selected.size}")
for (episode in selected) { for (episode in selected) {
if (episode.media != null && episode.feed != null && !episode.feed!!.isLocalFeed) DownloadServiceInterface if (episode.media != null && episode.feed != null && !episode.feed!!.isLocalFeed) DownloadServiceInterface.get()?.download(activity, episode)
.get()
?.download(activity, episode)
} }
}, verticalAlignment = Alignment.CenterVertically) { }, verticalAlignment = Alignment.CenterVertically) {
Icon(imageVector = ImageVector.vectorResource(id = R.drawable.ic_download), "Download") Icon(imageVector = ImageVector.vectorResource(id = R.drawable.ic_download), "Download")
@ -551,7 +566,20 @@ fun EpisodeLazyColumn(activity: MainActivity, vms: MutableList<EpisodeVM>, feed:
isExpanded = false isExpanded = false
selectMode = false selectMode = false
Logd(TAG, "ic_playlist_remove: ${selected.size}") Logd(TAG, "ic_playlist_remove: ${selected.size}")
removeFromQueue(*selected.toTypedArray()) runOnIOScope {
for (item_ in selected) {
var item = item_
val media = item.media
if (media != null) {
val almostEnded = hasAlmostEnded(media)
if (almostEnded && item.playState < PlayState.PLAYED.code) item = setPlayStateSync(PlayState.PLAYED.code, item, almostEnded, false)
if (almostEnded) item = upsert(item) { it.media?.playbackCompletionDate = Date() }
}
if (item.playState < PlayState.SKIPPED.code) setPlayState(PlayState.SKIPPED.code, false, item)
}
removeFromQueueSync(curQueue, *selected.toTypedArray())
// removeFromQueue(*selected.toTypedArray())
}
}, verticalAlignment = Alignment.CenterVertically) { }, verticalAlignment = Alignment.CenterVertically) {
Icon(imageVector = ImageVector.vectorResource(id = R.drawable.ic_playlist_remove), "Remove from active queue") Icon(imageVector = ImageVector.vectorResource(id = R.drawable.ic_playlist_remove), "Remove from active queue")
Text(stringResource(id = R.string.remove_from_queue_label)) } }, Text(stringResource(id = R.string.remove_from_queue_label)) } },

View File

@ -217,8 +217,7 @@ class AudioPlayerFragment : Fragment() {
})) }))
Spacer(Modifier.weight(0.1f)) Spacer(Modifier.weight(0.1f))
Column(horizontalAlignment = Alignment.CenterHorizontally) { Column(horizontalAlignment = Alignment.CenterHorizontally) {
Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_playback_speed), tint = textColor, Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_playback_speed), tint = textColor, contentDescription = "speed",
contentDescription = "speed",
modifier = Modifier.width(43.dp).height(43.dp).clickable(onClick = { modifier = Modifier.width(43.dp).height(43.dp).clickable(onClick = {
VariableSpeedDialog.newInstance(booleanArrayOf(true, true, true), null)?.show(childFragmentManager, null) VariableSpeedDialog.newInstance(booleanArrayOf(true, true, true), null)?.show(childFragmentManager, null)
})) }))
@ -555,7 +554,6 @@ class AudioPlayerFragment : Fragment() {
private fun updatePlaybackSpeedButton(event: FlowEvent.SpeedChangedEvent) { private fun updatePlaybackSpeedButton(event: FlowEvent.SpeedChangedEvent) {
val speedStr: String = DecimalFormat("0.00").format(event.newSpeed.toDouble()) val speedStr: String = DecimalFormat("0.00").format(event.newSpeed.toDouble())
txtvPlaybackSpeed = speedStr txtvPlaybackSpeed = speedStr
// binding.butPlaybackSpeed.setSpeed(event.newSpeed) TODO
} }
@UnstableApi @UnstableApi
@ -594,6 +592,7 @@ class AudioPlayerFragment : Fragment() {
fun updateUi(media: Playable) { fun updateUi(media: Playable) {
Logd(TAG, "updateUi called $media") Logd(TAG, "updateUi called $media")
titleText = media.getEpisodeTitle() titleText = media.getEpisodeTitle()
txtvPlaybackSpeed = DecimalFormat("0.00").format(curSpeedFB.toDouble())
if (prevMedia?.getIdentifier() != media.getIdentifier()) imgLoc = ImageResourceUtils.getEpisodeListImageLocation(media) if (prevMedia?.getIdentifier() != media.getIdentifier()) imgLoc = ImageResourceUtils.getEpisodeListImageLocation(media)
if (isPlayingVideoLocally && (curMedia as? EpisodeMedia)?.episode?.feed?.preferences?.videoModePolicy != VideoMode.AUDIO_ONLY) { if (isPlayingVideoLocally && (curMedia as? EpisodeMedia)?.episode?.feed?.preferences?.videoModePolicy != VideoMode.AUDIO_ONLY) {
// (activity as MainActivity).bottomSheet.setLocked(true) // (activity as MainActivity).bottomSheet.setLocked(true)
@ -803,8 +802,6 @@ class AudioPlayerFragment : Fragment() {
fun loadMediaInfo() { fun loadMediaInfo() {
Logd(TAG, "loadMediaInfo() curMedia: ${curMedia?.getIdentifier()}") Logd(TAG, "loadMediaInfo() curMedia: ${curMedia?.getIdentifier()}")
val actMain = (activity as MainActivity) val actMain = (activity as MainActivity)
var i = 0
// while (curMedia == null && i++ < 6) runBlocking { delay(500) }
if (curMedia == null) { if (curMedia == null) {
if (actMain.isPlayerVisible()) actMain.setPlayerVisible(false) if (actMain.isPlayerVisible()) actMain.setPlayerVisible(false)
return return

View File

@ -13,7 +13,6 @@ import ac.mdiq.podcini.playback.service.PlaybackService.Companion.seekTo
import ac.mdiq.podcini.preferences.UsageStatistics import ac.mdiq.podcini.preferences.UsageStatistics
import ac.mdiq.podcini.preferences.UserPreferences import ac.mdiq.podcini.preferences.UserPreferences
import ac.mdiq.podcini.storage.database.Queues.addToQueue import ac.mdiq.podcini.storage.database.Queues.addToQueue
import ac.mdiq.podcini.storage.database.Queues.removeFromQueue
import ac.mdiq.podcini.storage.database.RealmDB.realm import ac.mdiq.podcini.storage.database.RealmDB.realm
import ac.mdiq.podcini.storage.database.RealmDB.runOnIOScope import ac.mdiq.podcini.storage.database.RealmDB.runOnIOScope
import ac.mdiq.podcini.storage.database.RealmDB.unmanaged import ac.mdiq.podcini.storage.database.RealmDB.unmanaged
@ -222,10 +221,10 @@ class EpisodeInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
})) }))
if (episode?.media != null && !inQueue) { if (episode?.media != null && !inQueue) {
Spacer(modifier = Modifier.weight(0.2f)) Spacer(modifier = Modifier.weight(0.2f))
val inQueueIconRes = if (inQueue) R.drawable.ic_playlist_play else R.drawable.ic_playlist_remove val inQueueIconRes = R.drawable.ic_playlist_remove
Icon(imageVector = ImageVector.vectorResource(inQueueIconRes), tint = MaterialTheme.colorScheme.tertiary, contentDescription = "inQueue", Icon(imageVector = ImageVector.vectorResource(inQueueIconRes), tint = MaterialTheme.colorScheme.tertiary, contentDescription = "inQueue",
modifier = Modifier.background(MaterialTheme.colorScheme.tertiaryContainer).width(24.dp).height(24.dp).clickable(onClick = { modifier = Modifier.background(MaterialTheme.colorScheme.tertiaryContainer).width(24.dp).height(24.dp).clickable(onClick = {
if (inQueue) removeFromQueue(episode!!) else addToQueue(episode!!) addToQueue(episode!!)
})) }))
} }
Spacer(modifier = Modifier.weight(0.2f)) Spacer(modifier = Modifier.weight(0.2f))

View File

@ -14,7 +14,7 @@ import ac.mdiq.podcini.preferences.UserPreferences
import ac.mdiq.podcini.preferences.UserPreferences.appPrefs import ac.mdiq.podcini.preferences.UserPreferences.appPrefs
import ac.mdiq.podcini.storage.database.Queues.clearQueue import ac.mdiq.podcini.storage.database.Queues.clearQueue
import ac.mdiq.podcini.storage.database.Queues.isQueueKeepSorted import ac.mdiq.podcini.storage.database.Queues.isQueueKeepSorted
import ac.mdiq.podcini.storage.database.Queues.moveInQueue import ac.mdiq.podcini.storage.database.Queues.moveInQueueSync
import ac.mdiq.podcini.storage.database.Queues.queueKeepSortedOrder import ac.mdiq.podcini.storage.database.Queues.queueKeepSortedOrder
import ac.mdiq.podcini.storage.database.RealmDB.realm import ac.mdiq.podcini.storage.database.RealmDB.realm
import ac.mdiq.podcini.storage.database.RealmDB.runOnIOScope import ac.mdiq.podcini.storage.database.RealmDB.runOnIOScope
@ -212,7 +212,10 @@ import kotlin.math.max
else rightActionState.value.performAction(episode, this@QueuesFragment, swipeActions.filter ?: EpisodeFilter()) else rightActionState.value.performAction(episode, this@QueuesFragment, swipeActions.filter ?: EpisodeFilter())
} }
EpisodeLazyColumn(activity as MainActivity, vms = vms, EpisodeLazyColumn(activity as MainActivity, vms = vms,
isDraggable = dragDropEnabled, dragCB = { iFrom, iTo -> moveInQueue(iFrom, iTo, true) }, isDraggable = dragDropEnabled, dragCB = { iFrom, iTo ->
// moveInQueue(iFrom, iTo, true)
runOnIOScope { moveInQueueSync(iFrom, iTo, true) }
},
leftSwipeCB = { leftCB(it) }, rightSwipeCB = { rightCB(it) }) leftSwipeCB = { leftCB(it) }, rightSwipeCB = { rightCB(it) })
} }
} }

View File

@ -401,8 +401,8 @@
<string name="pref_smart_mark_as_played_title">علم بذكاء أنها انتهت</string> <string name="pref_smart_mark_as_played_title">علم بذكاء أنها انتهت</string>
<string name="pref_skip_keeps_episodes_sum">ابقي الحلقات التي يتم تخطيها</string> <string name="pref_skip_keeps_episodes_sum">ابقي الحلقات التي يتم تخطيها</string>
<string name="pref_skip_keeps_episodes_title">الاحتفاظ بالحلقات التي تم تخطيها</string> <string name="pref_skip_keeps_episodes_title">الاحتفاظ بالحلقات التي تم تخطيها</string>
<string name="pref_favorite_keeps_episodes_sum">تعليم الحلقة كمفضلة يبقيها على الجهاز</string>
<string name="pref_favorite_keeps_episodes_title">الاحتفاظ بالحلقات المفضلة</string>
<string name="playback_pref">تشغيل</string> <string name="playback_pref">تشغيل</string>
<string name="playback_pref_sum">تحكم سماعة الأذن, وقت التقدم, لائحة الاستماع</string> <string name="playback_pref_sum">تحكم سماعة الأذن, وقت التقدم, لائحة الاستماع</string>
<string name="downloads_pref">تنزيلات</string> <string name="downloads_pref">تنزيلات</string>

View File

@ -351,8 +351,8 @@
<string name="pref_smart_mark_as_played_title">Marcat intel·ligent d\'episodis reproduïts</string> <string name="pref_smart_mark_as_played_title">Marcat intel·ligent d\'episodis reproduïts</string>
<string name="pref_skip_keeps_episodes_sum">Mantenir episodis quan són omesos</string> <string name="pref_skip_keeps_episodes_sum">Mantenir episodis quan són omesos</string>
<string name="pref_skip_keeps_episodes_title">Mantenir episodis omesos</string> <string name="pref_skip_keeps_episodes_title">Mantenir episodis omesos</string>
<string name="pref_favorite_keeps_episodes_sum">Mantenir els episodis quan s\'han marcat com a preferits</string>
<string name="pref_favorite_keeps_episodes_title">Mantenir episodis preferits</string>
<string name="playback_pref">Reproducció</string> <string name="playback_pref">Reproducció</string>
<string name="playback_pref_sum">Controls d\'auriculars, Intervals d\'avançada, Cua</string> <string name="playback_pref_sum">Controls d\'auriculars, Intervals d\'avançada, Cua</string>
<string name="downloads_pref">Baixades</string> <string name="downloads_pref">Baixades</string>

View File

@ -392,8 +392,8 @@
<string name="pref_smart_mark_as_played_title">Chytré označování jako poslechnuté</string> <string name="pref_smart_mark_as_played_title">Chytré označování jako poslechnuté</string>
<string name="pref_skip_keeps_episodes_sum">Neodstraňovat epizody při jejich přeskočení</string> <string name="pref_skip_keeps_episodes_sum">Neodstraňovat epizody při jejich přeskočení</string>
<string name="pref_skip_keeps_episodes_title">Ponechat přeskočené epizody</string> <string name="pref_skip_keeps_episodes_title">Ponechat přeskočené epizody</string>
<string name="pref_favorite_keeps_episodes_sum">Ponechat epizody označené jako oblíbené.</string>
<string name="pref_favorite_keeps_episodes_title">Ponechat oblíbené epizody</string>
<string name="playback_pref">Přehrávání</string> <string name="playback_pref">Přehrávání</string>
<string name="playback_pref_sum">Ovládání tlačítky sluchátek, přeskakování, fronta</string> <string name="playback_pref_sum">Ovládání tlačítky sluchátek, přeskakování, fronta</string>
<string name="downloads_pref">Stahování</string> <string name="downloads_pref">Stahování</string>

View File

@ -367,8 +367,8 @@
<string name="pref_smart_mark_as_played_title">Smart markering som afspillet</string> <string name="pref_smart_mark_as_played_title">Smart markering som afspillet</string>
<string name="pref_skip_keeps_episodes_sum">Behold afsnit når de bliver sprunget over</string> <string name="pref_skip_keeps_episodes_sum">Behold afsnit når de bliver sprunget over</string>
<string name="pref_skip_keeps_episodes_title">Behold oversprungne afsnit</string> <string name="pref_skip_keeps_episodes_title">Behold oversprungne afsnit</string>
<string name="pref_favorite_keeps_episodes_sum">Behold afsnit, når de er markeret som favorit</string>
<string name="pref_favorite_keeps_episodes_title">Behold favoritafsnit</string>
<string name="playback_pref">Afspilning</string> <string name="playback_pref">Afspilning</string>
<string name="playback_pref_sum">Hovedtelefonstyring, overspringsintervaller, kø</string> <string name="playback_pref_sum">Hovedtelefonstyring, overspringsintervaller, kø</string>
<string name="downloads_pref">Overførsler</string> <string name="downloads_pref">Overførsler</string>

View File

@ -371,8 +371,8 @@
<string name="pref_smart_mark_as_played_title">Intelligentes \"als abgespielt markieren\"</string> <string name="pref_smart_mark_as_played_title">Intelligentes \"als abgespielt markieren\"</string>
<string name="pref_skip_keeps_episodes_sum">Episoden behalten, wenn sie übersprungen werden</string> <string name="pref_skip_keeps_episodes_sum">Episoden behalten, wenn sie übersprungen werden</string>
<string name="pref_skip_keeps_episodes_title">Übersprungene Episoden behalten</string> <string name="pref_skip_keeps_episodes_title">Übersprungene Episoden behalten</string>
<string name="pref_favorite_keeps_episodes_sum">Episoden nicht löschen, wenn sie als Favorit markiert wurden</string>
<string name="pref_favorite_keeps_episodes_title">Favorisierte Episoden nicht löschen</string>
<string name="playback_pref">Wiedergabe</string> <string name="playback_pref">Wiedergabe</string>
<string name="playback_pref_sum">Kopfhörersteuerung, Sprungintervall, Warteschlange</string> <string name="playback_pref_sum">Kopfhörersteuerung, Sprungintervall, Warteschlange</string>
<string name="downloads_pref">Downloads</string> <string name="downloads_pref">Downloads</string>

View File

@ -382,8 +382,8 @@
<string name="pref_smart_mark_as_played_title">Marcado inteligente como reproducido</string> <string name="pref_smart_mark_as_played_title">Marcado inteligente como reproducido</string>
<string name="pref_skip_keeps_episodes_sum">Conservar los episodios cuando son saltados</string> <string name="pref_skip_keeps_episodes_sum">Conservar los episodios cuando son saltados</string>
<string name="pref_skip_keeps_episodes_title">Conservar episodios saltados</string> <string name="pref_skip_keeps_episodes_title">Conservar episodios saltados</string>
<string name="pref_favorite_keeps_episodes_sum">Conservar los episodios cuando se marcan como favoritos</string>
<string name="pref_favorite_keeps_episodes_title">Conservar episodios favoritos</string>
<string name="playback_pref">Reproducción</string> <string name="playback_pref">Reproducción</string>
<string name="playback_pref_sum">Control de auriculares, Saltar intervalos, Cola</string> <string name="playback_pref_sum">Control de auriculares, Saltar intervalos, Cola</string>
<string name="downloads_pref">Descargas</string> <string name="downloads_pref">Descargas</string>

View File

@ -339,8 +339,8 @@
<string name="pref_smart_mark_as_played_sum">Erreproduzitutako saioak markatu nahiz bukatzeko segundo batzuk falta</string> <string name="pref_smart_mark_as_played_sum">Erreproduzitutako saioak markatu nahiz bukatzeko segundo batzuk falta</string>
<string name="pref_skip_keeps_episodes_sum">Saioak gorde jaustean</string> <string name="pref_skip_keeps_episodes_sum">Saioak gorde jaustean</string>
<string name="pref_skip_keeps_episodes_title">Mantendu saltatutako saioak</string> <string name="pref_skip_keeps_episodes_title">Mantendu saltatutako saioak</string>
<string name="pref_favorite_keeps_episodes_sum">Mantendu gogoko gisa markatutako saioak</string>
<string name="pref_favorite_keeps_episodes_title">Mantendu gogoko saioak</string>
<string name="playback_pref">Erreprodukzioa</string> <string name="playback_pref">Erreprodukzioa</string>
<string name="playback_pref_sum">Aurikularren kontrolak, saltatu tarteak, ilara</string> <string name="playback_pref_sum">Aurikularren kontrolak, saltatu tarteak, ilara</string>
<string name="downloads_pref">Deskargak</string> <string name="downloads_pref">Deskargak</string>

View File

@ -355,8 +355,8 @@
<string name="pref_smart_mark_as_played_title">علامت گذاری هوشمند به پخش شده</string> <string name="pref_smart_mark_as_played_title">علامت گذاری هوشمند به پخش شده</string>
<string name="pref_skip_keeps_episodes_sum">نگه داشتن قسمت‌ها هنگام پریدن از رویشان</string> <string name="pref_skip_keeps_episodes_sum">نگه داشتن قسمت‌ها هنگام پریدن از رویشان</string>
<string name="pref_skip_keeps_episodes_title">نگه‌داری قسمت‌های پریده</string> <string name="pref_skip_keeps_episodes_title">نگه‌داری قسمت‌های پریده</string>
<string name="pref_favorite_keeps_episodes_sum">نگه داری قسمت‌ها هنکامی که علامت محبوب خورده‌اند</string>
<string name="pref_favorite_keeps_episodes_title">نگه داری قسمت‌های محبوب</string>
<string name="playback_pref">پخش</string> <string name="playback_pref">پخش</string>
<string name="playback_pref_sum">کنترل هدفون ، رد کردن فواصل ، صف</string> <string name="playback_pref_sum">کنترل هدفون ، رد کردن فواصل ، صف</string>
<string name="downloads_pref">بارگیری‌ها</string> <string name="downloads_pref">بارگیری‌ها</string>

View File

@ -346,8 +346,8 @@
<string name="pref_smart_mark_as_played_title">Älykäs toistetuksi merkitseminen</string> <string name="pref_smart_mark_as_played_title">Älykäs toistetuksi merkitseminen</string>
<string name="pref_skip_keeps_episodes_sum">Säilytä jaksot, kun ne ohitetaan</string> <string name="pref_skip_keeps_episodes_sum">Säilytä jaksot, kun ne ohitetaan</string>
<string name="pref_skip_keeps_episodes_title">Säilytä ohitetut jaksot</string> <string name="pref_skip_keeps_episodes_title">Säilytä ohitetut jaksot</string>
<string name="pref_favorite_keeps_episodes_sum">Säilytä suosikeiksi merkityt jaksot</string>
<string name="pref_favorite_keeps_episodes_title">Säilytä suosikkijaksot</string>
<string name="playback_pref">Toisto</string> <string name="playback_pref">Toisto</string>
<string name="playback_pref_sum">Kuulokkeiden ohjaimet, ohitusaikavälit, jono</string> <string name="playback_pref_sum">Kuulokkeiden ohjaimet, ohitusaikavälit, jono</string>
<string name="downloads_pref">Lataukset</string> <string name="downloads_pref">Lataukset</string>

View File

@ -383,8 +383,8 @@
<string name="pref_smart_mark_as_played_title">Marquer comme lu intelligemment</string> <string name="pref_smart_mark_as_played_title">Marquer comme lu intelligemment</string>
<string name="pref_skip_keeps_episodes_sum">Garder les épisodes quand ils sont passés</string> <string name="pref_skip_keeps_episodes_sum">Garder les épisodes quand ils sont passés</string>
<string name="pref_skip_keeps_episodes_title">Garder les épisodes passés</string> <string name="pref_skip_keeps_episodes_title">Garder les épisodes passés</string>
<string name="pref_favorite_keeps_episodes_sum">Garder les épisodes marqués comme favoris</string>
<string name="pref_favorite_keeps_episodes_title">Garder les épisodes favoris</string>
<string name="playback_pref">Lecture</string> <string name="playback_pref">Lecture</string>
<string name="playback_pref_sum">Contrôles du casque, durée des sauts, liste de lecture</string> <string name="playback_pref_sum">Contrôles du casque, durée des sauts, liste de lecture</string>
<string name="downloads_pref">Téléchargements</string> <string name="downloads_pref">Téléchargements</string>

View File

@ -366,8 +366,8 @@
<string name="pref_smart_mark_as_played_title">Marcado intelixente como escoitado</string> <string name="pref_smart_mark_as_played_title">Marcado intelixente como escoitado</string>
<string name="pref_skip_keeps_episodes_sum">Manter os episodios cando son omitidos</string> <string name="pref_skip_keeps_episodes_sum">Manter os episodios cando son omitidos</string>
<string name="pref_skip_keeps_episodes_title">Manter episodios omitidos</string> <string name="pref_skip_keeps_episodes_title">Manter episodios omitidos</string>
<string name="pref_favorite_keeps_episodes_sum">Manter episodios cando están marcados como favoritos</string>
<string name="pref_favorite_keeps_episodes_title">Manter episodios favoritos</string>
<string name="playback_pref">Reprodución</string> <string name="playback_pref">Reprodución</string>
<string name="playback_pref_sum">Control de auriculares, Intervalos de salto, Cola</string> <string name="playback_pref_sum">Control de auriculares, Intervalos de salto, Cola</string>
<string name="downloads_pref">Descargas</string> <string name="downloads_pref">Descargas</string>

View File

@ -282,8 +282,8 @@
<string name="pref_auto_delete_sum">Hapus episode ketika pemutaran selesai</string> <string name="pref_auto_delete_sum">Hapus episode ketika pemutaran selesai</string>
<string name="pref_smart_mark_as_played_sum">Tandai episode sebagai telah diputar jika kurang dari beberapa detik waktu masih tersisa</string> <string name="pref_smart_mark_as_played_sum">Tandai episode sebagai telah diputar jika kurang dari beberapa detik waktu masih tersisa</string>
<string name="pref_skip_keeps_episodes_sum">Simpan episode ketika dilewatkan</string> <string name="pref_skip_keeps_episodes_sum">Simpan episode ketika dilewatkan</string>
<string name="pref_favorite_keeps_episodes_sum">Simpan episode saat difavoritkan</string>
<string name="pref_favorite_keeps_episodes_title">Simpan episode favorit</string>
<string name="playback_pref">Pemutaran</string> <string name="playback_pref">Pemutaran</string>
<string name="playback_pref_sum">Kontrol headphone, Jangka waktu lewati, Antrean</string> <string name="playback_pref_sum">Kontrol headphone, Jangka waktu lewati, Antrean</string>
<string name="downloads_pref_sum">Waktu pembaharuan, Jaringan seluler, Unduh otomatis, Hapus otomatis</string> <string name="downloads_pref_sum">Waktu pembaharuan, Jaringan seluler, Unduh otomatis, Hapus otomatis</string>

View File

@ -383,8 +383,8 @@
<string name="pref_smart_mark_as_played_title">Marcatura intelligente</string> <string name="pref_smart_mark_as_played_title">Marcatura intelligente</string>
<string name="pref_skip_keeps_episodes_sum">Mantiene gli episodi nella coda quando vengono saltati</string> <string name="pref_skip_keeps_episodes_sum">Mantiene gli episodi nella coda quando vengono saltati</string>
<string name="pref_skip_keeps_episodes_title">Manteni gli episodi saltati</string> <string name="pref_skip_keeps_episodes_title">Manteni gli episodi saltati</string>
<string name="pref_favorite_keeps_episodes_sum">Mantiene gli episodi se sono segnati come preferiti</string>
<string name="pref_favorite_keeps_episodes_title">Mantieni episodi preferiti</string>
<string name="playback_pref">Riproduzione</string> <string name="playback_pref">Riproduzione</string>
<string name="playback_pref_sum">Controllo cuffie, salto intervalli, coda</string> <string name="playback_pref_sum">Controllo cuffie, salto intervalli, coda</string>
<string name="downloads_pref">Download</string> <string name="downloads_pref">Download</string>

View File

@ -391,8 +391,8 @@
<string name="pref_smart_mark_as_played_title">סימון חכם כנוגנו</string> <string name="pref_smart_mark_as_played_title">סימון חכם כנוגנו</string>
<string name="pref_skip_keeps_episodes_sum">להשאיר פרקים למרות שדילגת עליהם</string> <string name="pref_skip_keeps_episodes_sum">להשאיר פרקים למרות שדילגת עליהם</string>
<string name="pref_skip_keeps_episodes_title">להשאיר פרקים שדולגו</string> <string name="pref_skip_keeps_episodes_title">להשאיר פרקים שדולגו</string>
<string name="pref_favorite_keeps_episodes_sum">להשאיר פרקים כשהם מסומנים כמועדפים</string>
<string name="pref_favorite_keeps_episodes_title">להשאיר פרקים מועדפים</string>
<string name="playback_pref">ניגון</string> <string name="playback_pref">ניגון</string>
<string name="playback_pref_sum">שליטה דרך האוזניות, מרחקי דילוג, תור</string> <string name="playback_pref_sum">שליטה דרך האוזניות, מרחקי דילוג, תור</string>
<string name="downloads_pref">הורדות</string> <string name="downloads_pref">הורדות</string>

View File

@ -336,8 +336,8 @@
<string name="pref_smart_mark_as_played_title">똑똑하게 재생한 것으로 표시</string> <string name="pref_smart_mark_as_played_title">똑똑하게 재생한 것으로 표시</string>
<string name="pref_skip_keeps_episodes_sum">에피소드를 넘겼을 경우에도 유지</string> <string name="pref_skip_keeps_episodes_sum">에피소드를 넘겼을 경우에도 유지</string>
<string name="pref_skip_keeps_episodes_title">넘긴 에피소드 유지</string> <string name="pref_skip_keeps_episodes_title">넘긴 에피소드 유지</string>
<string name="pref_favorite_keeps_episodes_sum">즐겨 찾기로 표시한 에피소드를 유지합니다</string>
<string name="pref_favorite_keeps_episodes_title">즐겨 찾기 에피소드 유지</string>
<string name="playback_pref">재생</string> <string name="playback_pref">재생</string>
<string name="playback_pref_sum">헤드폰 조작, 구간 넘기기, 대기열</string> <string name="playback_pref_sum">헤드폰 조작, 구간 넘기기, 대기열</string>
<string name="downloads_pref">다운로드</string> <string name="downloads_pref">다운로드</string>

View File

@ -343,8 +343,8 @@
<string name="pref_smart_mark_as_played_title">Smart markering som avspilt</string> <string name="pref_smart_mark_as_played_title">Smart markering som avspilt</string>
<string name="pref_skip_keeps_episodes_sum">Behold episoder når de hoppes over</string> <string name="pref_skip_keeps_episodes_sum">Behold episoder når de hoppes over</string>
<string name="pref_skip_keeps_episodes_title">Behold episoder som er hoppet over</string> <string name="pref_skip_keeps_episodes_title">Behold episoder som er hoppet over</string>
<string name="pref_favorite_keeps_episodes_sum">Behold episoder når de er merket som favoritt</string>
<string name="pref_favorite_keeps_episodes_title">Behold favorittepisoder</string>
<string name="playback_pref">Avspilling</string> <string name="playback_pref">Avspilling</string>
<string name="playback_pref_sum">Hodetelefon-kontroller, spole-intervaller, kø</string> <string name="playback_pref_sum">Hodetelefon-kontroller, spole-intervaller, kø</string>
<string name="downloads_pref">Nedlastinger</string> <string name="downloads_pref">Nedlastinger</string>

View File

@ -344,8 +344,8 @@
<string name="pref_smart_mark_as_played_title">Slim markeren als afgespeeld</string> <string name="pref_smart_mark_as_played_title">Slim markeren als afgespeeld</string>
<string name="pref_skip_keeps_episodes_sum">Bewaar afleveringen wanneer ze worden overgeslagen</string> <string name="pref_skip_keeps_episodes_sum">Bewaar afleveringen wanneer ze worden overgeslagen</string>
<string name="pref_skip_keeps_episodes_title">Overgeslagen afleveringen behouden</string> <string name="pref_skip_keeps_episodes_title">Overgeslagen afleveringen behouden</string>
<string name="pref_favorite_keeps_episodes_sum">Afleveringen bewaren als ze als favoriet gemarkeerd zijn</string>
<string name="pref_favorite_keeps_episodes_title">Favoriete afleveringen bewaren</string>
<string name="playback_pref">Afspelen</string> <string name="playback_pref">Afspelen</string>
<string name="playback_pref_sum">Onderbreken, afspelen, wachtrij</string> <string name="playback_pref_sum">Onderbreken, afspelen, wachtrij</string>
<string name="downloads_pref">Downloads</string> <string name="downloads_pref">Downloads</string>

View File

@ -368,8 +368,8 @@
<string name="pref_smart_mark_as_played_sum">Oznacz odcinek jako odtworzony, jeśli do końca pozostało mniej niż określona ilość czasu</string> <string name="pref_smart_mark_as_played_sum">Oznacz odcinek jako odtworzony, jeśli do końca pozostało mniej niż określona ilość czasu</string>
<string name="pref_skip_keeps_episodes_sum">Zachowuje pominięte odcinki w kolejce</string> <string name="pref_skip_keeps_episodes_sum">Zachowuje pominięte odcinki w kolejce</string>
<string name="pref_skip_keeps_episodes_title">Zachowaj pominięte odcinki</string> <string name="pref_skip_keeps_episodes_title">Zachowaj pominięte odcinki</string>
<string name="pref_favorite_keeps_episodes_sum">Zachowaj odcinki gdy są oznaczone jako ulubione</string>
<string name="pref_favorite_keeps_episodes_title">Zachowaj ulubione odcinki</string>
<string name="playback_pref">Odtwarzanie</string> <string name="playback_pref">Odtwarzanie</string>
<string name="playback_pref_sum">Kontrola za pomocą słuchawek, Pomijanie, Kolejka</string> <string name="playback_pref_sum">Kontrola za pomocą słuchawek, Pomijanie, Kolejka</string>
<string name="downloads_pref">Pobrane</string> <string name="downloads_pref">Pobrane</string>

View File

@ -363,8 +363,8 @@
<string name="pref_smart_mark_as_played_title">Marcação inteligente quando reproduzido</string> <string name="pref_smart_mark_as_played_title">Marcação inteligente quando reproduzido</string>
<string name="pref_skip_keeps_episodes_sum">Mantém os episódios quando eles forem pulados</string> <string name="pref_skip_keeps_episodes_sum">Mantém os episódios quando eles forem pulados</string>
<string name="pref_skip_keeps_episodes_title">Manter episódios ignorados</string> <string name="pref_skip_keeps_episodes_title">Manter episódios ignorados</string>
<string name="pref_favorite_keeps_episodes_sum">Manter os episódios quando eles estiverem marcados como favoritos</string>
<string name="pref_favorite_keeps_episodes_title">Manter episódios favoritos</string>
<string name="playback_pref">Reprodução</string> <string name="playback_pref">Reprodução</string>
<string name="playback_pref_sum">Controles de fone de ouvido, intervalos para saltar, Fila</string> <string name="playback_pref_sum">Controles de fone de ouvido, intervalos para saltar, Fila</string>
<string name="downloads_pref">Downloads</string> <string name="downloads_pref">Downloads</string>

View File

@ -382,8 +382,8 @@
<string name="pref_smart_mark_as_played_title">Marcar como reproduzido (inteligente)</string> <string name="pref_smart_mark_as_played_title">Marcar como reproduzido (inteligente)</string>
<string name="pref_skip_keeps_episodes_sum">Manter episódios mesmo se tiverem sido ignorados</string> <string name="pref_skip_keeps_episodes_sum">Manter episódios mesmo se tiverem sido ignorados</string>
<string name="pref_skip_keeps_episodes_title">Manter episódios ignorados</string> <string name="pref_skip_keeps_episodes_title">Manter episódios ignorados</string>
<string name="pref_favorite_keeps_episodes_sum">Manter episódios se forem assinalados como favoritos</string>
<string name="pref_favorite_keeps_episodes_title">Manter episódios favoritos</string>
<string name="playback_pref">Reprodução</string> <string name="playback_pref">Reprodução</string>
<string name="playback_pref_sum">Controlo com auscultador, intervalos e fila</string> <string name="playback_pref_sum">Controlo com auscultador, intervalos e fila</string>
<string name="downloads_pref">Descargas</string> <string name="downloads_pref">Descargas</string>

View File

@ -379,8 +379,8 @@
<string name="pref_smart_mark_as_played_title">Marchează inteligent ca fiind redate</string> <string name="pref_smart_mark_as_played_title">Marchează inteligent ca fiind redate</string>
<string name="pref_skip_keeps_episodes_sum">Păstrează episoade când acestea sunt sărite</string> <string name="pref_skip_keeps_episodes_sum">Păstrează episoade când acestea sunt sărite</string>
<string name="pref_skip_keeps_episodes_title">Păstrează episoadele sărite</string> <string name="pref_skip_keeps_episodes_title">Păstrează episoadele sărite</string>
<string name="pref_favorite_keeps_episodes_sum">Păstrează episoadele când sunt marcate ca favorite</string>
<string name="pref_favorite_keeps_episodes_title">Păstrează episoadele favorite</string>
<string name="playback_pref">Ascultare</string> <string name="playback_pref">Ascultare</string>
<string name="playback_pref_sum">Controlul căștilor, Sari peste un interval de timp, Coadă</string> <string name="playback_pref_sum">Controlul căștilor, Sari peste un interval de timp, Coadă</string>
<string name="downloads_pref">Descărcări</string> <string name="downloads_pref">Descărcări</string>

View File

@ -372,8 +372,8 @@
<string name="pref_smart_mark_as_played_title">Отметка «Прослушанное» до окончания</string> <string name="pref_smart_mark_as_played_title">Отметка «Прослушанное» до окончания</string>
<string name="pref_skip_keeps_episodes_sum">Сохранять выпуски, которые были пропущены</string> <string name="pref_skip_keeps_episodes_sum">Сохранять выпуски, которые были пропущены</string>
<string name="pref_skip_keeps_episodes_title">Сохранять пропущенные выпуски</string> <string name="pref_skip_keeps_episodes_title">Сохранять пропущенные выпуски</string>
<string name="pref_favorite_keeps_episodes_sum">Хранить выпуски, добавленные в избранное</string>
<string name="pref_favorite_keeps_episodes_title">Хранить избранные выпуски</string>
<string name="playback_pref">Воспроизведение</string> <string name="playback_pref">Воспроизведение</string>
<string name="playback_pref_sum">Кнопки гарнитуры, шаг перемотки, очередь</string> <string name="playback_pref_sum">Кнопки гарнитуры, шаг перемотки, очередь</string>
<string name="downloads_pref">Загрузки</string> <string name="downloads_pref">Загрузки</string>

View File

@ -391,8 +391,8 @@
<string name="pref_smart_mark_as_played_title">Inteligentné označovanie ako vypočuté</string> <string name="pref_smart_mark_as_played_title">Inteligentné označovanie ako vypočuté</string>
<string name="pref_skip_keeps_episodes_sum">Neodstraňovať epizódy pri ich preskočení</string> <string name="pref_skip_keeps_episodes_sum">Neodstraňovať epizódy pri ich preskočení</string>
<string name="pref_skip_keeps_episodes_title">Nemazať preskočené epizódy</string> <string name="pref_skip_keeps_episodes_title">Nemazať preskočené epizódy</string>
<string name="pref_favorite_keeps_episodes_sum">Ponechať epizódy, ktoré sú označené ako obľúbené</string>
<string name="pref_favorite_keeps_episodes_title">Ponechať obľúbené epizódy</string>
<string name="playback_pref">Prehrávanie</string> <string name="playback_pref">Prehrávanie</string>
<string name="playback_pref_sum">Ovládanie tlačidlami slúchadiel, preskakovanie, poradie</string> <string name="playback_pref_sum">Ovládanie tlačidlami slúchadiel, preskakovanie, poradie</string>
<string name="downloads_pref">Sťahovanie</string> <string name="downloads_pref">Sťahovanie</string>

View File

@ -367,8 +367,8 @@
<string name="pref_smart_mark_as_played_title">Smart markera som spelad</string> <string name="pref_smart_mark_as_played_title">Smart markera som spelad</string>
<string name="pref_skip_keeps_episodes_sum">Ta inte bort episoder när de hoppas över</string> <string name="pref_skip_keeps_episodes_sum">Ta inte bort episoder när de hoppas över</string>
<string name="pref_skip_keeps_episodes_title">Behåll överhoppade episoder</string> <string name="pref_skip_keeps_episodes_title">Behåll överhoppade episoder</string>
<string name="pref_favorite_keeps_episodes_sum">Behåll episoder när de är favoritmarkerade</string>
<string name="pref_favorite_keeps_episodes_title">Behåll favoritepisoder</string>
<string name="playback_pref">Uppspelning</string> <string name="playback_pref">Uppspelning</string>
<string name="playback_pref_sum">Hörlurskontroller, Överhoppningsintervaller, Kö</string> <string name="playback_pref_sum">Hörlurskontroller, Överhoppningsintervaller, Kö</string>
<string name="downloads_pref">Nedladdningar</string> <string name="downloads_pref">Nedladdningar</string>

View File

@ -353,8 +353,8 @@
<string name="pref_smart_mark_as_played_title">Oynatıldı olarak işaretle</string> <string name="pref_smart_mark_as_played_title">Oynatıldı olarak işaretle</string>
<string name="pref_skip_keeps_episodes_sum">Bölümler atlandığında tutmaya devam et</string> <string name="pref_skip_keeps_episodes_sum">Bölümler atlandığında tutmaya devam et</string>
<string name="pref_skip_keeps_episodes_title">Atlanan bölümleri sakla</string> <string name="pref_skip_keeps_episodes_title">Atlanan bölümleri sakla</string>
<string name="pref_favorite_keeps_episodes_sum">Bölümler favori olarak işaretlendiğinde tut</string>
<string name="pref_favorite_keeps_episodes_title">Favori bölümleri tut</string>
<string name="playback_pref">Çalma</string> <string name="playback_pref">Çalma</string>
<string name="playback_pref_sum">Kulaklık kontrolleri, Atlama aralığı ve Kuyruk ayarları</string> <string name="playback_pref_sum">Kulaklık kontrolleri, Atlama aralığı ve Kuyruk ayarları</string>
<string name="downloads_pref">İndirilenler</string> <string name="downloads_pref">İndirilenler</string>

View File

@ -391,8 +391,8 @@
<string name="pref_smart_mark_as_played_title">Розумне позначення прослуханих епізодів</string> <string name="pref_smart_mark_as_played_title">Розумне позначення прослуханих епізодів</string>
<string name="pref_skip_keeps_episodes_sum">Зберігати пропущені епізоди при програванні </string> <string name="pref_skip_keeps_episodes_sum">Зберігати пропущені епізоди при програванні </string>
<string name="pref_skip_keeps_episodes_title">Зберігати пропущені епізоди</string> <string name="pref_skip_keeps_episodes_title">Зберігати пропущені епізоди</string>
<string name="pref_favorite_keeps_episodes_sum">Зберігати епізоди що помічені як улюблені</string>
<string name="pref_favorite_keeps_episodes_title">Зберігати улюблені епізоди</string>
<string name="playback_pref">Відтворення</string> <string name="playback_pref">Відтворення</string>
<string name="playback_pref_sum">Керування навушниками, Пропуск інтервалів, Черга</string> <string name="playback_pref_sum">Керування навушниками, Пропуск інтервалів, Черга</string>
<string name="downloads_pref">Завантаження</string> <string name="downloads_pref">Завантаження</string>

View File

@ -359,8 +359,8 @@
<string name="pref_smart_mark_as_played_title">智能标记为已播放</string> <string name="pref_smart_mark_as_played_title">智能标记为已播放</string>
<string name="pref_skip_keeps_episodes_sum">当剧集被跳过时保留它们</string> <string name="pref_skip_keeps_episodes_sum">当剧集被跳过时保留它们</string>
<string name="pref_skip_keeps_episodes_title">保留跳过的节目</string> <string name="pref_skip_keeps_episodes_title">保留跳过的节目</string>
<string name="pref_favorite_keeps_episodes_sum">标记节目为收藏时保留节目</string>
<string name="pref_favorite_keeps_episodes_title">保留收藏的节目</string>
<string name="playback_pref">播放</string> <string name="playback_pref">播放</string>
<string name="playback_pref_sum">耳机控制、跳过间隔、排队</string> <string name="playback_pref_sum">耳机控制、跳过间隔、排队</string>
<string name="downloads_pref">下载</string> <string name="downloads_pref">下载</string>

View File

@ -323,6 +323,8 @@
<string name="in_progress">In progress</string> <string name="in_progress">In progress</string>
<string name="skipped">Skipped</string> <string name="skipped">Skipped</string>
<string name="played">Played</string> <string name="played">Played</string>
<string name="again">Again</string>
<string name="forever">Forever</string>
<string name="ignored">Ignored</string> <string name="ignored">Ignored</string>
<string name="remove_from_favorite_label">Remove from favorites</string> <string name="remove_from_favorite_label">Remove from favorites</string>
<string name="visit_website_label">Visit website</string> <string name="visit_website_label">Visit website</string>
@ -499,8 +501,9 @@
<string name="pref_smart_mark_as_played_title">Smart mark as played</string> <string name="pref_smart_mark_as_played_title">Smart mark as played</string>
<string name="pref_skip_keeps_episodes_sum">Keep episodes when they are skipped</string> <string name="pref_skip_keeps_episodes_sum">Keep episodes when they are skipped</string>
<string name="pref_skip_keeps_episodes_title">Keep skipped episodes</string> <string name="pref_skip_keeps_episodes_title">Keep skipped episodes</string>
<string name="pref_favorite_keeps_episodes_sum">Keep episodes when they are marked favorite</string>
<string name="pref_favorite_keeps_episodes_title">Keep favorite episodes</string> <string name="pref_keeps_important_episodes_sum">Keep episodes when they are marked Super or set as Again or Forever.</string>
<string name="pref_keeps_important_episodes_title">Keep important episodes</string>
<string name="playback_pref">Playback</string> <string name="playback_pref">Playback</string>
<string name="playback_pref_sum">Headphone controls, Skip intervals, Queue</string> <string name="playback_pref_sum">Headphone controls, Skip intervals, Queue</string>
<string name="downloads_pref">Downloads</string> <string name="downloads_pref">Downloads</string>

View File

@ -36,8 +36,8 @@
android:defaultValue="true" android:defaultValue="true"
android:enabled="true" android:enabled="true"
android:key="prefFavoriteKeepsEpisode" android:key="prefFavoriteKeepsEpisode"
android:summary="@string/pref_favorite_keeps_episodes_sum" android:summary="@string/pref_keeps_important_episodes_sum"
android:title="@string/pref_favorite_keeps_episodes_title"/> android:title="@string/pref_keeps_important_episodes_title"/>
<SwitchPreferenceCompat <SwitchPreferenceCompat
android:defaultValue="true" android:defaultValue="true"
android:enabled="true" android:enabled="true"

View File

@ -114,13 +114,13 @@
android:key="prefFollowQueue" android:key="prefFollowQueue"
android:summary="@string/pref_followQueue_sum" android:summary="@string/pref_followQueue_sum"
android:title="@string/pref_followQueue_title"/> android:title="@string/pref_followQueue_title"/>
<ac.mdiq.podcini.preferences.MaterialListPreference <!-- <ac.mdiq.podcini.preferences.MaterialListPreference-->
android:defaultValue="30" <!-- android:defaultValue="30"-->
android:entries="@array/smart_mark_as_played_values" <!-- android:entries="@array/smart_mark_as_played_values"-->
android:entryValues="@array/smart_mark_as_played_values" <!-- android:entryValues="@array/smart_mark_as_played_values"-->
android:key="prefSmartMarkAsPlayedSecs" <!-- android:key="prefSmartMarkAsPlayedSecs"-->
android:summary="@string/pref_smart_mark_as_played_sum" <!-- android:summary="@string/pref_smart_mark_as_played_sum"-->
android:title="@string/pref_smart_mark_as_played_title"/> <!-- android:title="@string/pref_smart_mark_as_played_title"/>-->
<SwitchPreferenceCompat <SwitchPreferenceCompat
android:defaultValue="true" android:defaultValue="true"
android:enabled="true" android:enabled="true"

View File

@ -1,3 +1,14 @@
# 6.13.2
* replaced the setting of prefSmartMarkAsPlayedSecs by an adaptive internal val smartMarkAsPlayedPercent = 95
* if the episode is played to over 95% of its duration, it's considered played
* reworked the chain actions of set play state, remove from queue, and delete media
* fixed the issue of fully played episode being marked as Skipped
* fixed speed indicator not updating issue in PlayerUI
* added Again and Forever states to episode filter
* in Settings, "Keep favorite episode" is changed to "Keep important episodes", and it applies to episodes set as Super, Again, or Forever
* some reduction in access to Preferences files for improved efficiency
# 6.13.1 # 6.13.1
* fixed the misbehavior (from 6.13.0) of rewind/forward/progress in PlayerUI * fixed the misbehavior (from 6.13.0) of rewind/forward/progress in PlayerUI

View File

@ -0,0 +1,10 @@
Version 6.13.1
* replaced the setting of prefSmartMarkAsPlayedSecs by an adaptive internal val smartMarkAsPlayedPercent = 95
* if the episode is played to over 95% of its duration, it's considered played
* reworked the chain actions of set play state, remove from queue, and delete media
* fixed the issue of fully played episode being marked as Skipped
* fixed speed indicator not updating issue in PlayerUI
* added Again and Forever states to episode filter
* in Settings, "Keep favorite episode" is changed to "Keep important episodes", and it applies to episodes set as Super, Again, or Forever
* some reduction in access to Preferences files for improved efficiency