6.1.1 commit

This commit is contained in:
Xilin Jia 2024-07-19 22:41:20 +01:00
parent 76e350ff72
commit d47bdab971
14 changed files with 86 additions and 34 deletions

View File

@ -126,8 +126,8 @@ android {
buildConfig true buildConfig true
} }
defaultConfig { defaultConfig {
versionCode 3020214 versionCode 3020215
versionName "6.1.0" versionName "6.1.1"
applicationId "ac.mdiq.podcini.R" applicationId "ac.mdiq.podcini.R"
def commit = "" def commit = ""

View File

@ -95,7 +95,8 @@ class OpmlBackupAgent : BackupAgentHelper() {
IOUtils.closeQuietly(writer) IOUtils.closeQuietly(writer)
} }
} }
@OptIn(UnstableApi::class) override fun restoreEntity(data: BackupDataInputStream) { @OptIn(UnstableApi::class)
override fun restoreEntity(data: BackupDataInputStream) {
Logd(TAG, "Backup restore") Logd(TAG, "Backup restore")
if (OPML_ENTITY_KEY != data.key) { if (OPML_ENTITY_KEY != data.key) {
Logd(TAG, "Unknown entity key: " + data.key) Logd(TAG, "Unknown entity key: " + data.key)
@ -103,6 +104,7 @@ class OpmlBackupAgent : BackupAgentHelper() {
} }
var digester: MessageDigest? = null var digester: MessageDigest? = null
var reader: Reader var reader: Reader
var linesRead = 0
try { try {
digester = MessageDigest.getInstance("MD5") digester = MessageDigest.getInstance("MD5")
reader = InputStreamReader(DigestInputStream(data, digester), Charset.forName("UTF-8")) reader = InputStreamReader(DigestInputStream(data, digester), Charset.forName("UTF-8"))
@ -112,25 +114,30 @@ class OpmlBackupAgent : BackupAgentHelper() {
try { try {
mChecksum = digester?.digest() ?: byteArrayOf() mChecksum = digester?.digest() ?: byteArrayOf()
BufferedReader(reader).use { bufferedReader -> BufferedReader(reader).use { bufferedReader ->
val tempFile = File.createTempFile("opml_restored", ".tmp", mContext.filesDir) val tempFile = File(mContext.filesDir, "opml_restored.txt")
// val tempFile = File.createTempFile("opml_restored", ".tmp", mContext.filesDir)
FileWriter(tempFile).use { fileWriter -> FileWriter(tempFile).use { fileWriter ->
while (true) { while (true) {
val line = bufferedReader.readLine() ?: break val line = bufferedReader.readLine() ?: break
Logd(TAG, "restoreEntity: $linesRead $line")
linesRead++
fileWriter.write(line) fileWriter.write(line)
fileWriter.write(System.lineSeparator()) // Write a newline character fileWriter.write(System.lineSeparator()) // Write a newline character
} }
} }
} }
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(mContext)
with(sharedPreferences.edit()) {
putBoolean(UserPreferences.Prefs.prefOPMLRestore.name, true)
apply()
}
} catch (e: XmlPullParserException) { } catch (e: XmlPullParserException) {
Log.e(TAG, "Error while parsing the OPML file", e) Log.e(TAG, "Error while parsing the OPML file", e)
} catch (e: IOException) { } catch (e: IOException) {
Log.e(TAG, "Failed to restore OPML backup", e) Log.e(TAG, "Failed to restore OPML backup", e)
} finally { } finally {
if (linesRead > 0) {
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(mContext)
with(sharedPreferences.edit()) {
putBoolean(UserPreferences.Prefs.prefOPMLRestore.name, true)
apply()
}
}
IOUtils.closeQuietly(reader) IOUtils.closeQuietly(reader)
} }
} }
@ -166,11 +173,12 @@ class OpmlBackupAgent : BackupAgentHelper() {
private const val OPML_BACKUP_KEY = "opml" private const val OPML_BACKUP_KEY = "opml"
val isOPMLRestared: Boolean val isOPMLRestared: Boolean
get() = appPrefs.getBoolean(UserPreferences.Prefs.prefOPMLRestore.name, true) get() = appPrefs.getBoolean(UserPreferences.Prefs.prefOPMLRestore.name, false)
fun performRestore(context: Context) { fun performRestore(context: Context) {
Logd(TAG, "performRestore") Logd(TAG, "performRestore")
val tempFile = File.createTempFile("opml_restored", ".tmp", context.filesDir) val tempFile = File(context.filesDir, "opml_restored.txt")
// val tempFile = File.createTempFile("opml_restored", ".tmp", context.filesDir)
if (tempFile.exists()) { if (tempFile.exists()) {
val reader = FileReader(tempFile) val reader = FileReader(tempFile)
val opmlElements = OpmlReader().readDocument(reader) val opmlElements = OpmlReader().readDocument(reader)
@ -179,6 +187,7 @@ class OpmlBackupAgent : BackupAgentHelper() {
feed.episodes.clear() feed.episodes.clear()
updateFeed(context, feed, false) updateFeed(context, feed, false)
} }
Toast.makeText(context, "${opmlElements.size} feeds were restored", Toast.LENGTH_SHORT).show()
runOnce(context) runOnce(context)
} else { } else {
Toast.makeText(context, "No backup data found", Toast.LENGTH_SHORT).show() Toast.makeText(context, "No backup data found", Toast.LENGTH_SHORT).show()

View File

@ -12,6 +12,7 @@ import ac.mdiq.podcini.storage.database.Queues.getInQueueEpisodeIds
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.EpisodeSortOrder import ac.mdiq.podcini.storage.model.EpisodeSortOrder
import ac.mdiq.podcini.util.Logd
import android.content.Context import android.content.Context
import android.util.Log import android.util.Log
import androidx.annotation.OptIn import androidx.annotation.OptIn
@ -68,7 +69,8 @@ object AutoCleanups {
override fun getReclaimableItems(): Int { override fun getReclaimableItems(): Int {
return candidates.size return candidates.size
} }
@OptIn(UnstableApi::class) public override fun performCleanup(context: Context, numToRemove: Int): Int { @OptIn(UnstableApi::class)
public override fun performCleanup(context: Context, numToRemove: Int): Int {
var candidates = candidates var candidates = candidates
// in the absence of better data, we'll sort by item publication date // in the absence of better data, we'll sort by item publication date
candidates = candidates.sortedWith { lhs: Episode, rhs: Episode -> candidates = candidates.sortedWith { lhs: Episode, rhs: Episode ->
@ -250,8 +252,12 @@ object AutoCleanups {
* @return The number of episodes that were deleted. * @return The number of episodes that were deleted.
*/ */
protected abstract fun performCleanup(context: Context, numToRemove: Int): Int protected abstract fun performCleanup(context: Context, numToRemove: Int): Int
// only used in tests
fun performCleanup(context: Context): Int { fun performCleanup(context: Context): Int {
return performCleanup(context, getDefaultCleanupParameter()) val numToRemove = getDefaultCleanupParameter()
if (numToRemove <= 0) return 0
return performCleanup(context, numToRemove)
} }
/** /**
* Returns a parameter for performCleanup. The implementation of this interface should decide how much * Returns a parameter for performCleanup. The implementation of this interface should decide how much
@ -266,7 +272,10 @@ object AutoCleanups {
* @return The number of epiosdes that were deleted * @return The number of epiosdes that were deleted
*/ */
fun makeRoomForEpisodes(context: Context, amountOfRoomNeeded: Int): Int { fun makeRoomForEpisodes(context: Context, amountOfRoomNeeded: Int): Int {
return performCleanup(context, getNumEpisodesToCleanup(amountOfRoomNeeded)) val numToRemove = getNumEpisodesToCleanup(amountOfRoomNeeded)
Logd("EpisodeCleanupAlgorithm", "makeRoomForEpisodes: $numToRemove")
if (numToRemove <= 0) return 0
return performCleanup(context, numToRemove)
} }
/** /**
* @return the number of episodes/items that *could* be cleaned up, if needed * @return the number of episodes/items that *could* be cleaned up, if needed

View File

@ -10,20 +10,17 @@ import ac.mdiq.podcini.preferences.UserPreferences.isEnableAutodownload
import ac.mdiq.podcini.preferences.UserPreferences.isEnableAutodownloadOnBattery import ac.mdiq.podcini.preferences.UserPreferences.isEnableAutodownloadOnBattery
import ac.mdiq.podcini.storage.database.Episodes.getEpisodes import ac.mdiq.podcini.storage.database.Episodes.getEpisodes
import ac.mdiq.podcini.storage.database.Episodes.getEpisodesCount import ac.mdiq.podcini.storage.database.Episodes.getEpisodesCount
import ac.mdiq.podcini.storage.database.Episodes.setPlayState
import ac.mdiq.podcini.storage.database.Feeds.getFeedList import ac.mdiq.podcini.storage.database.Feeds.getFeedList
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.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.utils.EpisodesPermutors.getPermutor
import ac.mdiq.podcini.util.Logd import ac.mdiq.podcini.util.Logd
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.IntentFilter import android.content.IntentFilter
import android.os.BatteryManager import android.os.BatteryManager
import androidx.media3.common.util.UnstableApi import androidx.media3.common.util.UnstableApi
import io.realm.kotlin.Realm
import io.realm.kotlin.UpdatePolicy import io.realm.kotlin.UpdatePolicy
import java.util.concurrent.ExecutorService import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors import java.util.concurrent.Executors
@ -149,27 +146,28 @@ object AutoDownloads {
if (networkShouldAutoDl && powerShouldAutoDl) { if (networkShouldAutoDl && powerShouldAutoDl) {
Logd(Companion.TAG, "autoDownloadEpisodeMedia Performing auto-dl of undownloaded episodes") Logd(Companion.TAG, "autoDownloadEpisodeMedia Performing auto-dl of undownloaded episodes")
val candidates: MutableSet<Episode> = mutableSetOf() val candidates: MutableSet<Episode> = mutableSetOf()
val queueItems = curQueue.episodes.filter { it.media?.downloaded != true } val queueItems = realm.query(Episode::class).query("id IN $0 AND media.downloaded == false", curQueue.episodeIds).find()
Logd(TAG, "autoDownloadEpisodeMedia add from queue: ${queueItems.size}")
if (queueItems.isNotEmpty()) candidates.addAll(queueItems) if (queueItems.isNotEmpty()) candidates.addAll(queueItems)
val feeds = feeds ?: getFeedList() val feeds = feeds ?: getFeedList()
feeds.forEach { f -> feeds.forEach { f ->
if (f.preferences?.autoDownload == true && !f.isLocalFeed) { if (f.preferences?.autoDownload == true && !f.isLocalFeed) {
var episodes = mutableListOf<Episode>() var episodes = mutableListOf<Episode>()
val downloadedCount = getEpisodesCount(EpisodeFilter(EpisodeFilter.States.downloaded.name), f.id) val downloadedCount = getEpisodesCount(EpisodeFilter(EpisodeFilter.States.downloaded.name), f.id)
val toDLCount = (f.preferences?.autoDLMaxEpisodes?:0) - downloadedCount val allowedDLCount = (f.preferences?.autoDLMaxEpisodes?:0) - downloadedCount
if (toDLCount > 0) { if (allowedDLCount > 0) {
var queryString = "feedId == ${f.id} AND isAutoDownloadEnabled == true AND media != nil AND media.downloaded == false" var queryString = "feedId == ${f.id} AND isAutoDownloadEnabled == true AND media != nil AND media.downloaded == false"
when (f.preferences?.autoDLPolicy) { when (f.preferences?.autoDLPolicy) {
FeedPreferences.AutoDLPolicy.ONLY_NEW -> { FeedPreferences.AutoDLPolicy.ONLY_NEW -> {
queryString += " AND playState == -1 SORT(pubDate DESC) LIMIT(${3*toDLCount})" queryString += " AND playState == -1 SORT(pubDate DESC) LIMIT(${3*allowedDLCount})"
episodes = realm.query(Episode::class).query(queryString).find().toMutableList() episodes = realm.query(Episode::class).query(queryString).find().toMutableList()
} }
FeedPreferences.AutoDLPolicy.NEWER -> { FeedPreferences.AutoDLPolicy.NEWER -> {
queryString += " AND playState != 1 SORT(pubDate DESC) LIMIT(${3*toDLCount})" queryString += " AND playState != 1 SORT(pubDate DESC) LIMIT(${3*allowedDLCount})"
episodes = realm.query(Episode::class).query(queryString).find().toMutableList() episodes = realm.query(Episode::class).query(queryString).find().toMutableList()
} }
FeedPreferences.AutoDLPolicy.OLDER -> { FeedPreferences.AutoDLPolicy.OLDER -> {
queryString += " AND playState != 1 SORT(pubDate ASC) LIMIT(${3*toDLCount})" queryString += " AND playState != 1 SORT(pubDate ASC) LIMIT(${3*allowedDLCount})"
episodes = realm.query(Episode::class).query(queryString).find().toMutableList() episodes = realm.query(Episode::class).query(queryString).find().toMutableList()
} }
else -> {} else -> {}
@ -177,9 +175,11 @@ object AutoDownloads {
if (episodes.isNotEmpty()) { if (episodes.isNotEmpty()) {
var count = 0 var count = 0
for (e in episodes) { for (e in episodes) {
if (isCurMedia(e.media)) continue
if (f.preferences?.autoDownloadFilter?.shouldAutoDownload(e) == true) { if (f.preferences?.autoDownloadFilter?.shouldAutoDownload(e) == true) {
Logd(TAG, "autoDownloadEpisodeMedia add to cadidates: ${e.title} ${e.isDownloaded}")
candidates.add(e) candidates.add(e)
if (++count >= toDLCount) break if (++count >= allowedDLCount) break
} else upsertBlk(e) { it.setPlayed(true)} } else upsertBlk(e) { it.setPlayed(true)}
} }
} }

View File

@ -110,6 +110,7 @@ class AddFeedFragment : Fragment() {
.setPositiveButton("Yes") { dialog, _ -> .setPositiveButton("Yes") { dialog, _ ->
performRestore(requireContext()) performRestore(requireContext())
dialog.dismiss() dialog.dismiss()
parentFragmentManager.popBackStack()
} }
.setNegativeButton("No") { dialog, _ -> .setNegativeButton("No") { dialog, _ ->
dialog.dismiss() dialog.dismiss()

View File

@ -324,6 +324,17 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
(activity as MainActivity).setPlayerVisible(true) (activity as MainActivity).setPlayerVisible(true)
} }
private fun onPlaybackPositionEvent(event: FlowEvent.PlaybackPositionEvent) {
// Logd(TAG, "onPlayEvent ${event.episode.title}")
val media = event.media
if (currentMedia?.getIdentifier() == null || media?.getIdentifier() != currentMedia?.getIdentifier()) {
currentMedia = media
playerDetailsFragment?.setItem(curEpisode!!)
}
playerUI?.onPositionUpdate(event)
if (!isCollapsed) playerDetailsFragment?.onPlaybackPositionEvent(event)
}
private var eventSink: Job? = null private var eventSink: Job? = null
private fun cancelFlowEvents() { private fun cancelFlowEvents() {
Logd(TAG, "cancelFlowEvents") Logd(TAG, "cancelFlowEvents")
@ -346,10 +357,7 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
is FlowEvent.FavoritesEvent -> onFavoriteEvent(event) is FlowEvent.FavoritesEvent -> onFavoriteEvent(event)
is FlowEvent.PlayerErrorEvent -> MediaPlayerErrorDialog.show(activity as Activity, event) is FlowEvent.PlayerErrorEvent -> MediaPlayerErrorDialog.show(activity as Activity, event)
is FlowEvent.SleepTimerUpdatedEvent -> if (event.isCancelled || event.wasJustEnabled()) loadMediaInfo(false) is FlowEvent.SleepTimerUpdatedEvent -> if (event.isCancelled || event.wasJustEnabled()) loadMediaInfo(false)
is FlowEvent.PlaybackPositionEvent -> { is FlowEvent.PlaybackPositionEvent -> onPlaybackPositionEvent(event)
playerUI?.onPositionUpdate(event)
if (!isCollapsed) playerDetailsFragment?.onPlaybackPositionEvent(event)
}
is FlowEvent.SpeedChangedEvent -> playerUI?.updatePlaybackSpeedButton(event) is FlowEvent.SpeedChangedEvent -> playerUI?.updatePlaybackSpeedButton(event)
else -> {} else -> {}
} }

View File

@ -5,6 +5,7 @@ import ac.mdiq.podcini.databinding.BaseEpisodesListFragmentBinding
import ac.mdiq.podcini.databinding.MultiSelectSpeedDialBinding import ac.mdiq.podcini.databinding.MultiSelectSpeedDialBinding
import ac.mdiq.podcini.net.feed.FeedUpdateManager import ac.mdiq.podcini.net.feed.FeedUpdateManager
import ac.mdiq.podcini.playback.base.InTheatre.isCurMedia import ac.mdiq.podcini.playback.base.InTheatre.isCurMedia
import ac.mdiq.podcini.storage.database.RealmDB.unmanaged
import ac.mdiq.podcini.storage.model.Episode import ac.mdiq.podcini.storage.model.Episode
import ac.mdiq.podcini.storage.model.EpisodeMedia import ac.mdiq.podcini.storage.model.EpisodeMedia
import ac.mdiq.podcini.storage.model.EpisodeFilter import ac.mdiq.podcini.storage.model.EpisodeFilter
@ -343,7 +344,8 @@ import kotlinx.coroutines.flow.collectLatest
curIndex else EpisodeUtil.indexOfItemWithId(episodes, item.id) curIndex else EpisodeUtil.indexOfItemWithId(episodes, item.id)
if (pos >= 0) { if (pos >= 0) {
episodes[pos] = item episodes[pos] = unmanaged(episodes[pos])
episodes[pos].media?.position = event.media.position
curIndex = pos curIndex = pos
adapter.notifyItemChanged(pos, Bundle().apply { putString("PositionUpdate", "PlaybackPositionEvent") }) adapter.notifyItemChanged(pos, Bundle().apply { putString("PositionUpdate", "PlaybackPositionEvent") })
} }

View File

@ -10,6 +10,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.Episodes.getEpisodes import ac.mdiq.podcini.storage.database.Episodes.getEpisodes
import ac.mdiq.podcini.storage.database.RealmDB.realm import ac.mdiq.podcini.storage.database.RealmDB.realm
import ac.mdiq.podcini.storage.database.RealmDB.unmanaged
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.EpisodeMedia
@ -310,7 +311,8 @@ import java.util.*
curIndex else EpisodeUtil.indexOfItemWithId(episodes, item.id) curIndex else EpisodeUtil.indexOfItemWithId(episodes, item.id)
if (pos >= 0) { if (pos >= 0) {
episodes[pos] = item episodes[pos] = unmanaged(episodes[pos])
episodes[pos].media?.position = event.media.position
curIndex = pos curIndex = pos
adapter.notifyItemChanged(pos, Bundle().apply { putString("PositionUpdate", "PlaybackPositionEvent") }) adapter.notifyItemChanged(pos, Bundle().apply { putString("PositionUpdate", "PlaybackPositionEvent") })
} }

View File

@ -421,7 +421,8 @@ import java.util.concurrent.Semaphore
curIndex else EpisodeUtil.indexOfItemWithId(episodes, item.id) curIndex else EpisodeUtil.indexOfItemWithId(episodes, item.id)
if (pos >= 0) { if (pos >= 0) {
episodes[pos] = item episodes[pos] = unmanaged(episodes[pos])
episodes[pos].media?.position = event.media.position
curIndex = pos curIndex = pos
adapter.notifyItemChanged(pos, Bundle().apply { putString("PositionUpdate", "PlaybackPositionEvent") }) adapter.notifyItemChanged(pos, Bundle().apply { putString("PositionUpdate", "PlaybackPositionEvent") })
} }

View File

@ -17,6 +17,7 @@ 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
import ac.mdiq.podcini.storage.database.RealmDB.unmanaged
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
import ac.mdiq.podcini.storage.model.EpisodeFilter import ac.mdiq.podcini.storage.model.EpisodeFilter
@ -337,7 +338,8 @@ import java.util.*
curIndex else EpisodeUtil.indexOfItemWithId(queueItems, item.id) curIndex else EpisodeUtil.indexOfItemWithId(queueItems, item.id)
if (pos >= 0) { if (pos >= 0) {
queueItems[pos] = item queueItems[pos] = unmanaged(queueItems[pos])
queueItems[pos].media?.position = event.media.position
curIndex = pos curIndex = pos
adapter?.notifyItemChanged(pos, Bundle().apply { putString("PositionUpdate", "PlaybackPositionEvent") }) adapter?.notifyItemChanged(pos, Bundle().apply { putString("PositionUpdate", "PlaybackPositionEvent") })
} }

View File

@ -8,6 +8,7 @@ import ac.mdiq.podcini.databinding.SearchFragmentBinding
import ac.mdiq.podcini.net.feed.discovery.CombinedSearcher import ac.mdiq.podcini.net.feed.discovery.CombinedSearcher
import ac.mdiq.podcini.playback.base.InTheatre.isCurMedia import ac.mdiq.podcini.playback.base.InTheatre.isCurMedia
import ac.mdiq.podcini.storage.database.RealmDB.realm import ac.mdiq.podcini.storage.database.RealmDB.realm
import ac.mdiq.podcini.storage.database.RealmDB.unmanaged
import ac.mdiq.podcini.storage.model.Episode import ac.mdiq.podcini.storage.model.Episode
import ac.mdiq.podcini.storage.model.EpisodeMedia import ac.mdiq.podcini.storage.model.EpisodeMedia
import ac.mdiq.podcini.storage.model.Feed import ac.mdiq.podcini.storage.model.Feed
@ -300,7 +301,8 @@ import java.lang.ref.WeakReference
curIndex else EpisodeUtil.indexOfItemWithId(results, item.id) curIndex else EpisodeUtil.indexOfItemWithId(results, item.id)
if (pos >= 0) { if (pos >= 0) {
results[pos] = item results[pos] = unmanaged(results[pos])
results[pos].media?.position = event.media.position
curIndex = pos curIndex = pos
adapter.notifyItemChanged(pos, Bundle().apply { putString("PositionUpdate", "PlaybackPositionEvent") }) adapter.notifyItemChanged(pos, Bundle().apply { putString("PositionUpdate", "PlaybackPositionEvent") })
} }

View File

@ -1,3 +1,11 @@
# 6.1.1
* fixed player UI not updating on change of episode
* fixed the mal-function of restoring previously backed-up OPML
* reduced reactions to PlaybackPositionEvent
* tuned AutoCleanup a bit
* tuned and fixed some some issues in audo-downloaded
# 6.1.0 # 6.1.0
* in FeedEpisode view fixed filtering after an episode's play state is changed * in FeedEpisode view fixed filtering after an episode's play state is changed

View File

@ -1,5 +1,5 @@
Version 6.0.14 brings several changes: Version 6.1.0 brings several changes:
* in FeedEpisode view fixed filtering after an episode's play state is changed * in FeedEpisode view fixed filtering after an episode's play state is changed
* fixed refreshing a feed causes duplicate episodes in FeedEpisodes view * fixed refreshing a feed causes duplicate episodes in FeedEpisodes view

View File

@ -0,0 +1,8 @@
Version 6.1.1 brings several changes:
* fixed player UI not updating on change of episode
* fixed the mal-function of restoring previously backed-up OPML
* reduced reactions to PlaybackPositionEvent
* tuned AutoCleanup a bit
* tuned and fixed some some issues in audo-downloaded