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"
// testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
versionCode 3020288
versionName "6.13.1"
versionCode 3020289
versionName "6.13.2"
applicationId "ac.mdiq.podcini.R"
def commit = ""
@ -167,6 +167,13 @@ android {
androidResources {
additionalParameters "--no-version-vectors"
}
dependenciesInfo {
// Disables dependency metadata when building APKs.
includeInApk = false
// Disables dependency metadata when building Android App Bundles.
includeInBundle = false
}
}
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.queue.SynchronizationQueueSink
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.appPrefs
import ac.mdiq.podcini.storage.database.Episodes
import ac.mdiq.podcini.storage.database.LogsAndStats
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.runOnIOScope
import ac.mdiq.podcini.storage.database.RealmDB.upsertBlk
import ac.mdiq.podcini.storage.model.DownloadResult
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())
}
@OptIn(UnstableApi::class) override fun cancel(context: Context, media: EpisodeMedia) {
override fun cancel(context: Context, media: EpisodeMedia) {
Logd(TAG, "starting cancel")
// This needs to be done here, not in the worker. Reason: The worker might or might not be running.
val item_ = media.episodeOrFetch()
@ -84,7 +87,7 @@ class DownloadServiceInterfaceImpl : DownloadServiceInterface() {
workInfoList.forEach { workInfo ->
if (workInfo.tags.contains(WORK_DATA_WAS_QUEUED)) {
val item_ = media.episodeOrFetch()
if (item_ != null) Queues.removeFromQueue(item_)
if (item_ != null) runOnIOScope { removeFromQueueSync(curQueue, item_) }
}
}
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.setAllowMobileFor
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.appPrefs
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.getFeedListDownloadUrls
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.upsert
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)
updatedItems.add(result.second)
}
removeFromQueue(*updatedItems.toTypedArray())
// removeFromQueue(*updatedItems.toTypedArray())
runOnIOScope {
removeFromQueueSync(curQueue, *updatedItems.toTypedArray())
for (episode in updatedItems) {
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.storage.database.Episodes.deleteMediaSync
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.shouldDeleteRemoveFromQueue
import ac.mdiq.podcini.storage.database.Feeds.shouldAutoDeleteItem
import ac.mdiq.podcini.storage.database.Feeds.allowForAutoDelete
import ac.mdiq.podcini.storage.database.Queues.removeFromAllQueuesSync
import ac.mdiq.podcini.storage.database.Queues.removeFromQueueSync
import ac.mdiq.podcini.storage.database.RealmDB.realm
import ac.mdiq.podcini.storage.database.RealmDB.runOnIOScope
import ac.mdiq.podcini.storage.database.RealmDB.unmanaged
import ac.mdiq.podcini.storage.database.RealmDB.upsert
import ac.mdiq.podcini.storage.database.RealmDB.upsertBlk
import ac.mdiq.podcini.storage.model.*
import ac.mdiq.podcini.storage.model.CurrentState.Companion.NO_MEDIA_PLAYING
@ -218,7 +220,9 @@ class PlaybackService : MediaLibraryService() {
// sound is about to change, eg. bluetooth -> speaker
Log.d(TAG, "audioBecomingNoisy onReceive called with action: ${intent.action}")
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
}
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 {
override fun statusChanged(newInfo: MediaPlayerInfo?) {
currentMediaType = mPlayer?.mediaType ?: MediaType.UNKNOWN
@ -354,35 +361,31 @@ class PlaybackService : MediaLibraryService() {
}
}
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 {
val shouldSkipKeepEpisode = appPrefs.getBoolean(UserPreferences.Prefs.prefSkipKeepsEpisode.name, true)
val shouldFavoriteKeepEpisode = appPrefs.getBoolean(UserPreferences.Prefs.prefFavoriteKeepsEpisode.name, true)
if (ended || smartMarkAsPlayed || autoSkipped || (skipped && !shouldSkipKeepEpisode)) {
Logd(TAG, "onPostPlayback ended: $ended smartMarkAsPlayed: $smartMarkAsPlayed autoSkipped: $autoSkipped skipped: $skipped")
// 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 shouldAutoDelete = (action == AutoDeleteAction.ALWAYS ||
(action == AutoDeleteAction.GLOBAL && item?.feed != null && shouldAutoDeleteItem(item!!.feed!!)))
if (playable is EpisodeMedia && shouldAutoDelete && (item?.isSUPER != true || !shouldFavoriteKeepEpisode)) {
(action == AutoDeleteAction.GLOBAL && item?.feed != null && allowForAutoDelete(item!!.feed!!)))
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 (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) {
Logd(TAG, "onPlaybackStart position: $position")
val delayInterval = positionUpdateInterval(playable.getDuration())
Logd(TAG, "onPlaybackStart position: $position delayInterval: $delayInterval")
taskManager.startWidgetUpdater(delayInterval)
// if (position != Playable.INVALID_TIME) playable.setPosition(position + (delayInterval/2).toInt())
if (position != Playable.INVALID_TIME) playable.setPosition(position)
@ -453,7 +456,7 @@ class PlaybackService : MediaLibraryService() {
writeNoMediaPlaying()
return null
}
// EventFlow.postEvent(FlowEvent.PlayEvent(nextItem))
EventFlow.postEvent(FlowEvent.PlayEvent(nextItem))
return if (nextItem.media == null) null else unmanaged(nextItem.media!!)
}
override fun findMedia(url: String): Playable? {
@ -1183,11 +1186,11 @@ class PlaybackService : MediaLibraryService() {
}
}
private fun pauseIfPauseOnDisconnect() {
Logd(TAG, "pauseIfPauseOnDisconnect()")
transientPause = (MediaPlayerBase.status == PlayerStatus.PLAYING || MediaPlayerBase.status == PlayerStatus.FALLBACK)
if (isPauseOnHeadsetDisconnect && !isCasting) mPlayer?.pause(!isPersistNotify, false)
}
// private fun pauseIfPauseOnDisconnect() {
// Logd(TAG, "pauseIfPauseOnDisconnect()")
// transientPause = (MediaPlayerBase.status == PlayerStatus.PLAYING || MediaPlayerBase.status == PlayerStatus.FALLBACK)
// if (isPauseOnHeadsetDisconnect && !isCasting) mPlayer?.pause(!isPersistNotify, false)
// }
/**
* @param bluetooth true if the event for unpausing came from bluetooth
@ -1318,9 +1321,6 @@ class PlaybackService : MediaLibraryService() {
if (!isPositionSaverActive) {
var positionSaver = Runnable { callback.positionSaverTick() }
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)
Logd(TAG, "Started PositionSaver")
} else Logd(TAG, "Call to startPositionSaver was ignored.")
@ -1493,7 +1493,6 @@ class PlaybackService : MediaLibraryService() {
Logd(TAG, "Sleep timer expired")
shakeListener?.pause()
shakeListener = null
hasVibrated = false
}
}
@ -1547,7 +1546,6 @@ class PlaybackService : MediaLibraryService() {
val gX = event.values[0] / SensorManager.GRAVITY_EARTH
val gY = event.values[1] / SensorManager.GRAVITY_EARTH
val gZ = event.values[2] / SensorManager.GRAVITY_EARTH
val gForce = sqrt((gX * gX + gY * gY + gZ * gZ).toDouble())
if (gForce > 2.25) {
Logd(TAG, "Detected shake $gForce")
@ -1562,13 +1560,12 @@ class PlaybackService : MediaLibraryService() {
private const val SCHED_EX_POOL_SIZE = 2
private const val SLEEP_TIMER_UPDATE_INTERVAL = 10000L // in millisoconds
const val POSITION_SAVER_WAITING_INTERVAL: Int = 5000 // in millisoconds
// const val WIDGET_UPDATER_NOTIFICATION_INTERVAL: Int = 5000 // in millisoconds
const val MIN_POSITION_SAVER_INTERVAL: Int = 5000 // in millisoconds
const val NOTIFICATION_THRESHOLD: Long = 10000 // in millisoconds
fun positionUpdateInterval(duration: Int): Long {
return if (prefAdaptiveProgressUpdate) max(POSITION_SAVER_WAITING_INTERVAL, duration/50).toLong()
else POSITION_SAVER_WAITING_INTERVAL.toLong()
return if (prefAdaptiveProgressUpdate) max(MIN_POSITION_SAVER_INTERVAL, duration/50).toLong()
else MIN_POSITION_SAVER_INTERVAL.toLong()
}
}
}
@ -1622,23 +1619,17 @@ class PlaybackService : MediaLibraryService() {
/**
* @return `true` if notifications are persistent, `false` otherwise
*/
val isPersistNotify: Boolean
get() = appPrefs.getBoolean(UserPreferences.Prefs.prefPersistNotify.name, true)
val isPersistNotify: Boolean by lazy { appPrefs.getBoolean(UserPreferences.Prefs.prefPersistNotify.name, true) }
val isPauseOnHeadsetDisconnect: Boolean
get() = appPrefs.getBoolean(UserPreferences.Prefs.prefPauseOnHeadsetDisconnect.name, true)
val isPauseOnHeadsetDisconnect: Boolean by lazy { appPrefs.getBoolean(UserPreferences.Prefs.prefPauseOnHeadsetDisconnect.name, true) }
val isUnpauseOnHeadsetReconnect: Boolean
get() = appPrefs.getBoolean(UserPreferences.Prefs.prefUnpauseOnHeadsetReconnect.name, true)
val isUnpauseOnHeadsetReconnect: Boolean by lazy { appPrefs.getBoolean(UserPreferences.Prefs.prefUnpauseOnHeadsetReconnect.name, true) }
val isUnpauseOnBluetoothReconnect: Boolean
get() = appPrefs.getBoolean(UserPreferences.Prefs.prefUnpauseOnBluetoothReconnect.name, false)
val isUnpauseOnBluetoothReconnect: Boolean by lazy { appPrefs.getBoolean(UserPreferences.Prefs.prefUnpauseOnBluetoothReconnect.name, false) }
val hardwareForwardButton: Int
get() = appPrefs.getString(UserPreferences.Prefs.prefHardwareForwardButton.name, KeyEvent.KEYCODE_MEDIA_FAST_FORWARD.toString())!!.toInt()
val hardwareForwardButton: Int by lazy { appPrefs.getString(UserPreferences.Prefs.prefHardwareForwardButton.name, KeyEvent.KEYCODE_MEDIA_FAST_FORWARD.toString())!!.toInt() }
val hardwarePreviousButton: Int
get() = appPrefs.getString(UserPreferences.Prefs.prefHardwarePreviousButton.name, KeyEvent.KEYCODE_MEDIA_REWIND.toString())!!.toInt()
val hardwarePreviousButton: Int by lazy { appPrefs.getString(UserPreferences.Prefs.prefHardwarePreviousButton.name, KeyEvent.KEYCODE_MEDIA_REWIND.toString())!!.toInt() }
/**
* Set to true to enable Continuous Playback

View File

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

View File

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

View File

@ -96,21 +96,22 @@ object Episodes {
}
// @JvmStatic is needed because some Runnable blocks call this
@OptIn(UnstableApi::class) @JvmStatic
@JvmStatic
fun deleteEpisodeMedia(context: Context, episode: Episode) : Job {
Logd(TAG, "deleteMediaOfEpisode called ${episode.title}")
return runOnIOScope {
if (episode.media == null) return@runOnIOScope
val episode_ = deleteMediaSync(context, episode)
if (shouldDeleteRemoveFromQueue()) removeFromQueueSync(null, episode_)
if (prefDeleteRemovesFromQueue) removeFromQueueSync(null, episode_)
}
}
fun shouldDeleteRemoveFromQueue(): Boolean {
return appPrefs.getBoolean(Prefs.prefDeleteRemovesFromQueue.name, false)
}
val prefDeleteRemovesFromQueue: Boolean
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 {
Logd(TAG, "deleteMediaSync called")
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.
* 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 {
Logd(TAG, "setRating called $rating")
return runOnIOScope {
@ -254,13 +231,12 @@ object Episodes {
Logd(TAG, "setPlayState called")
return runOnIOScope {
for (episode in episodes) {
setPlayStateSync(played, resetMediaPosition, episode)
setPlayStateSync(played, episode, resetMediaPosition)
}
}
}
@OptIn(UnstableApi::class)
suspend fun setPlayStateSync(played: Int, resetMediaPosition: Boolean, episode: Episode) : Episode {
suspend fun setPlayStateSync(played: Int, episode: Episode, resetMediaPosition: Boolean, removeFromQueue: Boolean = true) : Episode {
Logd(TAG, "setPlayStateSync called played: $played resetMediaPosition: $resetMediaPosition ${episode.title}")
var episode_ = 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)
}
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}")
EventFlow.postEvent(FlowEvent.EpisodePlayedEvent(result))
return result
}
private fun shouldMarkedPlayedRemoveFromQueues(): Boolean {
return appPrefs.getBoolean(Prefs.prefRemoveFromQueueMarkedPlayed.name, true)
}
val prefRemoveFromQueueMarkedPlayed: Boolean
get() = appPrefs.getBoolean(Prefs.prefRemoveFromQueueMarkedPlayed.name, true)
// fun shouldMarkedPlayedRemoveFromQueues(): Boolean {
// return appPrefs.getBoolean(Prefs.prefRemoveFromQueueMarkedPlayed.name, true)
// }
fun episodeFromStreamInfoItem(item: StreamInfoItem): Episode {
val e = Episode()

View File

@ -435,7 +435,7 @@ object Feeds {
}
@JvmStatic
fun shouldAutoDeleteItem(feed: Feed): Boolean {
fun allowForAutoDelete(feed: Feed): Boolean {
if (!isAutoDelete) return false
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.appPrefs
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.runOnIOScope
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.utils.EpisodeUtil.indexOfItemWithId
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.FlowEvent
import ac.mdiq.podcini.util.Logd
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 kotlinx.coroutines.Job
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.
* 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.
*/
@UnstableApi @JvmStatic @Synchronized
@JvmStatic @Synchronized
fun addToQueue(vararg episodes: Episode) : Job {
Logd(TAG, "addToQueue( ... ) called")
return runOnIOScope {
@ -130,7 +124,6 @@ object Queues {
it.update()
}
for (event in events) EventFlow.postEvent(event)
setPlayState(PlayState.INQUEUE.code, false, *setInQueue.toTypedArray())
// if (performAutoDownload) autodownloadEpisodeMedia(context)
}
@ -199,16 +192,15 @@ object Queues {
}
}
/**
* Removes a Episode object from the queue.
* @param episodes FeedItems that should be removed.
*/
@OptIn(UnstableApi::class) @JvmStatic
fun removeFromQueue(vararg episodes: Episode) : Job {
return runOnIOScope { removeFromQueueSync(curQueue, *episodes) }
}
// /**
// * Removes a Episode object from the queue.
// * @param episodes FeedItems that should be removed.
// */
// @JvmStatic
// fun removeFromQueue(vararg episodes: Episode) : Job {
// return runOnIOScope { removeFromQueueSync(curQueue, *episodes) }
// }
@OptIn(UnstableApi::class)
fun removeFromAllQueuesSync(vararg episodes: Episode) {
Logd(TAG, "removeFromAllQueuesSync called ")
val queues = realm.query(PlayQueue::class).find()
@ -239,7 +231,7 @@ object Queues {
if (indexOfItemWithId(eList, episode.id) >= 0) {
Logd(TAG, "removing from queue: ${episode.id} ${episode.title}")
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))
}
}
@ -316,9 +308,9 @@ object Queues {
* 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())
*/
fun moveInQueue(from: Int, to: Int, broadcastUpdate: Boolean) : Job {
return runOnIOScope { moveInQueueSync(from, to, broadcastUpdate) }
}
// fun moveInQueue(from: Int, to: Int, broadcastUpdate: Boolean) : Job {
// return runOnIOScope { moveInQueueSync(from, to, broadcastUpdate) }
// }
/**
* 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.skipped.name)) stateQuerys.add(" playState == ${PlayState.SKIPPED.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 (stateQuerys.isNotEmpty()) {
val query = StringBuilder(" (" + stateQuerys[0])
@ -147,6 +149,8 @@ class EpisodeFilter(vararg properties_: String) : Serializable {
inProgress,
skipped,
played,
again,
forever,
ignored,
has_chapters,
no_chapters,
@ -197,6 +201,8 @@ class EpisodeFilter(vararg properties_: String) : Serializable {
ItemProperties(R.string.in_progress, States.inProgress.name),
ItemProperties(R.string.skipped, States.skipped.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),
),
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
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.Playable
object EpisodeUtil {
private val TAG: String = EpisodeUtil::class.simpleName ?: "Anonymous"
val smartMarkAsPlayedSecs: Int
get() = appPrefs.getString(UserPreferences.Prefs.prefSmartMarkAsPlayedSecs.name, "30")!!.toInt()
// val smartMarkAsPlayedSecs: Int
// get() = appPrefs.getString(UserPreferences.Prefs.prefSmartMarkAsPlayedSecs.name, "30")!!.toInt()
private val smartMarkAsPlayedPercent: Int = 95
@JvmStatic
fun indexOfItemWithId(episodes: List<Episode?>, id: Long): Int {
@ -36,6 +35,6 @@ object EpisodeUtil {
@JvmStatic
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 {
PlaybackService.clearCurTempSpeed()
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))
}
playVideoIfNeeded(context, media)
@ -425,7 +425,7 @@ class StreamActionButton(item: Episode) : EpisodeActionButton(item) {
if (media !is EpisodeMedia || !InTheatre.isCurMedia(media)) PlaybackService.clearCurTempSpeed()
PlaybackServiceStarter(context, media).shouldStreamThisTime(true).callEvenIfRunning(true).start()
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))
}
playVideoIfNeeded(context, media)
@ -591,7 +591,7 @@ class PlayLocalActionButton(item: Episode) : EpisodeActionButton(item) {
} else {
PlaybackService.clearCurTempSpeed()
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))
}
if (media.getMediaType() == MediaType.VIDEO) context.startActivity(getPlayerActivityIntent(context,

View File

@ -1,23 +1,19 @@
package ac.mdiq.podcini.ui.actions
//import ac.mdiq.podcini.ui.dialog.SwipeActionsDialog
import ac.mdiq.podcini.R
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.shouldDeleteRemoveFromQueue
import ac.mdiq.podcini.storage.database.Feeds.shouldAutoDeleteItem
import ac.mdiq.podcini.storage.database.Episodes.setPlayStateSync
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.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.upsertBlk
import ac.mdiq.podcini.storage.model.Episode
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.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.NO_ACTION
import ac.mdiq.podcini.ui.activity.MainActivity
@ -56,10 +52,8 @@ import androidx.lifecycle.LifecycleOwner
import androidx.media3.common.util.UnstableApi
import com.google.android.material.snackbar.Snackbar
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import java.util.*
import kotlin.math.ceil
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)
}
@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
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))
}
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)
}
@OptIn(UnstableApi::class)
override fun performAction(item: Episode, fragment: Fragment, filter: EpisodeFilter) {
val position: Int = curQueue.episodes.indexOf(item)
removeFromQueue(item)
override fun performAction(item_: Episode, fragment: Fragment, filter: EpisodeFilter) {
val position: Int = curQueue.episodes.indexOf(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)) {
(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)) {
@ -426,15 +436,15 @@ open class SwipeActions(private val fragment: Fragment, private val tag: String)
}
(fragment.view as? ViewGroup)?.addView(composeView)
}
private fun delayedExecution(item: Episode, fragment: Fragment, duration: Float) = runBlocking {
delay(ceil((duration * 1.05f).toDouble()).toLong())
val media: EpisodeMedia? = item.media
val shouldAutoDelete = if (item.feed == null) false else shouldAutoDeleteItem(item.feed!!)
if (media != null && EpisodeUtil.hasAlmostEnded(media) && shouldAutoDelete) {
// deleteMediaOfEpisode(fragment.requireContext(), item)
val item_ = deleteMediaSync(fragment.requireContext(), item)
if (shouldDeleteRemoveFromQueue()) removeFromQueueSync(null, item_) }
}
// private fun delayedExecution(item: Episode, fragment: Fragment, duration: Float) = runBlocking {
// delay(ceil((duration * 1.05f).toDouble()).toLong())
// val media: EpisodeMedia? = item.media
// val shouldAutoDelete = if (item.feed == null) false else shouldAutoDeleteItem(item.feed!!)
// if (media != null && EpisodeUtil.hasAlmostEnded(media) && shouldAutoDelete) {
//// deleteMediaOfEpisode(fragment.requireContext(), item)
// val item_ = deleteMediaSync(fragment.requireContext(), item)
// if (prefDeleteRemovesFromQueue) removeFromQueueSync(null, item_) }
// }
}
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.MediaPlayerBase.Companion.status
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.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.shouldDeleteRemoveFromQueue
import ac.mdiq.podcini.storage.database.Feeds.addToMiscSyndicate
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.addToQueueSync
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.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.upsertBlk
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.fragment.EpisodeInfoFragment
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.FlowEvent
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.material.icons.Icons
import androidx.compose.material.icons.filled.AddCircle
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material3.*
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
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)
.clickable {
for (item in selected) {
var item_ = runBlocking { setPlayStateSync(state.code, false, item) }
val media: EpisodeMedia? = item_.media
val shouldAutoDelete = if (item_.feed == null) false else shouldAutoDeleteItem(item_.feed!!)
if (media != null && hasAlmostEnded(media) && shouldAutoDelete) {
item_ = deleteMediaSync(context, item_)
if (shouldDeleteRemoveFromQueue()) removeFromQueueSync(null, item_)
}
var media: EpisodeMedia? = item.media
val hasAlmostEnded = if (media != null) hasAlmostEnded(media) else false
var item_ = runBlocking { setPlayStateSync(state.code, item, hasAlmostEnded, false) }
when (state) {
PlayState.UNPLAYED -> {
if (isProviderConnected && item_.feed?.isLocalFeed != true && item_.media != null) {
@ -276,6 +273,13 @@ fun PlayStateDialog(selected: List<Episode>, onDismissRequest: () -> Unit) {
}
}
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)) {
val media_: EpisodeMedia? = item_.media
// 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
selectMode = false
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) {
Icon(imageVector = ImageVector.vectorResource(id = R.drawable.ic_delete), "Delete media")
Text(stringResource(id = R.string.delete_episode_label)) } },
@ -527,9 +544,7 @@ fun EpisodeLazyColumn(activity: MainActivity, vms: MutableList<EpisodeVM>, feed:
selectMode = false
Logd(TAG, "ic_download: ${selected.size}")
for (episode in selected) {
if (episode.media != null && episode.feed != null && !episode.feed!!.isLocalFeed) DownloadServiceInterface
.get()
?.download(activity, episode)
if (episode.media != null && episode.feed != null && !episode.feed!!.isLocalFeed) DownloadServiceInterface.get()?.download(activity, episode)
}
}, verticalAlignment = Alignment.CenterVertically) {
Icon(imageVector = ImageVector.vectorResource(id = R.drawable.ic_download), "Download")
@ -551,7 +566,20 @@ fun EpisodeLazyColumn(activity: MainActivity, vms: MutableList<EpisodeVM>, feed:
isExpanded = false
selectMode = false
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) {
Icon(imageVector = ImageVector.vectorResource(id = R.drawable.ic_playlist_remove), "Remove from active queue")
Text(stringResource(id = R.string.remove_from_queue_label)) } },

View File

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

View File

@ -14,7 +14,7 @@ import ac.mdiq.podcini.preferences.UserPreferences
import ac.mdiq.podcini.preferences.UserPreferences.appPrefs
import ac.mdiq.podcini.storage.database.Queues.clearQueue
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.RealmDB.realm
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())
}
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) })
}
}

View File

@ -401,8 +401,8 @@
<string name="pref_smart_mark_as_played_title">علم بذكاء أنها انتهت</string>
<string name="pref_skip_keeps_episodes_sum">ابقي الحلقات التي يتم تخطيها</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_sum">تحكم سماعة الأذن, وقت التقدم, لائحة الاستماع</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_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_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_sum">Controls d\'auriculars, Intervals d\'avançada, Cua</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_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_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_sum">Ovládání tlačítky sluchátek, přeskakování, fronta</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_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_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_sum">Hovedtelefonstyring, overspringsintervaller, kø</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_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_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_sum">Kopfhörersteuerung, Sprungintervall, Warteschlange</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_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_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_sum">Control de auriculares, Saltar intervalos, Cola</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_skip_keeps_episodes_sum">Saioak gorde jaustean</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_sum">Aurikularren kontrolak, saltatu tarteak, ilara</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_skip_keeps_episodes_sum">نگه داشتن قسمت‌ها هنگام پریدن از رویشان</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_sum">کنترل هدفون ، رد کردن فواصل ، صف</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_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_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_sum">Kuulokkeiden ohjaimet, ohitusaikavälit, jono</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_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_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_sum">Contrôles du casque, durée des sauts, liste de lecture</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_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_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_sum">Control de auriculares, Intervalos de salto, Cola</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_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_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_sum">Kontrol headphone, Jangka waktu lewati, Antrean</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_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_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_sum">Controllo cuffie, salto intervalli, coda</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_skip_keeps_episodes_sum">להשאיר פרקים למרות שדילגת עליהם</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_sum">שליטה דרך האוזניות, מרחקי דילוג, תור</string>
<string name="downloads_pref">הורדות</string>

View File

@ -336,8 +336,8 @@
<string name="pref_smart_mark_as_played_title">똑똑하게 재생한 것으로 표시</string>
<string name="pref_skip_keeps_episodes_sum">에피소드를 넘겼을 경우에도 유지</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_sum">헤드폰 조작, 구간 넘기기, 대기열</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_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_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_sum">Hodetelefon-kontroller, spole-intervaller, kø</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_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_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_sum">Onderbreken, afspelen, wachtrij</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_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_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_sum">Kontrola za pomocą słuchawek, Pomijanie, Kolejka</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_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_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_sum">Controles de fone de ouvido, intervalos para saltar, Fila</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_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_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_sum">Controlo com auscultador, intervalos e fila</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_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_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_sum">Controlul căștilor, Sari peste un interval de timp, Coadă</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_skip_keeps_episodes_sum">Сохранять выпуски, которые были пропущены</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_sum">Кнопки гарнитуры, шаг перемотки, очередь</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_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_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_sum">Ovládanie tlačidlami slúchadiel, preskakovanie, poradie</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_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_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_sum">Hörlurskontroller, Överhoppningsintervaller, Kö</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_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_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_sum">Kulaklık kontrolleri, Atlama aralığı ve Kuyruk ayarları</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_skip_keeps_episodes_sum">Зберігати пропущені епізоди при програванні </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_sum">Керування навушниками, Пропуск інтервалів, Черга</string>
<string name="downloads_pref">Завантаження</string>

View File

@ -359,8 +359,8 @@
<string name="pref_smart_mark_as_played_title">智能标记为已播放</string>
<string name="pref_skip_keeps_episodes_sum">当剧集被跳过时保留它们</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_sum">耳机控制、跳过间隔、排队</string>
<string name="downloads_pref">下载</string>

View File

@ -323,6 +323,8 @@
<string name="in_progress">In progress</string>
<string name="skipped">Skipped</string>
<string name="played">Played</string>
<string name="again">Again</string>
<string name="forever">Forever</string>
<string name="ignored">Ignored</string>
<string name="remove_from_favorite_label">Remove from favorites</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_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_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_sum">Headphone controls, Skip intervals, Queue</string>
<string name="downloads_pref">Downloads</string>

View File

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

View File

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