From 5fe3f049d208f18c3a680b632f6b292194dda46c Mon Sep 17 00:00:00 2001 From: Xilin Jia <6257601+XilinJia@users.noreply.github.com> Date: Sun, 4 Aug 2024 13:04:10 +0100 Subject: [PATCH] 6.3.4 commit --- README.md | 11 +- app/build.gradle | 4 +- .../service/playback/MediaPlayerBaseTest.kt | 2 +- .../service/playback/TaskManagerTest.kt | 2 +- .../kotlin/ac/test/podcini/ui/UITestUtils.kt | 2 +- .../service/DownloadServiceInterfaceImpl.kt | 6 +- .../mdiq/podcini/net/feed/LocalFeedUpdater.kt | 2 +- .../playback/service/LocalMediaPlayer.kt | 5 +- .../playback/service/PlaybackService.kt | 52 +++--- .../storage/algorithms/AutoCleanups.kt | 8 +- .../mdiq/podcini/storage/database/Episodes.kt | 20 +-- .../ac/mdiq/podcini/storage/database/Feeds.kt | 7 - .../podcini/storage/database/LogsAndStats.kt | 2 +- .../mdiq/podcini/storage/database/Queues.kt | 11 +- .../mdiq/podcini/storage/database/RealmDB.kt | 2 +- .../ac/mdiq/podcini/storage/model/Episode.kt | 26 +-- .../ac/mdiq/podcini/storage/model/Feed.kt | 24 ++- .../podcini/storage/model/FeedPreferences.kt | 26 ++- .../ui/actions/EpisodeMultiSelectHandler.kt | 4 +- .../actionbutton/EpisodeActionButton.kt | 3 +- .../actionbutton/MarkAsPlayedActionButton.kt | 2 +- .../actions/actionbutton/PlayActionButton.kt | 8 +- .../actions/menuhandler/EpisodeMenuHandler.kt | 6 +- .../RemoveFromQueueSwipeAction.kt | 2 +- .../TogglePlaybackStateSwipeAction.kt | 10 +- .../podcini/ui/adapter/EpisodesAdapter.kt | 4 +- .../podcini/ui/fragment/DownloadsFragment.kt | 5 +- .../ui/fragment/FeedEpisodesFragment.kt | 2 +- .../podcini/ui/fragment/FeedInfoFragment.kt | 39 +++- .../ui/fragment/FeedSettingsFragment.kt | 167 +++++++++--------- .../podcini/ui/fragment/QueuesFragment.kt | 33 ++-- .../ui/fragment/SubscriptionsFragment.kt | 42 ++--- .../ui/statistics/StatisticsFragment.kt | 2 +- .../mdiq/podcini/ui/utils/LocalDeleteModal.kt | 6 +- .../mdiq/podcini/ui/view/EpisodeViewHolder.kt | 7 +- app/src/main/res/values/strings.xml | 2 +- .../ac/mdiq/podcini/feed/FeedItemMother.kt | 2 +- .../ac/mdiq/podcini/storage/DbCleanupTests.kt | 10 +- .../storage/DbNullCleanupAlgorithmTest.kt | 2 +- .../DbPlayQueueCleanupAlgorithmTest.kt | 2 +- .../ac/mdiq/podcini/storage/DbTasksTest.kt | 14 +- .../ac/mdiq/podcini/storage/DbWriterTest.kt | 36 ++-- .../storage/EpisodeDuplicateGuesserTest.kt | 2 +- .../ExceptFavoriteCleanupAlgorithmTest.kt | 6 +- .../ItemEnqueuePositionCalculatorTest.kt | 2 +- changelog.md | 17 ++ .../android/en-US/changelogs/3020228.txt | 17 ++ 47 files changed, 382 insertions(+), 284 deletions(-) create mode 100644 fastlane/metadata/android/en-US/changelogs/3020228.txt diff --git a/README.md b/README.md index 5f42a5e7..b103cf44 100644 --- a/README.md +++ b/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 diff --git a/app/build.gradle b/app/build.gradle index 3bbb89c3..508357db 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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 = "" diff --git a/app/src/androidTest/kotlin/ac/test/podcini/service/playback/MediaPlayerBaseTest.kt b/app/src/androidTest/kotlin/ac/test/podcini/service/playback/MediaPlayerBaseTest.kt index b9d18899..36641a4c 100644 --- a/app/src/androidTest/kotlin/ac/test/podcini/service/playback/MediaPlayerBaseTest.kt +++ b/app/src/androidTest/kotlin/ac/test/podcini/service/playback/MediaPlayerBaseTest.kt @@ -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) diff --git a/app/src/androidTest/kotlin/ac/test/podcini/service/playback/TaskManagerTest.kt b/app/src/androidTest/kotlin/ac/test/podcini/service/playback/TaskManagerTest.kt index 2c610a91..1c9a2168 100644 --- a/app/src/androidTest/kotlin/ac/test/podcini/service/playback/TaskManagerTest.kt +++ b/app/src/androidTest/kotlin/ac/test/podcini/service/playback/TaskManagerTest.kt @@ -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() diff --git a/app/src/androidTest/kotlin/ac/test/podcini/ui/UITestUtils.kt b/app/src/androidTest/kotlin/ac/test/podcini/ui/UITestUtils.kt index 47b5143b..85b2a35c 100644 --- a/app/src/androidTest/kotlin/ac/test/podcini/ui/UITestUtils.kt +++ b/app/src/androidTest/kotlin/ac/test/podcini/ui/UITestUtils.kt @@ -113,7 +113,7 @@ class UITestUtils(private val context: Context) { val items: MutableList = 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) { diff --git a/app/src/main/kotlin/ac/mdiq/podcini/net/download/service/DownloadServiceInterfaceImpl.kt b/app/src/main/kotlin/ac/mdiq/podcini/net/download/service/DownloadServiceInterfaceImpl.kt index 07efe123..da1967d1 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/net/download/service/DownloadServiceInterfaceImpl.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/net/download/service/DownloadServiceInterfaceImpl.kt @@ -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> = 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()) diff --git a/app/src/main/kotlin/ac/mdiq/podcini/net/feed/LocalFeedUpdater.kt b/app/src/main/kotlin/ac/mdiq/podcini/net/feed/LocalFeedUpdater.kt index 86d84ad9..eafd450a 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/net/feed/LocalFeedUpdater.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/net/feed/LocalFeedUpdater.kt @@ -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 diff --git a/app/src/main/kotlin/ac/mdiq/podcini/playback/service/LocalMediaPlayer.kt b/app/src/main/kotlin/ac/mdiq/podcini/playback/service/LocalMediaPlayer.kt index 3fe682d3..7abe6984 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/playback/service/LocalMediaPlayer.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/playback/service/LocalMediaPlayer.kt @@ -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() diff --git a/app/src/main/kotlin/ac/mdiq/podcini/playback/service/PlaybackService.kt b/app/src/main/kotlin/ac/mdiq/podcini/playback/service/PlaybackService.kt index 3bd862eb..49da27f2 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/playback/service/PlaybackService.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/playback/service/PlaybackService.kt @@ -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) } diff --git a/app/src/main/kotlin/ac/mdiq/podcini/storage/algorithms/AutoCleanups.kt b/app/src/main/kotlin/ac/mdiq/podcini/storage/algorithms/AutoCleanups.kt index 05322189..c5029cd8 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/storage/algorithms/AutoCleanups.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/storage/algorithms/AutoCleanups.kt @@ -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) { diff --git a/app/src/main/kotlin/ac/mdiq/podcini/storage/database/Episodes.kt b/app/src/main/kotlin/ac/mdiq/podcini/storage/database/Episodes.kt index 6abeb463..858084ce 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/storage/database/Episodes.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/storage/database/Episodes.kt @@ -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) : Job { return runOnIOScope { val removedFromQueue: MutableList = 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) } } \ No newline at end of file diff --git a/app/src/main/kotlin/ac/mdiq/podcini/storage/database/Feeds.kt b/app/src/main/kotlin/ac/mdiq/podcini/storage/database/Feeds.kt index e3bb1d71..4289f2f2 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/storage/database/Feeds.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/storage/database/Feeds.kt @@ -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 { diff --git a/app/src/main/kotlin/ac/mdiq/podcini/storage/database/LogsAndStats.kt b/app/src/main/kotlin/ac/mdiq/podcini/storage/database/LogsAndStats.kt index 55321feb..39fcddd5 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/storage/database/LogsAndStats.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/storage/database/LogsAndStats.kt @@ -56,7 +56,7 @@ object LogsAndStats { feedTotalTime += m.duration if (m.lastPlayedTime in timeFilterFrom.. 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 } diff --git a/app/src/main/kotlin/ac/mdiq/podcini/storage/database/Queues.kt b/app/src/main/kotlin/ac/mdiq/podcini/storage/database/Queues.kt index 4f6c89e6..b98727e0 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/storage/database/Queues.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/storage/database/Queues.kt @@ -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 = ArrayList() val indicesToRemove: MutableList = 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)) diff --git a/app/src/main/kotlin/ac/mdiq/podcini/storage/database/RealmDB.kt b/app/src/main/kotlin/ac/mdiq/podcini/storage/database/RealmDB.kt index 6c35488b..f9e1c32e 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/storage/database/RealmDB.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/storage/database/RealmDB.kt @@ -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) diff --git a/app/src/main/kotlin/ac/mdiq/podcini/storage/model/Episode.kt b/app/src/main/kotlin/ac/mdiq/podcini/storage/model/Episode.kt index 969a5abe..cacadb4a 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/storage/model/Episode.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/storage/model/Episode.kt @@ -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 } } diff --git a/app/src/main/kotlin/ac/mdiq/podcini/storage/model/Feed.kt b/app/src/main/kotlin/ac/mdiq/podcini/storage/model/Feed.kt index 70e7b59d..a5326dd0 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/storage/model/Feed.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/storage/model/Feed.kt @@ -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 { + 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" diff --git a/app/src/main/kotlin/ac/mdiq/podcini/storage/model/FeedPreferences.kt b/app/src/main/kotlin/ac/mdiq/podcini/storage/model/FeedPreferences.kt index f86a12ab..5eef2f89 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/storage/model/FeedPreferences.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/storage/model/FeedPreferences.kt @@ -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 diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/actions/EpisodeMultiSelectHandler.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/actions/EpisodeMultiSelectHandler.kt index 26e4b7a4..b96ff73d 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/actions/EpisodeMultiSelectHandler.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/actions/EpisodeMultiSelectHandler.kt @@ -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 -> { diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/actions/actionbutton/EpisodeActionButton.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/actions/actionbutton/EpisodeActionButton.kt index cd721ac2..2770af84 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/actions/actionbutton/EpisodeActionButton.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/actions/actionbutton/EpisodeActionButton.kt @@ -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) } } diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/actions/actionbutton/MarkAsPlayedActionButton.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/actions/actionbutton/MarkAsPlayedActionButton.kt index 96f574e3..3ff23d0f 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/actions/actionbutton/MarkAsPlayedActionButton.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/actions/actionbutton/MarkAsPlayedActionButton.kt @@ -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) } } diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/actions/actionbutton/PlayActionButton.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/actions/actionbutton/PlayActionButton.kt index c3fdb542..8390e8a2 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/actions/actionbutton/PlayActionButton.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/actions/actionbutton/PlayActionButton.kt @@ -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 } diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/actions/menuhandler/EpisodeMenuHandler.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/actions/menuhandler/EpisodeMenuHandler.kt index d23ed23b..601790f8 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/actions/menuhandler/EpisodeMenuHandler.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/actions/menuhandler/EpisodeMenuHandler.kt @@ -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() diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/actions/swipeactions/RemoveFromQueueSwipeAction.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/actions/swipeactions/RemoveFromQueueSwipeAction.kt index 45565d36..cd4d5482 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/actions/swipeactions/RemoveFromQueueSwipeAction.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/actions/swipeactions/RemoveFromQueueSwipeAction.kt @@ -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() diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/actions/swipeactions/TogglePlaybackStateSwipeAction.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/actions/swipeactions/TogglePlaybackStateSwipeAction.kt index 6883e879..3fef1a42 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/actions/swipeactions/TogglePlaybackStateSwipeAction.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/actions/swipeactions/TogglePlaybackStateSwipeAction.kt @@ -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 } } diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/adapter/EpisodesAdapter.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/adapter/EpisodesAdapter.kt index 70370ecb..1251e650 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/adapter/EpisodesAdapter.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/adapter/EpisodesAdapter.kt @@ -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) { +// 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) diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/DownloadsFragment.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/DownloadsFragment.kt index 54e53934..8ad101a9 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/DownloadsFragment.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/DownloadsFragment.kt @@ -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() } } } diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/FeedEpisodesFragment.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/FeedEpisodesFragment.kt index 6c15f0e9..30df2f30 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/FeedEpisodesFragment.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/FeedEpisodesFragment.kt @@ -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 diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/FeedInfoFragment.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/FeedInfoFragment.kt index 8f8dda29..f8dd5c55 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/FeedInfoFragment.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/FeedInfoFragment.kt @@ -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(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) diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/FeedSettingsFragment.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/FeedSettingsFragment.kt index 44483e30..175c53cd 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/FeedSettingsFragment.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/FeedSettingsFragment.kt @@ -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? = 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() diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/QueuesFragment.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/QueuesFragment.kt index 043d3518..c44dfd66 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/QueuesFragment.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/QueuesFragment.kt @@ -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}") diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/SubscriptionsFragment.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/SubscriptionsFragment.kt index d70a2625..95dd68a1 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/SubscriptionsFragment.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/SubscriptionsFragment.kt @@ -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 = 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 = 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 = 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 = 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 = 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 = 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() } } diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/statistics/StatisticsFragment.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/statistics/StatisticsFragment.kt index a6b2c873..f655281b 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/statistics/StatisticsFragment.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/statistics/StatisticsFragment.kt @@ -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 diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/utils/LocalDeleteModal.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/utils/LocalDeleteModal.kt index 5479265f..5c6c6ecf 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/utils/LocalDeleteModal.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/utils/LocalDeleteModal.kt @@ -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 = 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) diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/view/EpisodeViewHolder.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/view/EpisodeViewHolder.kt index 6ca52786..0e84ae92 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/view/EpisodeViewHolder.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/view/EpisodeViewHolder.kt @@ -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) } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 120b4c91..90bf3595 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -512,7 +512,7 @@ Auto skip Skip introductions and ending credits. Associated queue - Set the queue to which epiosdes in the feed will added by default. + The queue to which epiosdes in the feed will added by default Skip last Skip first Skipped last %d seconds diff --git a/app/src/test/kotlin/ac/mdiq/podcini/feed/FeedItemMother.kt b/app/src/test/kotlin/ac/mdiq/podcini/feed/FeedItemMother.kt index 9a0a2d7d..10ffe102 100644 --- a/app/src/test/kotlin/ac/mdiq/podcini/feed/FeedItemMother.kt +++ b/app/src/test/kotlin/ac/mdiq/podcini/feed/FeedItemMother.kt @@ -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 } diff --git a/app/src/test/kotlin/ac/mdiq/podcini/storage/DbCleanupTests.kt b/app/src/test/kotlin/ac/mdiq/podcini/storage/DbCleanupTests.kt index 811a8cfd..67296a9d 100644 --- a/app/src/test/kotlin/ac/mdiq/podcini/storage/DbCleanupTests.kt +++ b/app/src/test/kotlin/ac/mdiq/podcini/storage/DbCleanupTests.kt @@ -87,7 +87,7 @@ open class DbCleanupTests { val items: MutableList = ArrayList() feed.episodes.addAll(items) val files: MutableList = 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 = ArrayList() feed.episodes.addAll(items) val files: MutableList = 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 = ArrayList() feed.episodes.addAll(items) val files: MutableList = 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 = ArrayList() feed.episodes.addAll(items) val files: MutableList = 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) { diff --git a/app/src/test/kotlin/ac/mdiq/podcini/storage/DbNullCleanupAlgorithmTest.kt b/app/src/test/kotlin/ac/mdiq/podcini/storage/DbNullCleanupAlgorithmTest.kt index e7ee55ef..e90ffdc8 100644 --- a/app/src/test/kotlin/ac/mdiq/podcini/storage/DbNullCleanupAlgorithmTest.kt +++ b/app/src/test/kotlin/ac/mdiq/podcini/storage/DbNullCleanupAlgorithmTest.kt @@ -81,7 +81,7 @@ class DbNullCleanupAlgorithmTest { feed.episodes.addAll(items) val files: MutableList = 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()) diff --git a/app/src/test/kotlin/ac/mdiq/podcini/storage/DbPlayQueueCleanupAlgorithmTest.kt b/app/src/test/kotlin/ac/mdiq/podcini/storage/DbPlayQueueCleanupAlgorithmTest.kt index ccd683d5..dcdb50aa 100644 --- a/app/src/test/kotlin/ac/mdiq/podcini/storage/DbPlayQueueCleanupAlgorithmTest.kt +++ b/app/src/test/kotlin/ac/mdiq/podcini/storage/DbPlayQueueCleanupAlgorithmTest.kt @@ -33,7 +33,7 @@ class DbPlayQueueCleanupAlgorithmTest : DbCleanupTests() { val items: MutableList = ArrayList() feed.episodes.addAll(items) val files: MutableList = 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) { diff --git a/app/src/test/kotlin/ac/mdiq/podcini/storage/DbTasksTest.kt b/app/src/test/kotlin/ac/mdiq/podcini/storage/DbTasksTest.kt index fa8254e5..9915895d 100644 --- a/app/src/test/kotlin/ac/mdiq/podcini/storage/DbTasksTest.kt +++ b/app/src/test/kotlin/ac/mdiq/podcini/storage/DbTasksTest.kt @@ -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 = 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) diff --git a/app/src/test/kotlin/ac/mdiq/podcini/storage/DbWriterTest.kt b/app/src/test/kotlin/ac/mdiq/podcini/storage/DbWriterTest.kt index 492c2839..aac3f39b 100644 --- a/app/src/test/kotlin/ac/mdiq/podcini/storage/DbWriterTest.kt +++ b/app/src/test/kotlin/ac/mdiq/podcini/storage/DbWriterTest.kt @@ -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 = 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 = 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 = 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 = 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) } diff --git a/app/src/test/kotlin/ac/mdiq/podcini/storage/EpisodeDuplicateGuesserTest.kt b/app/src/test/kotlin/ac/mdiq/podcini/storage/EpisodeDuplicateGuesserTest.kt index 2b7d8920..287bc5fd 100644 --- a/app/src/test/kotlin/ac/mdiq/podcini/storage/EpisodeDuplicateGuesserTest.kt +++ b/app/src/test/kotlin/ac/mdiq/podcini/storage/EpisodeDuplicateGuesserTest.kt @@ -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 diff --git a/app/src/test/kotlin/ac/mdiq/podcini/storage/ExceptFavoriteCleanupAlgorithmTest.kt b/app/src/test/kotlin/ac/mdiq/podcini/storage/ExceptFavoriteCleanupAlgorithmTest.kt index b3351434..d91c9837 100644 --- a/app/src/test/kotlin/ac/mdiq/podcini/storage/ExceptFavoriteCleanupAlgorithmTest.kt +++ b/app/src/test/kotlin/ac/mdiq/podcini/storage/ExceptFavoriteCleanupAlgorithmTest.kt @@ -29,7 +29,7 @@ class ExceptFavoriteCleanupAlgorithmTest : DbCleanupTests() { val items: MutableList = ArrayList() feed.episodes.addAll(items) val files: MutableList = 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 = ArrayList() feed.episodes.addAll(items) val files: MutableList = 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 = ArrayList() feed.episodes.addAll(items) val files: MutableList = 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) { diff --git a/app/src/test/kotlin/ac/mdiq/podcini/storage/ItemEnqueuePositionCalculatorTest.kt b/app/src/test/kotlin/ac/mdiq/podcini/storage/ItemEnqueuePositionCalculatorTest.kt index eb3be246..077c7b99 100644 --- a/app/src/test/kotlin/ac/mdiq/podcini/storage/ItemEnqueuePositionCalculatorTest.kt +++ b/app/src/test/kotlin/ac/mdiq/podcini/storage/ItemEnqueuePositionCalculatorTest.kt @@ -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) diff --git a/changelog.md b/changelog.md index 6e04757a..9fd23260 100644 --- a/changelog.md +++ b/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 diff --git a/fastlane/metadata/android/en-US/changelogs/3020228.txt b/fastlane/metadata/android/en-US/changelogs/3020228.txt new file mode 100644 index 00000000..05185b05 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/3020228.txt @@ -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