6.3.4 commit
This commit is contained in:
parent
e5188bc998
commit
5fe3f049d2
11
README.md
11
README.md
|
@ -22,7 +22,7 @@ Compared to AntennaPod this project:
|
|||
3. Iron-age celebrity SQLite is replaced with modern object-base Realm DB (Podcini.R),
|
||||
4. Outfits with Viewbinding, Coil replacing Glide, coroutines replacing RxJava and threads, SharedFlow replacing EventBus, and jetifier removed,
|
||||
5. Boasts new UI's including streamlined drawer, subscriptions view and player controller,
|
||||
6. Supports multiple and circular play queues associable to any podcast
|
||||
6. Supports multiple, virtual and circular play queues associable to any podcast
|
||||
7. Auto-download is governed by policy and limit settings of individual feed
|
||||
8. Accepts podcast as well as plain RSS and YouTube feeds,
|
||||
9. Offers Readability and Text-to-Speech for RSS contents,
|
||||
|
@ -59,13 +59,20 @@ While podcast subscriptions' OPML files (from AntennaPod or any other sources) c
|
|||
* easy switches on video player to other video mode or audio only
|
||||
* default video player mode setting in preferences
|
||||
* when video mode is set to audio only, click on image on audio player on a video episode brings up the normal player detailed view
|
||||
* "Prefer streaming over download" is now on setting of individual feed
|
||||
* Multiple queues can be used: 5 queues are provided by default, user can rename or add up to 10 queues
|
||||
* on app startup, the most recently updated queue is set to curQueue
|
||||
* any episodes can be easily added/moved to the active or any designated queues
|
||||
* any queue can be associated with any feed for customized playing experience
|
||||
* Every queue is circular: if the final item in queue finished, the first item in queue (if exists) will get played
|
||||
* Every queue has a bin containing past episodes removed from the queue
|
||||
* Episode played from a list other than the queue is now a one-off play, unless the episode is on the active queue, in which case, the next episode in the queue will be played
|
||||
* Feed associated queue can be set to None, in which case:
|
||||
* episodes in the feed are not automatically added to any queue, but are used as a natural queue for getting the next episode to play
|
||||
* the next episode is determined in such a way:
|
||||
* if the currently playing episode had been (manually) added to the active queue, then it's the next in queue
|
||||
* else if "prefer streaming" is set, it's the next unplayed episode in the feed episodes list based on the current sort order
|
||||
* else it's the next downloaded unplayed episode
|
||||
* Otherwise, episode played from a list other than the queue is now a one-off play, unless the episode is on the active queue, in which case, the next episode in the queue will be played
|
||||
|
||||
|
||||
### Podcast/Episode list
|
||||
|
|
|
@ -31,8 +31,8 @@ android {
|
|||
testApplicationId "ac.mdiq.podcini.tests"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
versionCode 3020227
|
||||
versionName "6.3.3"
|
||||
versionCode 3020228
|
||||
versionName "6.3.4"
|
||||
|
||||
applicationId "ac.mdiq.podcini.R"
|
||||
def commit = ""
|
||||
|
|
|
@ -105,7 +105,7 @@ class MediaPlayerBaseTest {
|
|||
VolumeAdaptionSetting.OFF, null, null)
|
||||
f.preferences = prefs
|
||||
f.episodes.clear()
|
||||
val i = Episode(0, "t", "i", "l", Date(), Episode.UNPLAYED, f)
|
||||
val i = Episode(0, "t", "i", "l", Date(), Episode.PlayState.UNPLAYED.code, f)
|
||||
f.episodes.add(i)
|
||||
val media = EpisodeMedia(0, i, 0, 0, 0, "audio/wav", fileUrl, downloadUrl, fileUrl != null, null, 0, 0)
|
||||
i.setMedia(media)
|
||||
|
|
|
@ -65,7 +65,7 @@ class TaskManagerTest {
|
|||
val f = Feed(0, null, "title", "link", "d", null, null, null, null, "id", null, "null", "url")
|
||||
f.episodes.clear()
|
||||
for (i in 0 until NUM_ITEMS) {
|
||||
f.episodes.add(Episode(0, pref + i, pref + i, "link", Date(), Episode.PLAYED, f))
|
||||
f.episodes.add(Episode(0, pref + i, pref + i, "link", Date(), Episode.PlayState.PLAYED.code, f))
|
||||
}
|
||||
// val adapter = getInstance()
|
||||
// adapter.open()
|
||||
|
|
|
@ -113,7 +113,7 @@ class UITestUtils(private val context: Context) {
|
|||
val items: MutableList<Episode> = ArrayList()
|
||||
for (j in 0 until NUM_ITEMS_PER_FEED) {
|
||||
val item = Episode(j.toLong(), "Feed " + (i + 1) + ": Item " + (j + 1), "item$j",
|
||||
"http://example.com/feed$i/item/$j", Date(), Episode.UNPLAYED, feed)
|
||||
"http://example.com/feed$i/item/$j", Date(), Episode.PlayState.UNPLAYED.code, feed)
|
||||
items.add(item)
|
||||
|
||||
if (!hostTextOnlyFeeds) {
|
||||
|
|
|
@ -14,7 +14,6 @@ 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.RealmDB.upsert
|
||||
import ac.mdiq.podcini.storage.database.RealmDB.upsertBlk
|
||||
import ac.mdiq.podcini.storage.model.DownloadResult
|
||||
import ac.mdiq.podcini.storage.model.Episode
|
||||
|
@ -78,7 +77,7 @@ class DownloadServiceInterfaceImpl : DownloadServiceInterface() {
|
|||
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()
|
||||
if (item_ != null) Episodes.deleteMediaOfEpisode(context, item_) // Remove partially downloaded file
|
||||
if (item_ != null) Episodes.deleteEpisodeMedia(context, item_) // Remove partially downloaded file
|
||||
val tag = WORK_TAG_EPISODE_URL + media.downloadUrl
|
||||
val future: Future<List<WorkInfo>> = WorkManager.getInstance(context).getWorkInfosByTag(tag)
|
||||
|
||||
|
@ -124,7 +123,8 @@ class DownloadServiceInterfaceImpl : DownloadServiceInterface() {
|
|||
.addTag(WORK_TAG)
|
||||
.addTag(WORK_TAG_EPISODE_URL + item.media!!.downloadUrl)
|
||||
if (enqueueDownloadedEpisodes()) {
|
||||
runBlocking { Queues.addToQueueSync(false, item, item.feed?.preferences?.queue) }
|
||||
if (item.feed?.preferences?.queue != null)
|
||||
runBlocking { Queues.addToQueueSync(false, item, item.feed?.preferences?.queue) }
|
||||
workRequest.addTag(WORK_DATA_WAS_QUEUED)
|
||||
}
|
||||
workRequest.setInputData(Data.Builder().putLong(WORK_DATA_MEDIA_ID, item.media!!.id).build())
|
||||
|
|
|
@ -132,7 +132,7 @@ object LocalFeedUpdater {
|
|||
}
|
||||
|
||||
private fun createFeedItem(feed: Feed, file: FastDocumentFile, context: Context): Episode {
|
||||
val item = Episode(0L, file.name, UUID.randomUUID().toString(), file.name, Date(file.lastModified), Episode.UNPLAYED, feed)
|
||||
val item = Episode(0L, file.name, UUID.randomUUID().toString(), file.name, Date(file.lastModified), Episode.PlayState.UNPLAYED.code, feed)
|
||||
item.disableAutoDownload()
|
||||
|
||||
val size = file.length
|
||||
|
|
|
@ -253,8 +253,11 @@ class LocalMediaPlayer(context: Context, callback: MediaPlayerCallback) : MediaP
|
|||
curMedia = playable
|
||||
if (curMedia is EpisodeMedia) {
|
||||
val media_ = curMedia as EpisodeMedia
|
||||
curIndexInQueue = EpisodeUtil.indexOfItemWithId(curQueue.episodes, media_.id)
|
||||
val item = media_.episodeOrFetch()
|
||||
val eList = if (item?.feed?.preferences?.queue != null) curQueue.episodes else item?.feed?.getVirtualQueueItems() ?: listOf()
|
||||
curIndexInQueue = EpisodeUtil.indexOfItemWithId(eList, media_.id)
|
||||
} else curIndexInQueue = -1
|
||||
|
||||
prevMedia = curMedia
|
||||
this.isStreaming = stream
|
||||
mediaType = curMedia!!.getMediaType()
|
||||
|
|
|
@ -311,7 +311,7 @@ class PlaybackService : MediaSessionService() {
|
|||
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(Episode.PLAYED, ended || (skipped && smartMarkAsPlayed), item!!)
|
||||
item = setPlayStateSync(Episode.PlayState.PLAYED.code, ended || (skipped && smartMarkAsPlayed), item!!)
|
||||
val action = item?.feed?.preferences?.autoDeleteAction
|
||||
val shouldAutoDelete = (action == AutoDeleteAction.ALWAYS ||
|
||||
(action == AutoDeleteAction.GLOBAL && item?.feed != null && shouldAutoDeleteItem(item!!.feed!!)))
|
||||
|
@ -355,11 +355,6 @@ class PlaybackService : MediaSessionService() {
|
|||
|
||||
override fun getNextInQueue(currentMedia: Playable?): Playable? {
|
||||
Logd(TAG, "call getNextInQueue currentMedia: ${currentMedia?.getEpisodeTitle()}")
|
||||
if (curIndexInQueue < 0) {
|
||||
Logd(TAG, "getNextInQueue(), curMedia is not in curQueue")
|
||||
writeNoMediaPlaying()
|
||||
return null
|
||||
}
|
||||
if (currentMedia !is EpisodeMedia) {
|
||||
Logd(TAG, "getNextInQueue(), but playable not an instance of EpisodeMedia, so not proceeding")
|
||||
writeNoMediaPlaying()
|
||||
|
@ -371,20 +366,29 @@ class PlaybackService : MediaSessionService() {
|
|||
writeNoMediaPlaying()
|
||||
return null
|
||||
}
|
||||
// val nextItem = getNextInQueue(item)
|
||||
if (curQueue.episodes.isEmpty()) {
|
||||
if (curIndexInQueue < 0 && item.feed?.preferences?.queue != null) {
|
||||
Logd(TAG, "getNextInQueue(), curMedia is not in curQueue")
|
||||
writeNoMediaPlaying()
|
||||
return null
|
||||
}
|
||||
val eList = if (item.feed?.preferences?.queue == null) item.feed?.getVirtualQueueItems() else curQueue.episodes
|
||||
if (eList.isNullOrEmpty()) {
|
||||
Logd(TAG, "getNextInQueue queue is empty")
|
||||
writeNoMediaPlaying()
|
||||
return null
|
||||
}
|
||||
Logd(TAG, "getNextInQueue eList: ${eList.size}")
|
||||
var j = 0
|
||||
val i = EpisodeUtil.indexOfItemWithId(curQueue.episodes, item.id)
|
||||
val i = EpisodeUtil.indexOfItemWithId(eList, item.id)
|
||||
Logd(TAG, "getNextInQueue current i: $i curIndexInQueue: $curIndexInQueue")
|
||||
if (i < 0) {
|
||||
if (curIndexInQueue < curQueue.episodes.size) j = curIndexInQueue
|
||||
else j = curQueue.episodes.size-1
|
||||
} else if (i < curQueue.episodes.size-1) j = i+1
|
||||
if (curIndexInQueue >= 0 && curIndexInQueue < eList.size) j = curIndexInQueue
|
||||
else j = eList.size-1
|
||||
} else if (i < eList.size-1) j = i+1
|
||||
Logd(TAG, "getNextInQueue next j: $j")
|
||||
|
||||
val nextItem = unmanaged(curQueue.episodes[j])
|
||||
val nextItem = unmanaged(eList[j])
|
||||
Logd(TAG, "getNextInQueue nextItem ${nextItem.title}")
|
||||
if (nextItem.media == null) {
|
||||
Logd(TAG, "getNextInQueue nextItem: $nextItem media is null")
|
||||
writeNoMediaPlaying()
|
||||
|
@ -397,7 +401,7 @@ class PlaybackService : MediaSessionService() {
|
|||
return null
|
||||
}
|
||||
|
||||
if (!nextItem.media!!.localFileAvailable() && !isStreamingAllowed && isFollowQueue && nextItem.feed != null && !nextItem.feed!!.isLocalFeed) {
|
||||
if (!nextItem.media!!.localFileAvailable() && !isStreamingAllowed && isFollowQueue && nextItem.feed?.isLocalFeed != true) {
|
||||
Logd(TAG, "getNextInQueue nextItem has no local file ${nextItem.title}")
|
||||
displayStreamingNotAllowedNotification(PlaybackServiceStarter(this@PlaybackService, nextItem.media!!).intent)
|
||||
writeNoMediaPlaying()
|
||||
|
@ -405,7 +409,7 @@ class PlaybackService : MediaSessionService() {
|
|||
}
|
||||
EventFlow.postEvent(FlowEvent.PlayEvent(item, FlowEvent.PlayEvent.Action.END))
|
||||
EventFlow.postEvent(FlowEvent.PlayEvent(nextItem))
|
||||
return if (nextItem.media == null) nextItem.media else unmanaged(nextItem.media!!)
|
||||
return if (nextItem.media == null) null else unmanaged(nextItem.media!!)
|
||||
}
|
||||
|
||||
override fun findMedia(url: String): Playable? {
|
||||
|
@ -419,14 +423,12 @@ class PlaybackService : MediaSessionService() {
|
|||
if (stopPlaying) taskManager.cancelPositionSaver()
|
||||
|
||||
if (mediaType == null) sendNotificationBroadcast(NOTIFICATION_TYPE_PLAYBACK_END, 0)
|
||||
else {
|
||||
sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD,
|
||||
when {
|
||||
isCasting -> EXTRA_CODE_CAST
|
||||
mediaType == MediaType.VIDEO -> EXTRA_CODE_VIDEO
|
||||
else -> EXTRA_CODE_AUDIO
|
||||
})
|
||||
}
|
||||
else sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD,
|
||||
when {
|
||||
isCasting -> EXTRA_CODE_CAST
|
||||
mediaType == MediaType.VIDEO -> EXTRA_CODE_VIDEO
|
||||
else -> EXTRA_CODE_AUDIO
|
||||
})
|
||||
}
|
||||
|
||||
override fun ensureMediaInfoLoaded(media: Playable) {
|
||||
|
@ -962,7 +964,7 @@ class PlaybackService : MediaSessionService() {
|
|||
if (event.action == FlowEvent.QueueEvent.Action.REMOVED) {
|
||||
Logd(TAG, "onQueueEvent: ending playback curEpisode ${curEpisode?.title}")
|
||||
for (e in event.episodes) {
|
||||
Logd(TAG, "onQueueEvent: ending playback event ${e?.title}")
|
||||
Logd(TAG, "onQueueEvent: ending playback event ${e.title}")
|
||||
if (e.id == curEpisode?.id) {
|
||||
mPlayer?.endPlayback(hasEnded = false, wasSkipped = true, shouldContinue = true, toStoppedState = true)
|
||||
break
|
||||
|
@ -1075,7 +1077,7 @@ class PlaybackService : MediaSessionService() {
|
|||
if (media != null) {
|
||||
media.setPosition(position)
|
||||
media.setLastPlayedTime(System.currentTimeMillis())
|
||||
if (it.isNew) it.playState = Episode.UNPLAYED
|
||||
if (it.isNew) it.playState = Episode.PlayState.UNPLAYED.code
|
||||
if (media.startPosition >= 0 && media.getPosition() > media.startPosition)
|
||||
media.playedDuration = (media.playedDurationWhenStarted + media.getPosition() - media.startPosition)
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import ac.mdiq.podcini.preferences.UserPreferences.EPISODE_CLEANUP_NULL
|
|||
import ac.mdiq.podcini.preferences.UserPreferences.appPrefs
|
||||
import ac.mdiq.podcini.preferences.UserPreferences.episodeCacheSize
|
||||
import ac.mdiq.podcini.preferences.UserPreferences.isEnableAutodownload
|
||||
import ac.mdiq.podcini.storage.database.Episodes.deleteMediaOfEpisode
|
||||
import ac.mdiq.podcini.storage.database.Episodes.deleteEpisodeMedia
|
||||
import ac.mdiq.podcini.storage.database.Episodes.getEpisodes
|
||||
import ac.mdiq.podcini.storage.database.Episodes.getEpisodesCount
|
||||
import ac.mdiq.podcini.storage.database.Queues.getInQueueEpisodeIds
|
||||
|
@ -84,7 +84,7 @@ object AutoCleanups {
|
|||
for (item in delete) {
|
||||
if (item.media == null) continue
|
||||
try {
|
||||
runBlocking { deleteMediaOfEpisode(context, item).join() }
|
||||
runBlocking { deleteEpisodeMedia(context, item).join() }
|
||||
} catch (e: InterruptedException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: ExecutionException) {
|
||||
|
@ -138,7 +138,7 @@ object AutoCleanups {
|
|||
for (item in delete) {
|
||||
if (item.media == null) continue
|
||||
try {
|
||||
runBlocking { deleteMediaOfEpisode(context, item).join() }
|
||||
runBlocking { deleteEpisodeMedia(context, item).join() }
|
||||
} catch (e: InterruptedException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: ExecutionException) {
|
||||
|
@ -205,7 +205,7 @@ object AutoCleanups {
|
|||
val delete = if (candidates.size > numToRemove) candidates.subList(0, numToRemove) else candidates
|
||||
for (item in delete) {
|
||||
try {
|
||||
runBlocking { deleteMediaOfEpisode(context, item).join() }
|
||||
runBlocking { deleteEpisodeMedia(context, item).join() }
|
||||
} catch (e: InterruptedException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: ExecutionException) {
|
||||
|
|
|
@ -19,10 +19,7 @@ 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.Episode.Companion.BUILDING
|
||||
import ac.mdiq.podcini.storage.model.Episode.Companion.NEW
|
||||
import ac.mdiq.podcini.storage.model.Episode.Companion.PLAYED
|
||||
import ac.mdiq.podcini.storage.model.Episode.Companion.UNPLAYED
|
||||
import ac.mdiq.podcini.storage.model.Episode.PlayState
|
||||
import ac.mdiq.podcini.storage.model.EpisodeFilter
|
||||
import ac.mdiq.podcini.storage.model.EpisodeMedia
|
||||
import ac.mdiq.podcini.storage.model.EpisodeSortOrder
|
||||
|
@ -98,7 +95,7 @@ object Episodes {
|
|||
|
||||
// @JvmStatic is needed because some Runnable blocks call this
|
||||
@OptIn(UnstableApi::class) @JvmStatic
|
||||
fun deleteMediaOfEpisode(context: Context, episode: Episode) : Job {
|
||||
fun deleteEpisodeMedia(context: Context, episode: Episode) : Job {
|
||||
Logd(TAG, "deleteMediaOfEpisode called ${episode.title}")
|
||||
return runOnIOScope {
|
||||
if (episode.media == null) return@runOnIOScope
|
||||
|
@ -136,7 +133,7 @@ object Episodes {
|
|||
url != null -> {
|
||||
// delete downloaded media file
|
||||
val mediaFile = File(url)
|
||||
if (mediaFile.exists() && !mediaFile.delete()) {
|
||||
if (!mediaFile.delete()) {
|
||||
Log.e(TAG, "delete media file failed: $url")
|
||||
val evt = FlowEvent.MessageEvent(context.getString(R.string.delete_failed))
|
||||
EventFlow.postEvent(evt)
|
||||
|
@ -176,6 +173,7 @@ object Episodes {
|
|||
* Remove the listed episodes and their EpisodeMedia entries.
|
||||
* Deleting media also removes the download log entries.
|
||||
*/
|
||||
@UnstableApi
|
||||
fun deleteEpisodes(context: Context, episodes: List<Episode>) : Job {
|
||||
return runOnIOScope {
|
||||
val removedFromQueue: MutableList<Episode> = ArrayList()
|
||||
|
@ -290,19 +288,19 @@ object Episodes {
|
|||
suspend fun setPlayStateSync(played: Int, resetMediaPosition: Boolean, episode: Episode) : Episode {
|
||||
Logd(TAG, "setPlayStateSync called resetMediaPosition: $resetMediaPosition")
|
||||
val result = upsert(episode) {
|
||||
if (played >= NEW && played <= BUILDING) it.playState = played
|
||||
if (played >= PlayState.NEW.code && played <= PlayState.BUILDING.code) it.playState = played
|
||||
else {
|
||||
if (it.playState == PLAYED) it.playState = UNPLAYED
|
||||
else it.playState = PLAYED
|
||||
if (it.playState == PlayState.PLAYED.code) it.playState = PlayState.UNPLAYED.code
|
||||
else it.playState = PlayState.PLAYED.code
|
||||
}
|
||||
if (resetMediaPosition) it.media?.setPosition(0)
|
||||
}
|
||||
if (played == PLAYED && shouldRemoveFromQueuesMarkPlayed()) removeFromAllQueuesSync(result)
|
||||
if (played == PlayState.PLAYED.code && shouldMarkedPlayedRemoveFromQueues()) removeFromAllQueuesSync(result)
|
||||
EventFlow.postEvent(FlowEvent.EpisodePlayedEvent(result))
|
||||
return result
|
||||
}
|
||||
|
||||
private fun shouldRemoveFromQueuesMarkPlayed(): Boolean {
|
||||
private fun shouldMarkedPlayedRemoveFromQueues(): Boolean {
|
||||
return appPrefs.getBoolean(Prefs.prefRemoveFromQueueMarkedPlayed.name, true)
|
||||
}
|
||||
}
|
|
@ -377,13 +377,6 @@ object Feeds {
|
|||
backupManager.dataChanged()
|
||||
}
|
||||
|
||||
// private fun persistFeedsSync(vararg feeds: Feed) {
|
||||
// Logd(TAG, "persistFeedsSync called")
|
||||
// for (feed in feeds) {
|
||||
// upsertBlk(feed) {}
|
||||
// }
|
||||
// }
|
||||
|
||||
fun persistFeedPreferences(feed: Feed) : Job {
|
||||
Logd(TAG, "persistFeedPreferences called")
|
||||
return runOnIOScope {
|
||||
|
|
|
@ -56,7 +56,7 @@ object LogsAndStats {
|
|||
feedTotalTime += m.duration
|
||||
if (m.lastPlayedTime in timeFilterFrom..<timeFilterTo) {
|
||||
if (includeMarkedAsPlayed) {
|
||||
if ((m.playbackCompletionTime > 0 && m.playedDuration > 0) || m.episodeOrFetch()?.playState == Episode.PLAYED || m.position > 0) {
|
||||
if ((m.playbackCompletionTime > 0 && m.playedDuration > 0) || m.episodeOrFetch()?.playState == Episode.PlayState.PLAYED.code || m.position > 0) {
|
||||
episodesStarted += 1
|
||||
feedPlayedTime += m.duration
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ 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.*
|
||||
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.event.EventFlow
|
||||
|
@ -144,7 +145,7 @@ object Queues {
|
|||
for (event in events) EventFlow.postEvent(event)
|
||||
|
||||
// EventFlow.postEvent(FlowEvent.EpisodeEvent.updated(updatedItems))
|
||||
if (markAsUnplayed && markAsUnplayeds.size > 0) setPlayState(Episode.UNPLAYED, false, *markAsUnplayeds.toTypedArray())
|
||||
if (markAsUnplayed && markAsUnplayeds.size > 0) setPlayState(Episode.PlayState.UNPLAYED.code, false, *markAsUnplayeds.toTypedArray())
|
||||
// if (performAutoDownload) autodownloadEpisodeMedia(context)
|
||||
}
|
||||
}
|
||||
|
@ -153,7 +154,6 @@ object Queues {
|
|||
suspend fun addToQueueSync(markAsUnplayed: Boolean, episode: Episode, queue_: PlayQueue? = null) {
|
||||
Logd(TAG, "addToQueueSync( ... ) called")
|
||||
|
||||
// val queue = if (queue_ != null) unmanaged(queue_) else curQueue
|
||||
val queue = queue_ ?: curQueue
|
||||
val currentlyPlaying = curMedia
|
||||
val positionCalculator = EnqueuePositionCalculator(enqueueLocation)
|
||||
|
@ -166,11 +166,9 @@ object Queues {
|
|||
insertPosition++
|
||||
it.update()
|
||||
}
|
||||
// queueNew.episodes.addAll(queue.episodes)
|
||||
// queueNew.episodes.add(insertPosition, episode)
|
||||
if (queue.id == curQueue.id) curQueue = queueNew
|
||||
|
||||
if (markAsUnplayed && episode.isNew) setPlayState(Episode.UNPLAYED, false, episode)
|
||||
if (markAsUnplayed && episode.isNew) setPlayState(Episode.PlayState.UNPLAYED.code, false, episode)
|
||||
if (queue.id == curQueue.id) EventFlow.postEvent(FlowEvent.QueueEvent.added(episode, insertPosition))
|
||||
// if (performAutoDownload) autodownloadEpisodeMedia(context)
|
||||
}
|
||||
|
@ -246,9 +244,10 @@ object Queues {
|
|||
val events: MutableList<FlowEvent.QueueEvent> = ArrayList()
|
||||
val indicesToRemove: MutableList<Int> = mutableListOf()
|
||||
val qItems = queue.episodes.toMutableList()
|
||||
val eList = episodes.toList()
|
||||
for (i in qItems.indices) {
|
||||
val episode = qItems[i]
|
||||
if (episodes.contains(episode)) {
|
||||
if (indexOfItemWithId(eList, episode.id) >= 0) {
|
||||
Logd(TAG, "removing from queue: ${episode.id} ${episode.title}")
|
||||
indicesToRemove.add(i)
|
||||
if (queue.id == curQueue.id) events.add(FlowEvent.QueueEvent.removed(episode))
|
||||
|
|
|
@ -18,7 +18,7 @@ import kotlin.coroutines.ContinuationInterceptor
|
|||
object RealmDB {
|
||||
private val TAG: String = RealmDB::class.simpleName ?: "Anonymous"
|
||||
|
||||
private const val SCHEMA_VERSION_NUMBER = 17L
|
||||
private const val SCHEMA_VERSION_NUMBER = 18L
|
||||
|
||||
private val ioScope = CoroutineScope(Dispatchers.IO)
|
||||
|
||||
|
|
|
@ -86,7 +86,7 @@ class Episode : RealmObject {
|
|||
|
||||
@Ignore
|
||||
val isNew: Boolean
|
||||
get() = playState == NEW
|
||||
get() = playState == PlayState.NEW.code
|
||||
|
||||
@Ignore
|
||||
val isInProgress: Boolean
|
||||
|
@ -123,7 +123,7 @@ class Episode : RealmObject {
|
|||
}
|
||||
|
||||
constructor() {
|
||||
this.playState = UNPLAYED
|
||||
this.playState = PlayState.UNPLAYED.code
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -187,19 +187,19 @@ class Episode : RealmObject {
|
|||
}
|
||||
|
||||
fun setNew() {
|
||||
playState = NEW
|
||||
playState = PlayState.NEW.code
|
||||
}
|
||||
|
||||
fun isPlayed(): Boolean {
|
||||
return playState == PLAYED
|
||||
return playState == PlayState.PLAYED.code
|
||||
}
|
||||
|
||||
fun setPlayed(played: Boolean) {
|
||||
playState = if (played) PLAYED else UNPLAYED
|
||||
playState = if (played) PlayState.PLAYED.code else PlayState.UNPLAYED.code
|
||||
}
|
||||
|
||||
fun setBuilding() {
|
||||
playState = BUILDING
|
||||
playState = PlayState.BUILDING.code
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -252,13 +252,15 @@ class Episode : RealmObject {
|
|||
return (id xor (id ushr 32)).toInt()
|
||||
}
|
||||
|
||||
enum class PlayState(val code: Int) {
|
||||
UNSPECIFIED(-2),
|
||||
NEW(-1),
|
||||
UNPLAYED(0),
|
||||
PLAYED(1),
|
||||
BUILDING(2),
|
||||
ABANDONED(3)
|
||||
}
|
||||
companion object {
|
||||
val TAG: String = Episode::class.simpleName ?: "Anonymous"
|
||||
|
||||
const val UNSPECIFIED: Int = -2
|
||||
const val NEW: Int = -1
|
||||
const val UNPLAYED: Int = 0
|
||||
const val PLAYED: Int = 1
|
||||
const val BUILDING: Int = 2
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package ac.mdiq.podcini.storage.model
|
|||
import ac.mdiq.podcini.storage.database.RealmDB.realm
|
||||
import ac.mdiq.podcini.storage.model.FeedFunding.Companion.extractPaymentLinks
|
||||
import ac.mdiq.podcini.storage.model.EpisodeSortOrder.Companion.fromCode
|
||||
import ac.mdiq.podcini.storage.utils.EpisodesPermutors.getPermutor
|
||||
import io.realm.kotlin.ext.realmListOf
|
||||
import io.realm.kotlin.types.RealmList
|
||||
import io.realm.kotlin.types.RealmObject
|
||||
|
@ -144,20 +145,7 @@ class Feed : RealmObject {
|
|||
|
||||
@Ignore
|
||||
val mostRecentItem: Episode?
|
||||
get() {
|
||||
// // we could sort, but we don't need to, a simple search is fine...
|
||||
// var mostRecentDate = Date(0)
|
||||
// var mostRecentItem: Episode? = null
|
||||
// for (item in episodes) {
|
||||
// val date = item.getPubDate()
|
||||
// if (date != null && date.after(mostRecentDate)) {
|
||||
// mostRecentDate = date
|
||||
// mostRecentItem = item
|
||||
// }
|
||||
// }
|
||||
// return mostRecentItem
|
||||
return realm.query(Episode::class).query("feedId == $id SORT(pubDate DESC)").first().find()
|
||||
}
|
||||
get() = realm.query(Episode::class).query("feedId == $id SORT(pubDate DESC)").first().find()
|
||||
|
||||
@Ignore
|
||||
var title: String?
|
||||
|
@ -297,6 +285,14 @@ class Feed : RealmObject {
|
|||
paymentLinks.add(funding)
|
||||
}
|
||||
|
||||
fun getVirtualQueueItems(): List<Episode> {
|
||||
var qString = "feedId == $id AND playState != ${Episode.PlayState.PLAYED.code}"
|
||||
if (preferences?.prefStreamOverDownload != true) qString += " AND media.downloaded == true"
|
||||
val eList_ = realm.query(Episode::class, qString).find().toMutableList()
|
||||
if (sortOrder != null) getPermutor(sortOrder!!).reorder(eList_)
|
||||
return eList_
|
||||
}
|
||||
|
||||
companion object {
|
||||
val TAG: String = Feed::class.simpleName ?: "Anonymous"
|
||||
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
package ac.mdiq.podcini.storage.model
|
||||
|
||||
import ac.mdiq.podcini.playback.base.InTheatre.curQueue
|
||||
import ac.mdiq.podcini.storage.database.RealmDB.realm
|
||||
import ac.mdiq.podcini.storage.model.VolumeAdaptionSetting.Companion.fromInteger
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import io.realm.kotlin.ext.realmSetOf
|
||||
import io.realm.kotlin.types.EmbeddedRealmObject
|
||||
import io.realm.kotlin.types.RealmSet
|
||||
|
@ -46,6 +48,8 @@ class FeedPreferences : EmbeddedRealmObject {
|
|||
}
|
||||
var volumeAdaption: Int = 0
|
||||
|
||||
var prefStreamOverDownload: Boolean = false
|
||||
|
||||
var filterString: String = ""
|
||||
|
||||
var sortOrderCode: Int = 0 // in EpisodeSortOrder
|
||||
|
@ -62,11 +66,31 @@ class FeedPreferences : EmbeddedRealmObject {
|
|||
|
||||
@Ignore
|
||||
var queue: PlayQueue? = null
|
||||
get() = if(queueId >= 0) realm.query(PlayQueue::class).query("id == $queueId").first().find() else null
|
||||
get() = when {
|
||||
queueId >= 0 -> realm.query(PlayQueue::class).query("id == $queueId").first().find()
|
||||
queueId == -1L -> curQueue
|
||||
queueId == -2L -> null
|
||||
else -> null
|
||||
}
|
||||
set(value) {
|
||||
field = value
|
||||
queueId = value?.id ?: -1L
|
||||
}
|
||||
@Ignore
|
||||
var queueText: String = "Default"
|
||||
get() = when (queueId) {
|
||||
0L -> "Default"
|
||||
-1L -> "Active"
|
||||
-2L -> "None"
|
||||
else -> "Custom"
|
||||
}
|
||||
@Ignore
|
||||
val queueTextExt: String
|
||||
get() = when (queueId) {
|
||||
-1L -> "Active"
|
||||
-2L -> "None"
|
||||
else -> queue?.name ?: "Default"
|
||||
}
|
||||
var queueId: Long = 0L
|
||||
|
||||
@Ignore
|
||||
|
|
|
@ -11,9 +11,7 @@ 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.RealmDB.realm
|
||||
import ac.mdiq.podcini.storage.database.RealmDB.unmanaged
|
||||
import ac.mdiq.podcini.storage.model.Episode
|
||||
import ac.mdiq.podcini.storage.model.Episode.Companion.UNSPECIFIED
|
||||
import ac.mdiq.podcini.storage.model.PlayQueue
|
||||
import ac.mdiq.podcini.ui.activity.MainActivity
|
||||
import ac.mdiq.podcini.ui.utils.LocalDeleteModal
|
||||
|
@ -47,7 +45,7 @@ class EpisodeMultiSelectHandler(private val activity: MainActivity, private val
|
|||
R.id.put_in_queue_batch -> PutToQueueDialog(activity, items).show()
|
||||
R.id.remove_from_queue_batch -> removeFromQueueChecked(items)
|
||||
R.id.toggle_played_batch -> {
|
||||
setPlayState(UNSPECIFIED, false, *items.toTypedArray())
|
||||
setPlayState(Episode.PlayState.UNSPECIFIED.code, false, *items.toTypedArray())
|
||||
// showMessage(R.plurals.marked_read_batch_label, items.size)
|
||||
}
|
||||
// R.id.mark_read_batch -> {
|
||||
|
|
|
@ -46,7 +46,8 @@ abstract class EpisodeActionButton internal constructor(@JvmField var item: Epis
|
|||
episode.feed != null && episode.feed!!.isLocalFeed -> PlayLocalActionButton(episode)
|
||||
media.downloaded -> PlayActionButton(episode)
|
||||
isDownloadingMedia -> CancelDownloadActionButton(episode)
|
||||
isStreamOverDownload || episode.feed == null || episode.feedId == null -> StreamActionButton(episode)
|
||||
isStreamOverDownload || episode.feed == null || episode.feedId == null || episode.feed?.preferences?.prefStreamOverDownload == true ->
|
||||
StreamActionButton(episode)
|
||||
else -> DownloadActionButton(episode)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ class MarkAsPlayedActionButton(item: Episode) : EpisodeActionButton(item) {
|
|||
}
|
||||
|
||||
@UnstableApi override fun onClick(context: Context) {
|
||||
if (!item.isPlayed()) setPlayState(Episode.PLAYED, true, item)
|
||||
if (!item.isPlayed()) setPlayState(Episode.PlayState.PLAYED.code, true, item)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,14 +1,17 @@
|
|||
package ac.mdiq.podcini.ui.actions.actionbutton
|
||||
|
||||
import ac.mdiq.podcini.R
|
||||
import ac.mdiq.podcini.net.utils.NetworkUtils.isStreamingAllowed
|
||||
import ac.mdiq.podcini.playback.PlaybackController.Companion.getPlayerActivityIntent
|
||||
import ac.mdiq.podcini.playback.PlaybackController.Companion.playbackService
|
||||
import ac.mdiq.podcini.playback.PlaybackServiceStarter
|
||||
import ac.mdiq.podcini.playback.base.InTheatre
|
||||
import ac.mdiq.podcini.storage.database.RealmDB.realm
|
||||
import ac.mdiq.podcini.storage.database.RealmDB.upsertBlk
|
||||
import ac.mdiq.podcini.storage.model.Episode
|
||||
import ac.mdiq.podcini.storage.model.EpisodeMedia
|
||||
import ac.mdiq.podcini.storage.model.MediaType
|
||||
import ac.mdiq.podcini.ui.actions.actionbutton.StreamActionButton.StreamingConfirmationDialog
|
||||
import ac.mdiq.podcini.util.Logd
|
||||
import ac.mdiq.podcini.util.event.EventFlow
|
||||
import ac.mdiq.podcini.util.event.FlowEvent
|
||||
|
@ -57,10 +60,11 @@ class PlayActionButton(item: Episode) : EpisodeActionButton(item) {
|
|||
fun notifyMissingEpisodeMediaFile(context: Context, media: EpisodeMedia) {
|
||||
Logd(TAG, "notifyMissingEpisodeMediaFile called")
|
||||
Log.i(TAG, "The feedmanager was notified about a missing episode. It will update its database now.")
|
||||
val episode = media.episodeOrFetch()
|
||||
val episode = realm.query(Episode::class).query("id == media.id").first().find()
|
||||
// val episode = media.episodeOrFetch()
|
||||
if (episode != null) {
|
||||
val episode_ = upsertBlk(episode) {
|
||||
it.media = media
|
||||
// it.media = media
|
||||
it.media?.downloaded = false
|
||||
it.media?.fileUrl = null
|
||||
}
|
||||
|
|
|
@ -141,7 +141,7 @@ object EpisodeMenuHandler {
|
|||
}
|
||||
R.id.mark_read_item -> {
|
||||
// selectedItem.setPlayed(true)
|
||||
setPlayState(Episode.PLAYED, true, selectedItem)
|
||||
setPlayState(Episode.PlayState.PLAYED.code, true, selectedItem)
|
||||
if (selectedItem.feed?.isLocalFeed != true && (isProviderConnected || wifiSyncEnabledKey)) {
|
||||
val media: EpisodeMedia? = selectedItem.media
|
||||
// not all items have media, Gpodder only cares about those that do
|
||||
|
@ -158,7 +158,7 @@ object EpisodeMenuHandler {
|
|||
}
|
||||
R.id.mark_unread_item -> {
|
||||
// selectedItem.setPlayed(false)
|
||||
setPlayState(Episode.UNPLAYED, false, selectedItem)
|
||||
setPlayState(Episode.PlayState.UNPLAYED.code, false, selectedItem)
|
||||
if (needSynch() && selectedItem.feed?.isLocalFeed != true && selectedItem.media != null) {
|
||||
val actionNew: EpisodeAction = EpisodeAction.Builder(selectedItem, EpisodeAction.NEW)
|
||||
.currentTimestamp()
|
||||
|
@ -176,7 +176,7 @@ object EpisodeMenuHandler {
|
|||
writeNoMediaPlaying()
|
||||
IntentUtils.sendLocalBroadcast(context, ACTION_SHUTDOWN_PLAYBACK_SERVICE)
|
||||
}
|
||||
setPlayState(Episode.UNPLAYED, true, selectedItem)
|
||||
setPlayState(Episode.PlayState.UNPLAYED.code, true, selectedItem)
|
||||
}
|
||||
R.id.visit_website_item -> {
|
||||
val url = selectedItem.getLinkWithFallback()
|
||||
|
|
|
@ -61,7 +61,7 @@ class RemoveFromQueueSwipeAction : SwipeAction {
|
|||
fun addToQueueAt(episode: Episode, index: Int) : Job {
|
||||
return runOnIOScope {
|
||||
if (curQueue.episodeIds.contains(episode.id)) return@runOnIOScope
|
||||
if (episode.isNew) setPlayState(Episode.UNPLAYED, false, episode)
|
||||
if (episode.isNew) setPlayState(Episode.PlayState.UNPLAYED.code, false, episode)
|
||||
curQueue = upsert(curQueue) {
|
||||
it.episodeIds.add(index, episode.id)
|
||||
it.update()
|
||||
|
|
|
@ -39,7 +39,7 @@ class TogglePlaybackStateSwipeAction : SwipeAction {
|
|||
}
|
||||
|
||||
override fun performAction(item: Episode, fragment: Fragment, filter: EpisodeFilter) {
|
||||
val newState = if (item.playState == Episode.UNPLAYED) Episode.PLAYED else Episode.UNPLAYED
|
||||
val newState = if (item.playState == Episode.PlayState.UNPLAYED.code) Episode.PlayState.PLAYED.code else Episode.PlayState.UNPLAYED.code
|
||||
|
||||
Logd("TogglePlaybackStateSwipeAction", "performAction( ${item.id} )")
|
||||
// we're marking it as unplayed since the user didn't actually play it
|
||||
|
@ -55,10 +55,10 @@ class TogglePlaybackStateSwipeAction : SwipeAction {
|
|||
if (shouldDeleteRemoveFromQueue()) removeFromQueueSync(null, item) }
|
||||
}
|
||||
val playStateStringRes: Int = when (newState) {
|
||||
Episode.UNPLAYED -> if (item.playState == Episode.NEW) R.string.removed_inbox_label //was new
|
||||
Episode.PlayState.UNPLAYED.code -> if (item.playState == Episode.PlayState.NEW.code) R.string.removed_inbox_label //was new
|
||||
else R.string.marked_as_unplayed_label //was played
|
||||
Episode.PLAYED -> R.string.marked_as_played_label
|
||||
else -> if (item.playState == Episode.NEW) R.string.removed_inbox_label
|
||||
Episode.PlayState.PLAYED.code -> R.string.marked_as_played_label
|
||||
else -> if (item.playState == Episode.PlayState.NEW.code) R.string.removed_inbox_label
|
||||
else R.string.marked_as_unplayed_label
|
||||
}
|
||||
val duration: Int = Snackbar.LENGTH_LONG
|
||||
|
@ -87,7 +87,7 @@ class TogglePlaybackStateSwipeAction : SwipeAction {
|
|||
}
|
||||
|
||||
override fun willRemove(filter: EpisodeFilter, item: Episode): Boolean {
|
||||
return if (item.playState == Episode.NEW) filter.showPlayed || filter.showNew
|
||||
return if (item.playState == Episode.PlayState.NEW.code) filter.showPlayed || filter.showNew
|
||||
else filter.showUnplayed || filter.showPlayed || filter.showNew
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package ac.mdiq.podcini.ui.adapter
|
||||
|
||||
import ac.mdiq.podcini.R
|
||||
import ac.mdiq.podcini.storage.database.RealmDB.unmanaged
|
||||
import ac.mdiq.podcini.storage.model.Episode
|
||||
import ac.mdiq.podcini.storage.model.Feed
|
||||
import ac.mdiq.podcini.ui.activity.MainActivity
|
||||
|
@ -92,6 +91,7 @@ open class EpisodesAdapter(mainActivity: MainActivity, var refreshFragPosCallbac
|
|||
|
||||
@UnstableApi
|
||||
override fun onBindViewHolder(holder: EpisodeViewHolder, pos: Int) {
|
||||
// Logd(TAG, "onBindViewHolder $pos ${holder.episode?.title}")
|
||||
if (pos >= episodes.size || pos < 0) {
|
||||
beforeBindViewHolder(holder, pos)
|
||||
holder.bindDummy()
|
||||
|
@ -150,8 +150,10 @@ open class EpisodesAdapter(mainActivity: MainActivity, var refreshFragPosCallbac
|
|||
|
||||
@UnstableApi
|
||||
override fun onBindViewHolder(holder: EpisodeViewHolder, pos: Int, payloads: MutableList<Any>) {
|
||||
// Logd(TAG, "onBindViewHolder payloads $pos ${holder.episode?.title}")
|
||||
if (payloads.isEmpty()) onBindViewHolder(holder, pos)
|
||||
else {
|
||||
holder.refreshAdapterPosCallback = ::refreshPosCallback
|
||||
val payload = payloads[0]
|
||||
when {
|
||||
payload is String && payload == "foo" -> onBindViewHolder(holder, pos)
|
||||
|
|
|
@ -213,13 +213,14 @@ import java.util.*
|
|||
if (nameEpisodeMap.isNotEmpty()) {
|
||||
for (e in nameEpisodeMap.values) {
|
||||
upsertBlk(e) {
|
||||
e.media?.setfileUrlOrNull(null)
|
||||
it.media?.setfileUrlOrNull(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
loadItems()
|
||||
Logd(TAG, "Episodes reconsiled: ${nameEpisodeMap.size}\nFiles removed: ${filesRemoved.size}")
|
||||
withContext(Dispatchers.Main) {
|
||||
Toast.makeText(requireContext().applicationContext, "Episodes reconsiled: ${nameEpisodeMap.size}\nFiles removed: ${filesRemoved.size}", Toast.LENGTH_LONG).show()
|
||||
Toast.makeText(requireContext(), "Episodes reconsiled: ${nameEpisodeMap.size}\nFiles removed: ${filesRemoved.size}", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -573,7 +573,7 @@ import java.util.concurrent.Semaphore
|
|||
val episodes_ = feed_.episodes.filter { feed_.episodeFilter.matches(it) }
|
||||
episodes.addAll(episodes_)
|
||||
} else episodes.addAll(feed_.episodes)
|
||||
val sortOrder = fromCode(feed_.preferences?.sortOrderCode ?: 0)
|
||||
val sortOrder = feed_.sortOrder
|
||||
if (sortOrder != null) getPermutor(sortOrder).reorder(episodes)
|
||||
if (onInit) {
|
||||
var hasNonMediaItems = false
|
||||
|
|
|
@ -18,6 +18,8 @@ import ac.mdiq.podcini.ui.utils.TransitionEffect
|
|||
import ac.mdiq.podcini.util.IntentUtils
|
||||
import ac.mdiq.podcini.util.Logd
|
||||
import ac.mdiq.podcini.util.ShareUtils
|
||||
import ac.mdiq.podcini.util.event.EventFlow
|
||||
import ac.mdiq.podcini.util.event.FlowEvent
|
||||
import android.R.string
|
||||
import android.app.Activity
|
||||
import android.content.*
|
||||
|
@ -46,10 +48,8 @@ import com.google.android.material.appbar.CollapsingToolbarLayout
|
|||
import com.google.android.material.appbar.MaterialToolbar
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import org.apache.commons.lang3.StringUtils
|
||||
import java.lang.ref.WeakReference
|
||||
import java.util.*
|
||||
|
@ -64,8 +64,6 @@ class FeedInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
|||
private val binding get() = _binding!!
|
||||
|
||||
private lateinit var feed: Feed
|
||||
// private lateinit var imgvCover: ImageView
|
||||
// private lateinit var imgvBackground: ImageView
|
||||
private lateinit var toolbar: MaterialToolbar
|
||||
|
||||
private val addLocalFolderLauncher = registerForActivityResult<Uri?, Uri>(AddLocalFolder()) {
|
||||
|
@ -131,6 +129,18 @@ class FeedInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
|||
return binding.root
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
Logd(TAG, "onStart() called")
|
||||
super.onStart()
|
||||
procFlowEvents()
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
Logd(TAG, "onStop() called")
|
||||
super.onStop()
|
||||
cancelFlowEvents()
|
||||
}
|
||||
|
||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||
super.onConfigurationChanged(newConfig)
|
||||
val horizontalSpacing = resources.getDimension(R.dimen.additional_horizontal_spacing).toInt()
|
||||
|
@ -269,6 +279,23 @@ class FeedInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
|||
}
|
||||
}
|
||||
|
||||
private var eventSink: Job? = null
|
||||
private fun cancelFlowEvents() {
|
||||
eventSink?.cancel()
|
||||
eventSink = null
|
||||
}
|
||||
private fun procFlowEvents() {
|
||||
if (eventSink == null) eventSink = lifecycleScope.launch {
|
||||
EventFlow.events.collectLatest { event ->
|
||||
Logd(TAG, "Received event: ${event.TAG}")
|
||||
when (event) {
|
||||
is FlowEvent.FeedPrefsChangeEvent -> feed = event.feed
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class AddLocalFolder : ActivityResultContracts.OpenDocumentTree() {
|
||||
override fun createIntent(context: Context, input: Uri?): Intent {
|
||||
return super.createIntent(context, input).addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
|
|
|
@ -9,18 +9,16 @@ import ac.mdiq.podcini.net.feed.FeedUpdateManager.runOnce
|
|||
import ac.mdiq.podcini.preferences.UserPreferences.isEnableAutodownload
|
||||
import ac.mdiq.podcini.storage.database.Feeds.persistFeedPreferences
|
||||
import ac.mdiq.podcini.storage.database.RealmDB.realm
|
||||
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.FeedPreferences.AutoDeleteAction
|
||||
import ac.mdiq.podcini.storage.model.FeedPreferences.Companion.FeedAutoDeleteOptions
|
||||
import ac.mdiq.podcini.ui.adapter.SimpleChipAdapter
|
||||
import ac.mdiq.podcini.ui.compose.CustomTheme
|
||||
import ac.mdiq.podcini.ui.compose.Spinner
|
||||
import ac.mdiq.podcini.ui.dialog.AuthenticationDialog
|
||||
import ac.mdiq.podcini.ui.dialog.TagSettingsDialog
|
||||
import ac.mdiq.podcini.ui.utils.ItemOffsetDecoration
|
||||
import ac.mdiq.podcini.ui.compose.CustomTheme
|
||||
import ac.mdiq.podcini.ui.compose.Spinner
|
||||
import ac.mdiq.podcini.util.Logd
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
|
@ -40,11 +38,6 @@ import androidx.compose.foundation.layout.*
|
|||
import androidx.compose.foundation.selection.selectable
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.AddCircle
|
||||
import androidx.compose.material.icons.filled.CheckCircle
|
||||
import androidx.compose.material.icons.outlined.AccountCircle
|
||||
import androidx.compose.material.icons.outlined.CheckCircle
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
|
@ -70,6 +63,7 @@ class FeedSettingsFragment : Fragment() {
|
|||
private val binding get() = _binding!!
|
||||
private var feed: Feed? = null
|
||||
private var autoDeleteSummaryResId by mutableIntStateOf(R.string.global_default)
|
||||
private var curPrefQueue by mutableStateOf(feed?.preferences?.queueTextExt ?: "Default")
|
||||
private var autoDeletePolicy = "global"
|
||||
private var queues: List<PlayQueue>? = null
|
||||
|
||||
|
@ -102,6 +96,7 @@ class FeedSettingsFragment : Fragment() {
|
|||
var checked by remember { mutableStateOf(feed?.preferences?.keepUpdated ?: true) }
|
||||
Switch(
|
||||
checked = checked,
|
||||
modifier = Modifier.height(24.dp),
|
||||
onCheckedChange = {
|
||||
checked = it
|
||||
feed = upsertBlk(feed!!) {
|
||||
|
@ -117,6 +112,35 @@ class FeedSettingsFragment : Fragment() {
|
|||
)
|
||||
}
|
||||
Column {
|
||||
Row(Modifier.fillMaxWidth()) {
|
||||
Icon(ImageVector.vectorResource(id = R.drawable.ic_stream), "", tint = textColor)
|
||||
Spacer(modifier = Modifier.width(20.dp))
|
||||
Text(
|
||||
text = stringResource(R.string.pref_stream_over_download_title),
|
||||
style = MaterialTheme.typography.h6,
|
||||
color = textColor
|
||||
)
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
var checked by remember { mutableStateOf(feed?.preferences?.prefStreamOverDownload ?: false) }
|
||||
Switch(
|
||||
checked = checked,
|
||||
modifier = Modifier.height(24.dp),
|
||||
onCheckedChange = {
|
||||
checked = it
|
||||
feed = upsertBlk(feed!!) {
|
||||
it.preferences?.prefStreamOverDownload = checked
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = stringResource(R.string.pref_stream_over_download_sum),
|
||||
style = MaterialTheme.typography.body2,
|
||||
color = textColor
|
||||
)
|
||||
}
|
||||
Column {
|
||||
curPrefQueue = feed?.preferences?.queueTextExt ?: "Default"
|
||||
Row(Modifier.fillMaxWidth()) {
|
||||
Icon(ImageVector.vectorResource(id = R.drawable.ic_playlist_play), "", tint = textColor)
|
||||
Spacer(modifier = Modifier.width(20.dp))
|
||||
|
@ -125,27 +149,21 @@ class FeedSettingsFragment : Fragment() {
|
|||
style = MaterialTheme.typography.h6,
|
||||
color = textColor,
|
||||
modifier = Modifier.clickable(onClick = {
|
||||
// queues = realm.query(PlayQueue::class).find()
|
||||
val selectedOption = when (feed?.preferences?.queueId) {
|
||||
null, 0L -> "Default"
|
||||
-1L -> "Active"
|
||||
else -> "Custom"
|
||||
}
|
||||
val composeView = ComposeView(requireContext()).apply {
|
||||
setContent {
|
||||
val showDialog = remember { mutableStateOf(true) }
|
||||
CustomTheme(requireContext()) {
|
||||
SetAssociatedQueue(showDialog.value, selectedOption = selectedOption, onDismissRequest = { showDialog.value = false })
|
||||
}
|
||||
val selectedOption = feed?.preferences?.queueText ?: "Default"
|
||||
val composeView = ComposeView(requireContext()).apply {
|
||||
setContent {
|
||||
val showDialog = remember { mutableStateOf(true) }
|
||||
CustomTheme(requireContext()) {
|
||||
SetAssociatedQueue(showDialog.value, selectedOption = selectedOption, onDismissRequest = { showDialog.value = false })
|
||||
}
|
||||
}
|
||||
(view as? ViewGroup)?.addView(composeView)
|
||||
|
||||
}
|
||||
(view as? ViewGroup)?.addView(composeView)
|
||||
})
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = stringResource(R.string.pref_feed_associated_queue_sum),
|
||||
text = curPrefQueue + " : " + stringResource(R.string.pref_feed_associated_queue_sum),
|
||||
style = MaterialTheme.typography.body2,
|
||||
color = textColor
|
||||
)
|
||||
|
@ -159,16 +177,15 @@ class FeedSettingsFragment : Fragment() {
|
|||
style = MaterialTheme.typography.h6,
|
||||
color = textColor,
|
||||
modifier = Modifier.clickable(onClick = {
|
||||
val composeView = ComposeView(requireContext()).apply {
|
||||
setContent {
|
||||
val showDialog = remember { mutableStateOf(true) }
|
||||
CustomTheme(requireContext()) {
|
||||
AutoDeleteDialog(showDialog.value, onDismissRequest = { showDialog.value = false })
|
||||
}
|
||||
val composeView = ComposeView(requireContext()).apply {
|
||||
setContent {
|
||||
val showDialog = remember { mutableStateOf(true) }
|
||||
CustomTheme(requireContext()) {
|
||||
AutoDeleteDialog(showDialog.value, onDismissRequest = { showDialog.value = false })
|
||||
}
|
||||
}
|
||||
(view as? ViewGroup)?.addView(composeView)
|
||||
|
||||
}
|
||||
(view as? ViewGroup)?.addView(composeView)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
@ -231,35 +248,29 @@ class FeedSettingsFragment : Fragment() {
|
|||
) {
|
||||
Column {
|
||||
FeedAutoDeleteOptions.forEach { text ->
|
||||
Row(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.selectable(
|
||||
selected = (text == selectedOption),
|
||||
onClick = {
|
||||
Logd(TAG, "row clicked: $text $selectedOption")
|
||||
if (text != selectedOption) {
|
||||
onOptionSelected(text)
|
||||
val action_ = when (text) {
|
||||
"global" -> AutoDeleteAction.GLOBAL
|
||||
"always" -> AutoDeleteAction.ALWAYS
|
||||
"never" -> AutoDeleteAction.NEVER
|
||||
else -> AutoDeleteAction.GLOBAL
|
||||
}
|
||||
feed = upsertBlk(feed!!) {
|
||||
it.preferences?.autoDeleteAction = action_
|
||||
}
|
||||
getAutoDeletePolicy()
|
||||
onDismissRequest()
|
||||
}
|
||||
}
|
||||
)
|
||||
.padding(horizontal = 16.dp),
|
||||
Row(Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
RadioButton(
|
||||
selected = (text == selectedOption),
|
||||
onClick = { }
|
||||
Checkbox(checked = (text == selectedOption),
|
||||
onCheckedChange = { isChecked ->
|
||||
Logd(TAG, "row clicked: $text $selectedOption")
|
||||
if (text != selectedOption) {
|
||||
onOptionSelected(text)
|
||||
val action_ = when (text) {
|
||||
"global" -> AutoDeleteAction.GLOBAL
|
||||
"always" -> AutoDeleteAction.ALWAYS
|
||||
"never" -> AutoDeleteAction.NEVER
|
||||
else -> AutoDeleteAction.GLOBAL
|
||||
}
|
||||
feed = upsertBlk(feed!!) {
|
||||
it.preferences?.autoDeleteAction = action_
|
||||
}
|
||||
getAutoDeletePolicy()
|
||||
onDismissRequest()
|
||||
}
|
||||
}
|
||||
)
|
||||
Text(
|
||||
text = text,
|
||||
|
@ -281,37 +292,36 @@ class FeedSettingsFragment : Fragment() {
|
|||
var selected by remember {mutableStateOf(selectedOption)}
|
||||
if (showDialog) {
|
||||
Dialog(onDismissRequest = { onDismissRequest() }) {
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.wrapContentSize(align = Alignment.Center)
|
||||
.padding(16.dp),
|
||||
Card(modifier = Modifier
|
||||
.wrapContentSize(align = Alignment.Center)
|
||||
.padding(16.dp),
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
Column(modifier = Modifier.padding(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
queueSettingOptions.forEach { option ->
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
Row(modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Checkbox(
|
||||
checked = option == selected,
|
||||
Checkbox(checked = option == selected,
|
||||
onCheckedChange = { isChecked ->
|
||||
selected = option
|
||||
if (isChecked) Logd(TAG, "$option is checked")
|
||||
when (selected) {
|
||||
"Default" -> {
|
||||
feed = upsertBlk(feed!!) {
|
||||
it.preferences?.queueId = 0L
|
||||
}
|
||||
feed = upsertBlk(feed!!) { it.preferences?.queueId = 0L }
|
||||
curPrefQueue = selected
|
||||
onDismissRequest()
|
||||
}
|
||||
"Active" -> {
|
||||
feed = upsertBlk(feed!!) {
|
||||
it.preferences?.queueId = 1L
|
||||
}
|
||||
feed = upsertBlk(feed!!) { it.preferences?.queueId = -1L }
|
||||
curPrefQueue = selected
|
||||
onDismissRequest()
|
||||
}
|
||||
"None" -> {
|
||||
feed = upsertBlk(feed!!) { it.preferences?.queueId = -2L }
|
||||
curPrefQueue = selected
|
||||
onDismissRequest()
|
||||
}
|
||||
"Custom" -> {}
|
||||
|
@ -326,9 +336,8 @@ class FeedSettingsFragment : Fragment() {
|
|||
Spinner(items = queues!!.map { it.name }, selectedItem = feed?.preferences?.queue?.name ?: "Default") { name ->
|
||||
Logd(TAG, "Queue selected: $name")
|
||||
val q = queues?.firstOrNull { it.name == name }
|
||||
feed = upsertBlk(feed!!) {
|
||||
it.preferences?.queue = q
|
||||
}
|
||||
feed = upsertBlk(feed!!) { it.preferences?.queue = q }
|
||||
if (q != null) curPrefQueue = q.name
|
||||
onDismissRequest()
|
||||
}
|
||||
}
|
||||
|
@ -753,7 +762,7 @@ class FeedSettingsFragment : Fragment() {
|
|||
private val TAG: String = FeedSettingsFragment::class.simpleName ?: "Anonymous"
|
||||
private const val EXTRA_FEED_ID = "ac.mdiq.podcini.extra.feedId"
|
||||
|
||||
val queueSettingOptions = listOf("Default", "Active", "Custom")
|
||||
val queueSettingOptions = listOf("Default", "Active", "None", "Custom")
|
||||
|
||||
fun newInstance(feed: Feed): FeedSettingsFragment {
|
||||
val fragment = FeedSettingsFragment()
|
||||
|
|
|
@ -85,7 +85,7 @@ import java.util.*
|
|||
private val binding get() = _binding!!
|
||||
|
||||
private lateinit var recyclerView: EpisodesRecyclerView
|
||||
private lateinit var emptyView: EmptyViewHandler
|
||||
private lateinit var emptyViewHandler: EmptyViewHandler
|
||||
private lateinit var toolbar: MaterialToolbar
|
||||
private lateinit var swipeActions: SwipeActions
|
||||
private lateinit var speedDialView: SpeedDialView
|
||||
|
@ -179,12 +179,12 @@ import java.util.*
|
|||
adapter?.setOnSelectModeListener(this)
|
||||
recyclerView.adapter = adapter
|
||||
|
||||
emptyView = EmptyViewHandler(requireContext())
|
||||
emptyView.attachToRecyclerView(recyclerView)
|
||||
emptyView.setIcon(R.drawable.ic_playlist_play)
|
||||
emptyView.setTitle(R.string.no_items_header_label)
|
||||
emptyView.setMessage(R.string.no_items_label)
|
||||
emptyView.updateAdapter(adapter)
|
||||
emptyViewHandler = EmptyViewHandler(requireContext())
|
||||
emptyViewHandler.attachToRecyclerView(recyclerView)
|
||||
emptyViewHandler.setIcon(R.drawable.ic_playlist_play)
|
||||
emptyViewHandler.setTitle(R.string.no_items_header_label)
|
||||
emptyViewHandler.setMessage(R.string.no_items_label)
|
||||
emptyViewHandler.updateAdapter(adapter)
|
||||
|
||||
val multiSelectDial = MultiSelectSpeedDialBinding.bind(binding.root)
|
||||
speedDialView = multiSelectDial.fabSD
|
||||
|
@ -290,8 +290,12 @@ import java.util.*
|
|||
}
|
||||
when (event.action) {
|
||||
FlowEvent.QueueEvent.Action.ADDED -> {
|
||||
if (event.episodes.isNotEmpty() && !curQueue.isInQueue(event.episodes[0])) queueItems.add(event.position, event.episodes[0])
|
||||
adapter?.notifyItemInserted(event.position)
|
||||
if (event.episodes.isNotEmpty() && !curQueue.isInQueue(event.episodes[0])) {
|
||||
val pos = queueItems.size
|
||||
queueItems.addAll(event.episodes)
|
||||
adapter?.notifyItemRangeInserted(pos, queueItems.size)
|
||||
adapter?.notifyItemRangeChanged(pos, event.episodes.size);
|
||||
}
|
||||
}
|
||||
FlowEvent.QueueEvent.Action.SET_QUEUE, FlowEvent.QueueEvent.Action.SORTED -> {
|
||||
queueItems.clear()
|
||||
|
@ -303,9 +307,12 @@ import java.util.*
|
|||
for (e in event.episodes) {
|
||||
val pos: Int = EpisodeUtil.indexOfItemWithId(queueItems, e.id)
|
||||
if (pos >= 0) {
|
||||
Logd(TAG, "removing episode $pos ${queueItems[pos]} $e")
|
||||
Logd(TAG, "removing episode $pos ${queueItems[pos].title} $e")
|
||||
val holder = recyclerView.findViewHolderForLayoutPosition(pos) as? EpisodeViewHolder
|
||||
holder?.unbind()
|
||||
queueItems.removeAt(pos)
|
||||
adapter?.notifyItemRemoved(pos)
|
||||
adapter?.notifyItemRangeChanged(pos, adapter!!.getItemCount()-pos);
|
||||
} else {
|
||||
Log.e(TAG, "Trying to remove item non-existent from queue ${e.id} ${e.title}")
|
||||
continue
|
||||
|
@ -637,18 +644,16 @@ import java.util.*
|
|||
private fun loadCurQueue(restoreScrollPosition: Boolean) {
|
||||
if (!loadItemsRunning) {
|
||||
loadItemsRunning = true
|
||||
adapter?.updateItems(mutableListOf())
|
||||
// adapter?.updateItems(mutableListOf())
|
||||
Logd(TAG, "loadCurQueue() called ${curQueue.name}")
|
||||
while (curQueue.name.isEmpty()) runBlocking { delay(100) }
|
||||
if (queueItems.isEmpty()) emptyView.hide()
|
||||
if (queueItems.isNotEmpty()) emptyViewHandler.hide()
|
||||
queueItems.clear()
|
||||
if (showBin) {
|
||||
queueItems.addAll(realm.query(Episode::class, "id IN $0", curQueue.idsBinList)
|
||||
.find().sortedByDescending { curQueue.idsBinList.indexOf(it.id) })
|
||||
} else {
|
||||
curQueue.episodes.clear()
|
||||
// curQueue.episodes.addAll(realm.query(Episode::class, "id IN $0", curQueue.episodeIds)
|
||||
// .find().sortedBy { curQueue.episodeIds.indexOf(it.id) })
|
||||
queueItems.addAll(curQueue.episodes)
|
||||
}
|
||||
Logd(TAG, "loadCurQueue() curQueue.episodes: ${curQueue.episodes.size}")
|
||||
|
|
|
@ -388,7 +388,7 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener, Selec
|
|||
val dir = 1 - 2*feedOrderDir // get from 0, 1 to 1, -1
|
||||
val comparator: Comparator<Feed> = when (feedOrder) {
|
||||
FeedSortOrder.UNPLAYED_NEW_OLD.index -> {
|
||||
val queryString = "feedId == $0 AND (playState == ${Episode.NEW} OR playState == ${Episode.UNPLAYED})"
|
||||
val queryString = "feedId == $0 AND (playState == ${Episode.PlayState.NEW.code} OR playState == ${Episode.PlayState.UNPLAYED.code})"
|
||||
val counterMap: MutableMap<Long, Long> = mutableMapOf()
|
||||
for (f in feedList_) {
|
||||
val c = realm.query(Episode::class).query(queryString, f.id).count().find()
|
||||
|
@ -410,7 +410,7 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener, Selec
|
|||
}
|
||||
}
|
||||
FeedSortOrder.MOST_PLAYED.index -> {
|
||||
val queryString = "feedId == $0 AND playState == ${Episode.PLAYED}"
|
||||
val queryString = "feedId == $0 AND playState == ${Episode.PlayState.PLAYED.code}"
|
||||
val counterMap: MutableMap<Long, Long> = mutableMapOf()
|
||||
for (f in feedList_) {
|
||||
val c = realm.query(Episode::class).query(queryString, f.id).count().find()
|
||||
|
@ -444,7 +444,7 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener, Selec
|
|||
}
|
||||
FeedSortOrder.LAST_UPDATED_UNPLAYED_NEW_OLD.index -> {
|
||||
val queryString =
|
||||
"feedId == $0 AND (playState == ${Episode.NEW} OR playState == ${Episode.UNPLAYED}) SORT(pubDate DESC)"
|
||||
"feedId == $0 AND (playState == ${Episode.PlayState.NEW.code} OR playState == ${Episode.PlayState.UNPLAYED.code}) SORT(pubDate DESC)"
|
||||
val counterMap: MutableMap<Long, Long> = mutableMapOf()
|
||||
for (f in feedList_) {
|
||||
val d = realm.query(Episode::class).query(queryString, f.id).first().find()?.pubDate ?: 0L
|
||||
|
@ -466,7 +466,7 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener, Selec
|
|||
}
|
||||
FeedSortOrder.MOST_DOWNLOADED_UNPLAYED.index -> {
|
||||
val queryString =
|
||||
"feedId == $0 AND (playState == ${Episode.NEW} OR playState == ${Episode.UNPLAYED}) AND media.downloaded == true"
|
||||
"feedId == $0 AND (playState == ${Episode.PlayState.NEW.code} OR playState == ${Episode.PlayState.UNPLAYED.code}) AND media.downloaded == true"
|
||||
val counterMap: MutableMap<Long, Long> = mutableMapOf()
|
||||
for (f in feedList_) {
|
||||
val c = realm.query(Episode::class).query(queryString, f.id).count().find()
|
||||
|
@ -477,7 +477,7 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener, Selec
|
|||
}
|
||||
// doing FEED_ORDER_NEW
|
||||
else -> {
|
||||
val queryString = "feedId == $0 AND playState == ${Episode.NEW}"
|
||||
val queryString = "feedId == $0 AND playState == ${Episode.PlayState.NEW.code}"
|
||||
val counterMap: MutableMap<Long, Long> = mutableMapOf()
|
||||
for (f in feedList_) {
|
||||
val c = realm.query(Episode::class).query(queryString, f.id).count().find()
|
||||
|
@ -738,37 +738,33 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener, Selec
|
|||
var selected by remember {mutableStateOf("")}
|
||||
if (showDialog) {
|
||||
Dialog(onDismissRequest = { onDismissRequest() }) {
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.wrapContentSize(align = Alignment.Center)
|
||||
.padding(16.dp),
|
||||
Card(modifier = Modifier
|
||||
.wrapContentSize(align = Alignment.Center)
|
||||
.padding(16.dp),
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
Column(modifier = Modifier.padding(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
queueSettingOptions.forEach { option ->
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
Row(modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Checkbox(
|
||||
checked = option == selected,
|
||||
Checkbox(checked = option == selected,
|
||||
onCheckedChange = { isChecked ->
|
||||
selected = option
|
||||
if (isChecked) Logd(TAG, "$option is checked")
|
||||
when (selected) {
|
||||
"Default" -> {
|
||||
saveFeedPreferences { it: FeedPreferences ->
|
||||
it.queueId = 0L
|
||||
}
|
||||
saveFeedPreferences { it: FeedPreferences -> it.queueId = 0L }
|
||||
onDismissRequest()
|
||||
}
|
||||
"Active" -> {
|
||||
saveFeedPreferences { it: FeedPreferences ->
|
||||
it.queueId = -1L
|
||||
}
|
||||
saveFeedPreferences { it: FeedPreferences -> it.queueId = -1L }
|
||||
onDismissRequest()
|
||||
}
|
||||
"None" -> {
|
||||
saveFeedPreferences { it: FeedPreferences -> it.queueId = -2L }
|
||||
onDismissRequest()
|
||||
}
|
||||
"Custom" -> {}
|
||||
|
@ -784,9 +780,7 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener, Selec
|
|||
Logd(TAG, "Queue selected: $name")
|
||||
val q = queues.firstOrNull { it.name == name }
|
||||
if (q != null) {
|
||||
saveFeedPreferences { it: FeedPreferences ->
|
||||
it.queueId = q.id
|
||||
}
|
||||
saveFeedPreferences { it: FeedPreferences -> it.queueId = q.id }
|
||||
onDismissRequest()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -447,7 +447,7 @@ class StatisticsFragment : Fragment() {
|
|||
else {
|
||||
// progress import does not include playedDuration
|
||||
if (includeMarkedAsPlayed) {
|
||||
if (m.playbackCompletionTime > 0 || m.episodeOrFetch()?.playState == Episode.PLAYED)
|
||||
if (m.playbackCompletionTime > 0 || m.episodeOrFetch()?.playState == Episode.PlayState.PLAYED.code)
|
||||
dur += m.duration
|
||||
else if (m.position > 0) dur += m.position
|
||||
} else dur += m.position
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package ac.mdiq.podcini.ui.utils
|
||||
|
||||
import ac.mdiq.podcini.R
|
||||
import ac.mdiq.podcini.storage.database.Episodes.deleteMediaOfEpisode
|
||||
import ac.mdiq.podcini.storage.database.Episodes.deleteEpisodeMedia
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
|
@ -13,7 +13,7 @@ object LocalDeleteModal {
|
|||
val localItems: MutableList<Episode> = mutableListOf()
|
||||
for (item in items) {
|
||||
if (item.feed?.isLocalFeed == true) localItems.add(item)
|
||||
else deleteMediaOfEpisode(context, item)
|
||||
else deleteEpisodeMedia(context, item)
|
||||
}
|
||||
|
||||
if (localItems.isNotEmpty()) {
|
||||
|
@ -22,7 +22,7 @@ object LocalDeleteModal {
|
|||
.setMessage(R.string.delete_local_feed_warning_body)
|
||||
.setPositiveButton(R.string.delete_label) { dialog: DialogInterface?, which: Int ->
|
||||
for (item in localItems) {
|
||||
deleteMediaOfEpisode(context, item)
|
||||
deleteEpisodeMedia(context, item)
|
||||
}
|
||||
}
|
||||
.setNegativeButton(R.string.cancel_label, null)
|
||||
|
|
|
@ -7,9 +7,8 @@ import ac.mdiq.podcini.playback.base.InTheatre
|
|||
import ac.mdiq.podcini.playback.base.InTheatre.curQueue
|
||||
import ac.mdiq.podcini.preferences.UserPreferences
|
||||
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.Companion.BUILDING
|
||||
import ac.mdiq.podcini.storage.model.Episode.PlayState
|
||||
import ac.mdiq.podcini.storage.model.EpisodeMedia
|
||||
import ac.mdiq.podcini.storage.model.Feed.Companion.PREFIX_GENERATIVE_COVER
|
||||
import ac.mdiq.podcini.storage.model.MediaType
|
||||
|
@ -99,7 +98,6 @@ open class EpisodeViewHolder(private val activity: MainActivity, parent: ViewGro
|
|||
}
|
||||
|
||||
fun bind(item: Episode) {
|
||||
// Logd(TAG, "in bind: ${item.title} ${item.isFavorite} ${item.isPlayed()}")
|
||||
if (episodeMonitor == null) {
|
||||
val item_ = realm.query(Episode::class).query("id == ${item.id}").first()
|
||||
episodeMonitor = CoroutineScope(Dispatchers.Default).launch {
|
||||
|
@ -116,6 +114,7 @@ open class EpisodeViewHolder(private val activity: MainActivity, parent: ViewGro
|
|||
else -> {}
|
||||
}
|
||||
}
|
||||
// return
|
||||
}
|
||||
}
|
||||
if (mediaMonitor == null) {
|
||||
|
@ -174,7 +173,7 @@ open class EpisodeViewHolder(private val activity: MainActivity, parent: ViewGro
|
|||
when {
|
||||
item.media != null -> bind(item.media!!)
|
||||
// for generating TTS files for episode without media
|
||||
item.playState == BUILDING -> {
|
||||
item.playState == PlayState.BUILDING.code -> {
|
||||
secondaryActionProgress.setPercentage(actionButton!!.processing, item)
|
||||
secondaryActionProgress.setIndeterminate(false)
|
||||
}
|
||||
|
|
|
@ -512,7 +512,7 @@
|
|||
<string name="pref_feed_skip">Auto skip</string>
|
||||
<string name="pref_feed_skip_sum">Skip introductions and ending credits.</string>
|
||||
<string name="pref_feed_associated_queue">Associated queue</string>
|
||||
<string name="pref_feed_associated_queue_sum">Set the queue to which epiosdes in the feed will added by default.</string>
|
||||
<string name="pref_feed_associated_queue_sum">The queue to which epiosdes in the feed will added by default</string>
|
||||
<string name="pref_feed_skip_ending">Skip last</string>
|
||||
<string name="pref_feed_skip_intro">Skip first</string>
|
||||
<string name="pref_feed_skip_ending_toast">Skipped last %d seconds</string>
|
||||
|
|
|
@ -8,7 +8,7 @@ internal object FeedItemMother {
|
|||
|
||||
@JvmStatic
|
||||
fun anyFeedItemWithImage(): Episode {
|
||||
val item = Episode(0, "Item", "Item", "url", Date(), Episode.PLAYED, FeedMother.anyFeed())
|
||||
val item = Episode(0, "Item", "Item", "url", Date(), Episode.PlayState.PLAYED.code, FeedMother.anyFeed())
|
||||
item.imageUrl = (IMAGE_URL)
|
||||
return item
|
||||
}
|
||||
|
|
|
@ -87,7 +87,7 @@ open class DbCleanupTests {
|
|||
val items: MutableList<Episode> = ArrayList()
|
||||
feed.episodes.addAll(items)
|
||||
val files: MutableList<File> = ArrayList()
|
||||
populateItems(numItems, feed, items, files, Episode.PLAYED, false, false)
|
||||
populateItems(numItems, feed, items, files, Episode.PlayState.PLAYED.code, false, false)
|
||||
|
||||
performAutoCleanup(context)
|
||||
for (i in files.indices) {
|
||||
|
@ -104,7 +104,7 @@ open class DbCleanupTests {
|
|||
for (i in 0 until numItems) {
|
||||
val itemDate = Date((numItems - i).toLong())
|
||||
var playbackCompletionDate: Date? = null
|
||||
if (itemState == Episode.PLAYED) {
|
||||
if (itemState == Episode.PlayState.PLAYED.code) {
|
||||
playbackCompletionDate = itemDate
|
||||
}
|
||||
val item = Episode(0, "title", "id$i", "link", itemDate, itemState, feed)
|
||||
|
@ -140,7 +140,7 @@ open class DbCleanupTests {
|
|||
val items: MutableList<Episode> = ArrayList()
|
||||
feed.episodes.addAll(items)
|
||||
val files: MutableList<File> = ArrayList()
|
||||
populateItems(numItems, feed, items, files, Episode.UNPLAYED, false, false)
|
||||
populateItems(numItems, feed, items, files, Episode.PlayState.UNPLAYED.code, false, false)
|
||||
|
||||
performAutoCleanup(context)
|
||||
for (file in files) {
|
||||
|
@ -157,7 +157,7 @@ open class DbCleanupTests {
|
|||
val items: MutableList<Episode> = ArrayList()
|
||||
feed.episodes.addAll(items)
|
||||
val files: MutableList<File> = ArrayList()
|
||||
populateItems(numItems, feed, items, files, Episode.PLAYED, true, false)
|
||||
populateItems(numItems, feed, items, files, Episode.PlayState.PLAYED.code, true, false)
|
||||
|
||||
performAutoCleanup(context)
|
||||
for (file in files) {
|
||||
|
@ -198,7 +198,7 @@ open class DbCleanupTests {
|
|||
val items: MutableList<Episode> = ArrayList()
|
||||
feed.episodes.addAll(items)
|
||||
val files: MutableList<File> = ArrayList()
|
||||
populateItems(numItems, feed, items, files, Episode.PLAYED, false, true)
|
||||
populateItems(numItems, feed, items, files, Episode.PlayState.PLAYED.code, false, true)
|
||||
|
||||
performAutoCleanup(context)
|
||||
for (file in files) {
|
||||
|
|
|
@ -81,7 +81,7 @@ class DbNullCleanupAlgorithmTest {
|
|||
feed.episodes.addAll(items)
|
||||
val files: MutableList<File> = ArrayList()
|
||||
for (i in 0 until numItems) {
|
||||
val item = Episode(0, "title", "id$i", "link", Date(), Episode.PLAYED, feed)
|
||||
val item = Episode(0, "title", "id$i", "link", Date(), Episode.PlayState.PLAYED.code, feed)
|
||||
|
||||
val f = File(destFolder, "file $i")
|
||||
Assert.assertTrue(f.createNewFile())
|
||||
|
|
|
@ -33,7 +33,7 @@ class DbPlayQueueCleanupAlgorithmTest : DbCleanupTests() {
|
|||
val items: MutableList<Episode> = ArrayList()
|
||||
feed.episodes.addAll(items)
|
||||
val files: MutableList<File> = ArrayList()
|
||||
populateItems(numItems, feed, items, files, Episode.UNPLAYED, false, false)
|
||||
populateItems(numItems, feed, items, files, Episode.PlayState.UNPLAYED.code, false, false)
|
||||
|
||||
performAutoCleanup(context)
|
||||
for (i in files.indices) {
|
||||
|
|
|
@ -61,7 +61,7 @@ class DbTasksTest {
|
|||
feed.episodes.clear()
|
||||
for (i in 0 until numItems) {
|
||||
feed.episodes.add(Episode(0, "item $i", "id $i", "link $i",
|
||||
Date(), Episode.UNPLAYED, feed))
|
||||
Date(), Episode.PlayState.UNPLAYED.code, feed))
|
||||
}
|
||||
val newFeed = updateFeed(context, feed, false)
|
||||
|
||||
|
@ -97,7 +97,7 @@ class DbTasksTest {
|
|||
feed.episodes.clear()
|
||||
for (i in 0 until numItemsOld) {
|
||||
feed.episodes.add(Episode(0, "item $i", "id $i", "link $i",
|
||||
Date(i.toLong()), Episode.PLAYED, feed))
|
||||
Date(i.toLong()), Episode.PlayState.PLAYED.code, feed))
|
||||
}
|
||||
// val adapter = getInstance()
|
||||
// adapter.open()
|
||||
|
@ -117,7 +117,7 @@ class DbTasksTest {
|
|||
|
||||
for (i in numItemsOld until numItemsNew + numItemsOld) {
|
||||
feed.episodes.add(0, Episode(0, "item $i", "id $i", "link $i",
|
||||
Date(i.toLong()), Episode.UNPLAYED, feed))
|
||||
Date(i.toLong()), Episode.PlayState.UNPLAYED.code, feed))
|
||||
}
|
||||
|
||||
val newFeed = updateFeed(context, feed, false)
|
||||
|
@ -134,7 +134,7 @@ class DbTasksTest {
|
|||
@Test
|
||||
fun testUpdateFeedMediaUrlResetState() {
|
||||
val feed = Feed("url", null, "title")
|
||||
val item = Episode(0, "item", "id", "link", Date(), Episode.PLAYED, feed)
|
||||
val item = Episode(0, "item", "id", "link", Date(), Episode.PlayState.PLAYED.code, feed)
|
||||
feed.episodes.add(item)
|
||||
|
||||
// val adapter = getInstance()
|
||||
|
@ -166,7 +166,7 @@ class DbTasksTest {
|
|||
feed.episodes.clear()
|
||||
for (i in 0..9) {
|
||||
feed.episodes.add(
|
||||
Episode(0, "item $i", "id $i", "link $i", Date(i.toLong()), Episode.PLAYED, feed))
|
||||
Episode(0, "item $i", "id $i", "link $i", Date(i.toLong()), Episode.PlayState.PLAYED.code, feed))
|
||||
}
|
||||
// val adapter = getInstance()
|
||||
// adapter.open()
|
||||
|
@ -188,7 +188,7 @@ class DbTasksTest {
|
|||
feed.episodes.clear()
|
||||
for (i in 0..9) {
|
||||
val item =
|
||||
Episode(0, "item $i", "id $i", "link $i", Date(i.toLong()), Episode.PLAYED, feed)
|
||||
Episode(0, "item $i", "id $i", "link $i", Date(i.toLong()), Episode.PlayState.PLAYED.code, feed)
|
||||
val media = EpisodeMedia(item, "download url $i", 123, "media/mp3")
|
||||
item.setMedia(media)
|
||||
feed.episodes.add(item)
|
||||
|
@ -244,7 +244,7 @@ class DbTasksTest {
|
|||
val items: MutableList<Episode> = ArrayList(numFeedItems)
|
||||
for (i in 1..numFeedItems) {
|
||||
val item = Episode(0, "item $i of $title", "id$title$i", "link",
|
||||
Date(), Episode.UNPLAYED, feed)
|
||||
Date(), Episode.PlayState.UNPLAYED.code, feed)
|
||||
items.add(item)
|
||||
}
|
||||
feed.episodes.addAll(items)
|
||||
|
|
|
@ -6,7 +6,7 @@ import ac.mdiq.podcini.playback.base.InTheatre.curQueue
|
|||
import ac.mdiq.podcini.preferences.UserPreferences
|
||||
import ac.mdiq.podcini.storage.database.Episodes.addToHistory
|
||||
import ac.mdiq.podcini.storage.database.Episodes.deleteEpisodes
|
||||
import ac.mdiq.podcini.storage.database.Episodes.deleteMediaOfEpisode
|
||||
import ac.mdiq.podcini.storage.database.Episodes.deleteEpisodeMedia
|
||||
import ac.mdiq.podcini.storage.database.Episodes.getEpisode
|
||||
import ac.mdiq.podcini.storage.database.Episodes.getEpisodeMedia
|
||||
import ac.mdiq.podcini.storage.database.Episodes.persistEpisode
|
||||
|
@ -97,7 +97,7 @@ class DbWriterTest {
|
|||
val feed = Feed("url", null, "title")
|
||||
val items: MutableList<Episode> = ArrayList()
|
||||
feed.episodes.addAll(items)
|
||||
val item = Episode(0, "Item", "Item", "url", Date(), Episode.PLAYED, feed)
|
||||
val item = Episode(0, "Item", "Item", "url", Date(), Episode.PlayState.PLAYED.code, feed)
|
||||
items.add(item)
|
||||
val media = EpisodeMedia(0, item, duration, 1, 1, "mime_type",
|
||||
"dummy path", "download_url", true, null, 0, 0)
|
||||
|
@ -138,7 +138,7 @@ class DbWriterTest {
|
|||
val feed = Feed("url", null, "title")
|
||||
val items: MutableList<Episode> = ArrayList()
|
||||
feed.episodes.addAll(items)
|
||||
val item = Episode(0, "Item", "Item", "url", Date(), Episode.PLAYED, feed)
|
||||
val item = Episode(0, "Item", "Item", "url", Date(), Episode.PlayState.PLAYED.code, feed)
|
||||
|
||||
var media: EpisodeMedia? = EpisodeMedia(0, item, 1, 1, 1, "mime_type",
|
||||
dest.absolutePath, "download_url", true, null, 0, 0)
|
||||
|
@ -154,7 +154,7 @@ class DbWriterTest {
|
|||
Assert.assertTrue(item.id != 0L)
|
||||
|
||||
runBlocking {
|
||||
val job = deleteMediaOfEpisode(context, item)
|
||||
val job = deleteEpisodeMedia(context, item)
|
||||
withTimeout(TIMEOUT*1000) { job.join() }
|
||||
}
|
||||
media = getEpisodeMedia(media.id)
|
||||
|
@ -176,7 +176,7 @@ class DbWriterTest {
|
|||
val feed = Feed("url", null, "title")
|
||||
val items: MutableList<Episode> = ArrayList()
|
||||
feed.episodes.addAll(items)
|
||||
val item = Episode(0, "Item", "Item", "url", Date(), Episode.UNPLAYED, feed)
|
||||
val item = Episode(0, "Item", "Item", "url", Date(), Episode.PlayState.UNPLAYED.code, feed)
|
||||
|
||||
var media: EpisodeMedia? = EpisodeMedia(0, item, 1, 1, 1, "mime_type",
|
||||
dest.absolutePath, "download_url", true, null, 0, 0)
|
||||
|
@ -196,7 +196,7 @@ class DbWriterTest {
|
|||
queue = curQueue.episodes
|
||||
Assert.assertTrue(queue.size != 0)
|
||||
|
||||
deleteMediaOfEpisode(context, item)
|
||||
deleteEpisodeMedia(context, item)
|
||||
Awaitility.await().timeout(2, TimeUnit.SECONDS).until { !dest.exists() }
|
||||
media = getEpisodeMedia(media.id)
|
||||
Assert.assertNotNull(media)
|
||||
|
@ -218,7 +218,7 @@ class DbWriterTest {
|
|||
val itemFiles: MutableList<File> = ArrayList()
|
||||
// create items with downloaded media files
|
||||
for (i in 0..9) {
|
||||
val item = Episode(0, "Item $i", "Item$i", "url", Date(), Episode.PLAYED, feed)
|
||||
val item = Episode(0, "Item $i", "Item$i", "url", Date(), Episode.PlayState.PLAYED.code, feed)
|
||||
feed.episodes.add(item)
|
||||
|
||||
val enc = File(destFolder, "file $i")
|
||||
|
@ -308,7 +308,7 @@ class DbWriterTest {
|
|||
|
||||
// create items
|
||||
for (i in 0..9) {
|
||||
val item = Episode(0, "Item $i", "Item$i", "url", Date(), Episode.PLAYED, feed)
|
||||
val item = Episode(0, "Item $i", "Item$i", "url", Date(), Episode.PlayState.PLAYED.code, feed)
|
||||
feed.episodes.add(item)
|
||||
}
|
||||
|
||||
|
@ -352,7 +352,7 @@ class DbWriterTest {
|
|||
|
||||
// create items with downloaded media files
|
||||
for (i in 0..9) {
|
||||
val item = Episode(0, "Item $i", "Item$i", "url", Date(), Episode.PLAYED, feed)
|
||||
val item = Episode(0, "Item $i", "Item$i", "url", Date(), Episode.PlayState.PLAYED.code, feed)
|
||||
feed.episodes.add(item)
|
||||
val enc = File(destFolder, "file $i")
|
||||
val media = EpisodeMedia(0, item, 1, 1, 1, "mime_type",
|
||||
|
@ -415,7 +415,7 @@ class DbWriterTest {
|
|||
|
||||
// create items with downloaded media files
|
||||
for (i in 0..9) {
|
||||
val item = Episode(0, "Item $i", "Item$i", "url", Date(), Episode.PLAYED, feed)
|
||||
val item = Episode(0, "Item $i", "Item$i", "url", Date(), Episode.PlayState.PLAYED.code, feed)
|
||||
feed.episodes.add(item)
|
||||
val enc = File(destFolder, "file $i")
|
||||
val media = EpisodeMedia(0, item, 1, 1, 1, "mime_type",
|
||||
|
@ -463,7 +463,7 @@ class DbWriterTest {
|
|||
|
||||
// create items
|
||||
for (i in 0..9) {
|
||||
val item = Episode(0, "Item $i", "Item$i", "url", Date(), Episode.PLAYED, feed)
|
||||
val item = Episode(0, "Item $i", "Item$i", "url", Date(), Episode.PlayState.PLAYED.code, feed)
|
||||
item.setMedia(EpisodeMedia(item, "", 0, ""))
|
||||
feed.episodes.add(item)
|
||||
}
|
||||
|
@ -494,7 +494,7 @@ class DbWriterTest {
|
|||
private fun playbackHistorySetup(playbackCompletionDate: Date?): EpisodeMedia {
|
||||
val feed = Feed("url", null, "title")
|
||||
feed.episodes.clear()
|
||||
val item = Episode(0, "title", "id", "link", Date(), Episode.PLAYED, feed)
|
||||
val item = Episode(0, "title", "id", "link", Date(), Episode.PlayState.PLAYED.code, feed)
|
||||
val media = EpisodeMedia(0, item, 10, 0, 1, "mime", null,
|
||||
"url", false, playbackCompletionDate, 0, 0)
|
||||
feed.episodes.add(item)
|
||||
|
@ -547,7 +547,7 @@ class DbWriterTest {
|
|||
val feed = Feed("url", null, "title")
|
||||
feed.episodes.clear()
|
||||
for (i in 0 until numItems) {
|
||||
val item = Episode(0, "title $i", "id $i", "link $i", Date(), Episode.PLAYED, feed)
|
||||
val item = Episode(0, "title $i", "id $i", "link $i", Date(), Episode.PlayState.PLAYED.code, feed)
|
||||
item.setMedia(EpisodeMedia(item, "", 0, ""))
|
||||
feed.episodes.add(item)
|
||||
}
|
||||
|
@ -576,7 +576,7 @@ class DbWriterTest {
|
|||
fun testAddQueueItemSingleItem() {
|
||||
val feed = Feed("url", null, "title")
|
||||
feed.episodes.clear()
|
||||
val item = Episode(0, "title", "id", "link", Date(), Episode.PLAYED, feed)
|
||||
val item = Episode(0, "title", "id", "link", Date(), Episode.PlayState.PLAYED.code, feed)
|
||||
item.setMedia(EpisodeMedia(item, "", 0, ""))
|
||||
feed.episodes.add(item)
|
||||
|
||||
|
@ -605,7 +605,7 @@ class DbWriterTest {
|
|||
fun testAddQueueItemSingleItemAlreadyInQueue() {
|
||||
val feed = Feed("url", null, "title")
|
||||
feed.episodes.clear()
|
||||
val item = Episode(0, "title", "id", "link", Date(), Episode.PLAYED, feed)
|
||||
val item = Episode(0, "title", "id", "link", Date(), Episode.PlayState.PLAYED.code, feed)
|
||||
item.setMedia(EpisodeMedia(item, "", 0, ""))
|
||||
feed.episodes.add(item)
|
||||
|
||||
|
@ -766,7 +766,7 @@ class DbWriterTest {
|
|||
feed.episodes.clear()
|
||||
for (i in 0 until numItems) {
|
||||
val item = Episode(0, "title $i", "id $i", "link $i",
|
||||
Date(), Episode.PLAYED, feed)
|
||||
Date(), Episode.PlayState.PLAYED.code, feed)
|
||||
item.setMedia(EpisodeMedia(item, "", 0, ""))
|
||||
feed.episodes.add(item)
|
||||
}
|
||||
|
@ -817,7 +817,7 @@ class DbWriterTest {
|
|||
val feed = Feed("url", null, "title")
|
||||
feed.episodes.clear()
|
||||
for (i in 0 until numItems) {
|
||||
val item = Episode(0, "title $i", "id $i", "link $i", Date(), Episode.NEW, feed)
|
||||
val item = Episode(0, "title $i", "id $i", "link $i", Date(), Episode.PlayState.NEW.code, feed)
|
||||
item.setMedia(EpisodeMedia(item, "", 0, ""))
|
||||
feed.episodes.add(item)
|
||||
}
|
||||
|
@ -848,7 +848,7 @@ class DbWriterTest {
|
|||
val feed = Feed("url", null, "title")
|
||||
feed.episodes.clear()
|
||||
for (i in 0 until numItems) {
|
||||
val item = Episode(0, "title $i", "id $i", "link $i", Date(), Episode.PLAYED, feed)
|
||||
val item = Episode(0, "title $i", "id $i", "link $i", Date(), Episode.PlayState.PLAYED.code, feed)
|
||||
item.setMedia(EpisodeMedia(item, "", 0, ""))
|
||||
feed.episodes.add(item)
|
||||
}
|
||||
|
|
|
@ -57,7 +57,7 @@ class EpisodeDuplicateGuesserTest {
|
|||
private fun item(guid: String, title: String, downloadUrl: String,
|
||||
date: Long, duration: Long, mime: String
|
||||
): Episode {
|
||||
val item = Episode(0, title, guid, "link", Date(date), Episode.PLAYED, null)
|
||||
val item = Episode(0, title, guid, "link", Date(date), Episode.PlayState.PLAYED.code, null)
|
||||
val media = EpisodeMedia(item, downloadUrl, duration, mime)
|
||||
item.setMedia(media)
|
||||
return item
|
||||
|
|
|
@ -29,7 +29,7 @@ class ExceptFavoriteCleanupAlgorithmTest : DbCleanupTests() {
|
|||
val items: MutableList<Episode> = ArrayList()
|
||||
feed.episodes.addAll(items)
|
||||
val files: MutableList<File> = ArrayList()
|
||||
populateItems(numberOfItems, feed, items, files, Episode.UNPLAYED, false, false)
|
||||
populateItems(numberOfItems, feed, items, files, Episode.PlayState.UNPLAYED.code, false, false)
|
||||
|
||||
performAutoCleanup(context)
|
||||
for (i in files.indices) {
|
||||
|
@ -48,7 +48,7 @@ class ExceptFavoriteCleanupAlgorithmTest : DbCleanupTests() {
|
|||
val items: MutableList<Episode> = ArrayList()
|
||||
feed.episodes.addAll(items)
|
||||
val files: MutableList<File> = ArrayList()
|
||||
populateItems(numberOfItems, feed, items, files, Episode.UNPLAYED, true, false)
|
||||
populateItems(numberOfItems, feed, items, files, Episode.PlayState.UNPLAYED.code, true, false)
|
||||
|
||||
performAutoCleanup(context)
|
||||
for (i in files.indices) {
|
||||
|
@ -67,7 +67,7 @@ class ExceptFavoriteCleanupAlgorithmTest : DbCleanupTests() {
|
|||
val items: MutableList<Episode> = ArrayList()
|
||||
feed.episodes.addAll(items)
|
||||
val files: MutableList<File> = ArrayList()
|
||||
populateItems(numberOfItems, feed, items, files, Episode.UNPLAYED, false, true)
|
||||
populateItems(numberOfItems, feed, items, files, Episode.PlayState.UNPLAYED.code, false, true)
|
||||
|
||||
performAutoCleanup(context)
|
||||
for (i in files.indices) {
|
||||
|
|
|
@ -57,7 +57,7 @@ object ItemEnqueuePositionCalculatorTest {
|
|||
|
||||
fun createFeedItem(id: Long): Episode {
|
||||
val item = Episode(id, "Item$id", "ItemId$id", "url",
|
||||
Date(), Episode.PLAYED, anyFeed())
|
||||
Date(), Episode.PlayState.PLAYED.code, anyFeed())
|
||||
val media = EpisodeMedia(item, "http://download.url.net/$id", 1234567, "audio/mpeg")
|
||||
media.id = item.id
|
||||
item.setMedia(media)
|
||||
|
|
17
changelog.md
17
changelog.md
|
@ -1,3 +1,20 @@
|
|||
# 6.3.4
|
||||
|
||||
* fixed mis-behavior of setting associated queue to Active in FeedSettings
|
||||
* items on dialog for "Auto delete episodes" are changed to checkbox
|
||||
* playState Int variables have been put into enum PlayState
|
||||
* corrected an error in incomplete reconsile
|
||||
* fixed the nasty mis-behavior in Queues view when removing episodes
|
||||
* updated feed in FeedInfo view when feed preferences change
|
||||
* enhanced feed setting UI, added display of current queue preference
|
||||
* added "prefer streaming over download" in feed setting. ruling along the global setting, streaming is preferred when either one is set to true
|
||||
* added None in associated queue setting of any feed
|
||||
* if set, episodes in the feed are not automatically added to any queue, but are used as a natural queue for getting the next episode to play
|
||||
* the next episode is determined in such a way:
|
||||
* if the currently playing episode had been (manually) added to the active queue, then it's the next in queue
|
||||
* else if "prefer streaming" is set, it's the next unplayed episode in the feed episodes list based on the current sort order
|
||||
* else it's the next downloaded unplayed episode
|
||||
|
||||
# 6.3.3
|
||||
|
||||
* fixed crash when setting as Played/Unplayed in EpisodeInfo view
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
|
||||
Version 6.3.4 brings several changes:
|
||||
|
||||
* fixed mis-behavior of setting associated queue to Active in FeedSettings
|
||||
* items on dialog for "Auto delete episodes" are changed to checkbox
|
||||
* playState Int variables have been put into enum PlayState
|
||||
* corrected an error in incomplete reconsile
|
||||
* fixed the nasty mis-behavior in Queues view when removing episodes
|
||||
* updated feed in FeedInfo view when feed preferences change
|
||||
* enhanced feed setting UI, added display of current queue preference
|
||||
* added "prefer streaming over download" in feed setting. ruling along the global setting, streaming is preferred when either one is set to true
|
||||
* added None in associated queue setting of any feed
|
||||
* if set, episodes in the feed are not automatically added to any queue, but are used as a natural queue for getting the next episode to play
|
||||
* the next episode is determined in such a way:
|
||||
* if the currently playing episode had been (manually) added to the active queue, then it's the next in queue
|
||||
* else if "prefer streaming" is set, it's the next unplayed episode in the feed episodes list based on the current sort order
|
||||
* else it's the next downloaded unplayed episode
|
Loading…
Reference in New Issue