diff --git a/app/build.gradle b/app/build.gradle index 4bb8523b..73ac2283 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -126,8 +126,8 @@ android { buildConfig true } defaultConfig { - versionCode 3020217 - versionName "6.1.3" + versionCode 3020218 + versionName "6.1.4" applicationId "ac.mdiq.podcini.R" def commit = "" diff --git a/app/src/main/kotlin/ac/mdiq/podcini/playback/base/MediaPlayerBase.kt b/app/src/main/kotlin/ac/mdiq/podcini/playback/base/MediaPlayerBase.kt index 02ff135b..9cbaceeb 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/playback/base/MediaPlayerBase.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/playback/base/MediaPlayerBase.kt @@ -221,7 +221,7 @@ abstract class MediaPlayerBase protected constructor(protected val context: Cont * * @return a Future, just for the purpose of tracking its execution. */ - protected abstract fun endPlayback(hasEnded: Boolean, wasSkipped: Boolean, shouldContinue: Boolean, toStoppedState: Boolean) + internal abstract fun endPlayback(hasEnded: Boolean, wasSkipped: Boolean, shouldContinue: Boolean, toStoppedState: Boolean) /** * @return `true` if the WifiLock feature should be used, `false` otherwise. 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 c6a6b4e5..d54b9680 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 @@ -6,6 +6,7 @@ import ac.mdiq.podcini.net.utils.NetworkUtils.isAllowMobileStreaming import ac.mdiq.podcini.net.utils.NetworkUtils.isStreamingAllowed import ac.mdiq.podcini.playback.PlaybackServiceStarter import ac.mdiq.podcini.playback.base.InTheatre +import ac.mdiq.podcini.playback.base.InTheatre.curEpisode import ac.mdiq.podcini.playback.base.InTheatre.curMedia import ac.mdiq.podcini.playback.base.InTheatre.curQueue import ac.mdiq.podcini.playback.base.InTheatre.curState @@ -37,8 +38,8 @@ import ac.mdiq.podcini.storage.database.Episodes.shouldDeleteRemoveFromQueue import ac.mdiq.podcini.storage.database.Feeds.shouldAutoDeleteItem import ac.mdiq.podcini.storage.database.Queues.addToQueue import ac.mdiq.podcini.storage.database.Queues.removeFromQueueSync -import ac.mdiq.podcini.storage.database.RealmDB.realm import ac.mdiq.podcini.storage.database.RealmDB.runOnIOScope +import ac.mdiq.podcini.storage.database.RealmDB.unmanaged import ac.mdiq.podcini.storage.database.RealmDB.upsertBlk import ac.mdiq.podcini.storage.model.* import ac.mdiq.podcini.storage.model.CurrentState.Companion.NO_MEDIA_PLAYING @@ -81,7 +82,6 @@ import androidx.work.impl.utils.futures.SettableFuture import com.google.common.collect.ImmutableList import com.google.common.util.concurrent.Futures import com.google.common.util.concurrent.ListenableFuture -import io.realm.kotlin.ext.isManaged import kotlinx.coroutines.* import kotlinx.coroutines.flow.collectLatest import java.util.* @@ -361,8 +361,17 @@ class PlaybackService : MediaSessionService() { writeNoMediaPlaying() return null } - val nextItem = getNextInQueue(item) - if (nextItem?.media == null) { +// val nextItem = getNextInQueue(item) + if (curQueue.episodes.isEmpty()) { + Logd(TAG, "getNextInQueue queue is empty") + writeNoMediaPlaying() + return null + } + val i = curQueue.episodes.indexOf(item) + var j = 0 + if (i >= 0 && i < curQueue.episodes.size-1) j = i+1 + val nextItem = unmanaged(curQueue.episodes[j]) + if (nextItem.media == null) { Logd(TAG, "getNextInQueue nextItem: $nextItem media is null") writeNoMediaPlaying() return null @@ -384,17 +393,17 @@ class PlaybackService : MediaSessionService() { return nextItem.media } - private fun getNextInQueue(episode: Episode): Episode? { - Logd(TAG, "getNextInQueue() with: itemId ${episode.id}") - if (curQueue.episodes.isEmpty()) return null - - val i = curQueue.episodes.indexOf(episode) - var j = 0 - if (i >= 0 && i < curQueue.episodes.size-1) j = i+1 - - val itemNew = curQueue.episodes[j] - return if (itemNew.isManaged()) realm.copyFromRealm(itemNew) else itemNew - } +// private fun getNextInQueue(episode: Episode): Episode? { +// Logd(TAG, "getNextInQueue() with: itemId ${episode.id}") +// if (curQueue.episodes.isEmpty()) return null +// +// val i = curQueue.episodes.indexOf(episode) +// var j = 0 +// if (i >= 0 && i < curQueue.episodes.size-1) j = i+1 +// +// val itemNew = curQueue.episodes[j] +// return unmanaged(itemNew) +// } override fun findMedia(url: String): Playable? { val item = getEpisodeByGuidOrUrl(null, url) @@ -918,6 +927,7 @@ class PlaybackService : MediaSessionService() { EventFlow.events.collectLatest { event -> Logd(TAG, "Received event: ${event.TAG}") when (event) { + is FlowEvent.QueueEvent -> onQueueEvent(event) is FlowEvent.PlayerErrorEvent -> onPlayerError(event) is FlowEvent.BufferUpdateEvent -> onBufferUpdate(event) is FlowEvent.SleepTimerUpdatedEvent -> onSleepTimerUpdate(event) @@ -925,12 +935,53 @@ class PlaybackService : MediaSessionService() { is FlowEvent.FeedPrefsChangeEvent -> onFeedPrefsChanged(event) // is FlowEvent.SkipIntroEndingChangedEvent -> skipIntroEndingPresetChanged(event) is FlowEvent.PlayEvent -> currentitem = event.episode + is FlowEvent.EpisodeMediaEvent -> onEpisodeMediaEvent(event) else -> {} } } } } + private fun onEpisodeMediaEvent(event: FlowEvent.EpisodeMediaEvent) { + if (event.action == FlowEvent.EpisodeMediaEvent.Action.REMOVED) { + for (e in event.episodes) { + if (e.id == curEpisode?.id) { + curEpisode = unmanaged(e) + curMedia = curEpisode!!.media + mPlayer?.endPlayback(hasEnded = false, wasSkipped = true, shouldContinue = true, toStoppedState = true) + break + } + } + } + } + + private fun onQueueEvent(event: FlowEvent.QueueEvent) { + if (event.action == FlowEvent.QueueEvent.Action.REMOVED) { + for (e in event.episodes) { + if (e.id == curEpisode?.id) { + mPlayer?.endPlayback(hasEnded = false, wasSkipped = true, shouldContinue = true, toStoppedState = true) + break + } + } + } + } + + // private fun onVolumeAdaptionChanged(event: FlowEvent.VolumeAdaptionChangedEvent) { +// if (mPlayer != null) updateVolumeIfNecessary(mPlayer!!, event.feedId, event.volumeAdaptionSetting) +// } + + private fun onFeedPrefsChanged(event: FlowEvent.FeedPrefsChangeEvent) { + val item = (curMedia as? EpisodeMedia)?.episode ?: currentitem + if (item?.feed?.id == event.feed.id) { + item.feed = null +// seems no need to pause?? +// if (MediaPlayerBase.status == PlayerStatus.PLAYING) { +// mPlayer?.pause(abandonFocus = false, reinit = false) +// mPlayer?.resume() +// } + } + } + private fun onPlayerError(event: FlowEvent.PlayerErrorEvent) { if (MediaPlayerBase.status == PlayerStatus.PLAYING || MediaPlayerBase.status == PlayerStatus.FALLBACK) mPlayer!!.pause(abandonFocus = true, reinit = false) @@ -1062,22 +1113,6 @@ class PlaybackService : MediaSessionService() { } } -// private fun onVolumeAdaptionChanged(event: FlowEvent.VolumeAdaptionChangedEvent) { -// if (mPlayer != null) updateVolumeIfNecessary(mPlayer!!, event.feedId, event.volumeAdaptionSetting) -// } - - private fun onFeedPrefsChanged(event: FlowEvent.FeedPrefsChangeEvent) { - val item = (curMedia as? EpisodeMedia)?.episode ?: currentitem - if (item?.feed?.id == event.feed.id) { - item.feed = null -// seems no need to pause?? -// if (MediaPlayerBase.status == PlayerStatus.PLAYING) { -// mPlayer?.pause(abandonFocus = false, reinit = false) -// mPlayer?.resume() -// } - } - } - enum class NotificationCustomButton(val customAction: String, val commandButton: CommandButton) { SKIP( customAction = CUSTOM_COMMAND_SKIP_ACTION_ID, 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 e5c04e4d..16df02e8 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 @@ -164,7 +164,7 @@ object Episodes { val action = EpisodeAction.Builder(episode, EpisodeAction.DELETE).currentTimestamp().build() SynchronizationQueueSink.enqueueEpisodeActionIfSyncActive(context, action) } - EventFlow.postEvent(FlowEvent.EpisodeEvent.updated(episode)) + EventFlow.postEvent(FlowEvent.EpisodeMediaEvent.removed(episode)) } return episode } @@ -223,7 +223,7 @@ object Episodes { if (episode != null) { episode.media = media episode = upsert(episode) {} - EventFlow.postEvent(FlowEvent.EpisodeEvent.updated(episode)) + EventFlow.postEvent(FlowEvent.EpisodeMediaEvent.updated(episode)) } else Log.e(TAG, "persistEpisodeMedia media.episode is null") } } diff --git a/app/src/main/kotlin/ac/mdiq/podcini/storage/model/EpisodeMedia.kt b/app/src/main/kotlin/ac/mdiq/podcini/storage/model/EpisodeMedia.kt index 5697d95c..b8dd835a 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/storage/model/EpisodeMedia.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/storage/model/EpisodeMedia.kt @@ -344,7 +344,7 @@ class EpisodeMedia: EmbeddedRealmObject, Playable { hasEmbeddedPicture = false } } - upsertBlk(episode!!) {} + if (episode != null) upsertBlk(episode!!) {} } override fun equals(other: Any?): Boolean { 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 9591609a..6985ca7d 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 @@ -22,6 +22,7 @@ import android.app.Activity import android.content.DialogInterface import android.util.Log import android.view.LayoutInflater +import android.view.View import android.widget.RadioButton import androidx.annotation.PluralsRes import androidx.media3.common.util.UnstableApi @@ -118,6 +119,8 @@ class EpisodeMultiSelectHandler(private val activity: MainActivity, private val fun show() { val activity = activityRef.get() ?: return val binding = SelectQueueDialogBinding.inflate(LayoutInflater.from(activity)) + binding.removeCheckbox.visibility = View.VISIBLE + val queues = realm.query(PlayQueue::class).find() for (i in queues.indices) { val radioButton = RadioButton(activity) @@ -137,21 +140,23 @@ class EpisodeMultiSelectHandler(private val activity: MainActivity, private val .setTitle(R.string.put_in_queue_label) .setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int -> val queues = realm.query(PlayQueue::class).find() - val toRemove = mutableSetOf() - val toRemoveCur = mutableListOf() - items.forEach { e -> - if (curQueue.isInQueue(e)) toRemoveCur.add(e) - } - items.forEach { e -> - for (q in queues) { - if (q.isInQueue(e)) { - toRemove.add(e.id) - break + if (binding.removeCheckbox.isChecked) { + val toRemove = mutableSetOf() + val toRemoveCur = mutableListOf() + items.forEach { e -> + if (curQueue.isInQueue(e)) toRemoveCur.add(e) + } + items.forEach { e -> + for (q in queues) { + if (q.isInQueue(e)) { + toRemove.add(e.id) + break + } } } + if (toRemove.isNotEmpty()) runBlocking { removeFromAllQueuesQuiet(toRemove.toList()) } + if (toRemoveCur.isNotEmpty()) EventFlow.postEvent(FlowEvent.QueueEvent.removed(toRemoveCur)) } - if (toRemove.isNotEmpty()) runBlocking { removeFromAllQueuesQuiet(toRemove.toList()) } - if (toRemoveCur.isNotEmpty()) EventFlow.postEvent(FlowEvent.QueueEvent.removed(toRemoveCur)) items.forEach { e -> runBlocking { addToQueueSync(false, e, toQueue) } } 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 ca5421fe..ad5fdac5 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 @@ -63,7 +63,7 @@ class PlayActionButton(item: Episode) : EpisodeActionButton(item) { episode.media?.downloaded = false episode.media?.fileUrl = null upsertBlk(episode) {} - EventFlow.postEvent(FlowEvent.EpisodeEvent.updated(episode)) + EventFlow.postEvent(FlowEvent.EpisodeMediaEvent.removed(episode)) } EventFlow.postEvent(FlowEvent.MessageEvent(context.getString(R.string.error_file_not_found))) } diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/AudioPlayerFragment.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/AudioPlayerFragment.kt index 4b57cce8..965ea04e 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/AudioPlayerFragment.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/AudioPlayerFragment.kt @@ -279,6 +279,7 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar override fun onResume() { Logd(TAG, "onResume() isCollapsed: $isCollapsed") super.onResume() + loadMediaInfo(false) } override fun onStart() { diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/BaseEpisodesFragment.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/BaseEpisodesFragment.kt index d1e73397..fba3eacd 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/BaseEpisodesFragment.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/BaseEpisodesFragment.kt @@ -329,11 +329,30 @@ import kotlinx.coroutines.flow.collectLatest for (item in event.episodes) { val pos: Int = EpisodeUtil.indexOfItemWithId(episodes, item.id) if (pos >= 0) { - episodes.removeAt(pos) if (getFilter().matches(item)) { - episodes.add(pos, item) + episodes[pos] = item adapter.notifyItemChangedCompat(pos) - } else adapter.notifyItemRemoved(pos) + } else { + episodes.removeAt(pos) + adapter.notifyItemRemoved(pos) + } + } + } + } + + private fun onEpisodeMediaEvent(event: FlowEvent.EpisodeMediaEvent) { +// Logd(TAG, "onEventMainThread() called with ${event.TAG}") + for (item in event.episodes) { + val pos: Int = EpisodeUtil.indexOfItemWithId(episodes, item.id) + if (pos >= 0) { + episodes[pos] = unmanaged(episodes[pos]) + episodes[pos].media = item.media + if (getFilter().matches(item)) { + adapter.notifyItemChangedCompat(pos) + } else { +// episodes.removeAt(pos) +// adapter.notifyItemRemoved(pos) + } } } } @@ -387,6 +406,7 @@ import kotlinx.coroutines.flow.collectLatest is FlowEvent.FeedListEvent, is FlowEvent.EpisodePlayedEvent, is FlowEvent.PlayerSettingsEvent, is FlowEvent.FavoritesEvent -> loadItems() is FlowEvent.PlaybackPositionEvent -> onPlaybackPositionEvent(event) is FlowEvent.EpisodeEvent -> onEpisodeEvent(event) + is FlowEvent.EpisodeMediaEvent -> onEpisodeMediaEvent(event) else -> {} } } 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 9f7e2125..deb67e63 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 @@ -218,6 +218,7 @@ import java.util.* Logd(TAG, "Received event: ${event.TAG}") when (event) { is FlowEvent.EpisodeEvent -> onEpisodeEvent(event) + is FlowEvent.EpisodeMediaEvent -> onEpisodeMediaEvent(event) is FlowEvent.PlaybackPositionEvent -> onPlaybackPositionEvent(event) is FlowEvent.FavoritesEvent -> onFavoriteEvent(event) is FlowEvent.PlayerSettingsEvent -> loadItems() @@ -290,12 +291,28 @@ import java.util.* if (pos >= 0) { episodes.removeAt(pos) val media = item.media - if (media != null && media.downloaded) { - episodes.add(pos, item) -// adapter.notifyItemChangedCompat(pos) - } else { -// adapter.notifyItemRemoved(pos) - } + if (media != null && media.downloaded) episodes.add(pos, item) + } + } +// have to do this as adapter.notifyItemRemoved(pos) when pos == 0 causes crash + if (size > 0) { +// adapter.setDummyViews(0) + adapter.updateItems(episodes) + } + refreshInfoBar() + } + + private fun onEpisodeMediaEvent(event: FlowEvent.EpisodeMediaEvent) { +// Logd(TAG, "onEpisodeEvent() called with ${event.TAG}") + var i = 0 + val size: Int = event.episodes.size + while (i < size) { + val item: Episode = event.episodes[i++] + val pos = EpisodeUtil.indexOfItemWithId(episodes, item.id) + if (pos >= 0) { + episodes.removeAt(pos) + val media = item.media + if (media != null && media.downloaded) episodes.add(pos, item) } } // have to do this as adapter.notifyItemRemoved(pos) when pos == 0 causes crash 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 1bca4d0b..8f2dd585 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 @@ -335,6 +335,7 @@ import java.util.concurrent.Semaphore var i = 0 val size: Int = event.episodes.size + val poses: MutableList = mutableListOf() while (i < size) { val item = event.episodes[i++] if (item.feedId != feed!!.id) continue @@ -342,9 +343,43 @@ import java.util.concurrent.Semaphore if (pos >= 0) { Logd(TAG, "episode event: ${item.title} ${item.playState} ${item.isDownloaded}") episodes[pos] = item - adapter.notifyItemChangedCompat(pos) + poses.add(pos) } } + if (poses.size == 1) { + if (filterOutEpisode(episodes[poses[0]])) adapter.updateItems(episodes) + else adapter.notifyItemChangedCompat(poses[0]) + } else if (poses.size > 1) { + redoFilter() + adapter.notifyDataSetChanged() + } + } + + private fun onEpisodeMediaEvent(event: FlowEvent.EpisodeMediaEvent) { +// Logd(TAG, "onEpisodeEvent() called with ${event.TAG}") + if (feed == null || episodes.isEmpty()) return + + var i = 0 + val size: Int = event.episodes.size + val poses: MutableList = mutableListOf() + while (i < size) { + val item = event.episodes[i++] + if (item.feedId != feed!!.id) continue + val pos: Int = EpisodeUtil.indexOfItemWithId(episodes, item.id) + if (pos >= 0) { + Logd(TAG, "episode event: ${item.title} ${item.playState} ${item.isDownloaded}") + episodes[pos] = unmanaged(episodes[pos]) + episodes[pos].media = item.media + poses.add(pos) + } + } + if (poses.size == 1) { + if (filterOutEpisode(episodes[poses[0]])) adapter.updateItems(episodes) + else adapter.notifyItemChangedCompat(poses[0]) + } else if (poses.size > 1) { + redoFilter() + adapter.notifyDataSetChanged() + } } private fun onQueueEvent(event: FlowEvent.QueueEvent) { @@ -363,7 +398,10 @@ import java.util.concurrent.Semaphore Logd(TAG, "onPlayEvent ${event.episode.title}") if (feed != null) { val pos: Int = EpisodeUtil.indexOfItemWithId(episodes, event.episode.id) - if (pos >= 0) adapter.notifyItemChangedCompat(pos) + if (pos >= 0) { + if (filterOutEpisode(event.episode)) adapter.updateItems(episodes) + else adapter.notifyItemChangedCompat(pos) + } } } @@ -373,15 +411,11 @@ import java.util.concurrent.Semaphore val pos: Int = EpisodeUtil.indexOfItemWithId(episodes, item.id) if (pos >= 0) { Logd(TAG, "played item: ${item.title} ${item.playState}") - if (enableFilter && ((feed!!.episodeFilter.showUnplayed && item.isPlayed()) || feed!!.episodeFilter.showPlayed && !item.isPlayed())) { - episodes.removeAt(pos) - adapter.updateItems(episodes) - } else { + if (filterOutEpisode(item)) adapter.updateItems(episodes) + else { episodes[pos] = item adapter.notifyItemChangedCompat(pos) } -// episodes[pos].playState = item.playState -// adapter.notifyItemChangedCompat(pos) } } @@ -391,8 +425,8 @@ import java.util.concurrent.Semaphore if (pos >= 0) { episodes[pos] = unmanaged(episodes[pos]) episodes[pos].isFavorite = item.isFavorite -// episodes[pos] = item - adapter.notifyItemChangedCompat(pos) + if (filterOutEpisode(item)) adapter.updateItems(episodes) + else adapter.notifyItemChangedCompat(pos) } } @@ -404,7 +438,8 @@ import java.util.concurrent.Semaphore val pos: Int = EpisodeUtil.indexOfItemWithDownloadUrl(episodes, downloadUrl) if (pos >= 0) { // TODO: need a better way - adapter.notifyItemChangedCompat(pos) + if (filterOutEpisode(episodes[pos])) adapter.updateItems(episodes) + else adapter.notifyItemChangedCompat(pos) } } } @@ -445,6 +480,7 @@ import java.util.concurrent.Semaphore is FlowEvent.PlaybackPositionEvent -> onPlaybackPositionEvent(event) is FlowEvent.FeedPrefsChangeEvent -> if (feed?.id == event.feed.id) loadItems() is FlowEvent.EpisodeEvent -> onEpisodeEvent(event) + is FlowEvent.EpisodeMediaEvent -> onEpisodeMediaEvent(event) is FlowEvent.PlayerSettingsEvent -> loadItems() is FlowEvent.EpisodePlayedEvent -> onEpisodePlayedEvent(event) is FlowEvent.FeedListEvent -> if (feed != null && event.contains(feed!!)) loadItems() @@ -614,6 +650,22 @@ import java.util.concurrent.Semaphore while (loadItemsRunning) Thread.sleep(50) } + private fun filterOutEpisode(episode: Episode): Boolean { + if (enableFilter && !feed?.preferences?.filterString.isNullOrEmpty() && !feed!!.episodeFilter.matches(episode)) { + episodes.remove(episode) + return true + } + return false + } + + private fun redoFilter() { + if (enableFilter && !feed?.preferences?.filterString.isNullOrEmpty()) { + val episodes_ = episodes.toList() + episodes.clear() + episodes.addAll(episodes_.filter { feed!!.episodeFilter.matches(it) }) + } + } + @UnstableApi private fun loadItems() { if (!loadItemsRunning) { 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 e6547500..636f0c18 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 @@ -355,7 +355,7 @@ class FeedSettingsFragment : Fragment() { } } private fun setupAutoDownloadGlobalPreference() { - if (!isEnableAutodownload) { + if (!isEnableAutodownload || feedPrefs?.autoDownload != true) { val autodl = findPreference(Prefs.autoDownload.name) autodl!!.isChecked = false autodl.isEnabled = false diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/QueueFragment.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/QueueFragment.kt index 7048a367..781855bd 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/QueueFragment.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/QueueFragment.kt @@ -228,6 +228,7 @@ import java.util.* when (event) { is FlowEvent.QueueEvent -> onQueueEvent(event) is FlowEvent.EpisodeEvent -> onEpisodeEvent(event) + is FlowEvent.EpisodeMediaEvent -> onEpisodeMediaEvent(event) is FlowEvent.FavoritesEvent -> onFavoriteEvent(event) is FlowEvent.PlayEvent -> onPlayEvent(event) is FlowEvent.PlaybackPositionEvent -> onPlaybackPositionEvent(event) @@ -320,15 +321,45 @@ import java.util.* } var i = 0 val size: Int = event.episodes.size + val poses: MutableList = mutableListOf() while (i < size) { val item: Episode = event.episodes[i++] val pos: Int = EpisodeUtil.indexOfItemWithId(queueItems, item.id) if (pos >= 0) { queueItems[pos] = item - adapter?.notifyItemChangedCompat(pos) - refreshInfoBar() + poses.add(pos) } } + if (poses.isNotEmpty()) { + if (poses.size == 1) adapter?.notifyItemChangedCompat(poses[0]) + else adapter?.notifyDataSetChanged() + refreshInfoBar() + } + } + + private fun onEpisodeMediaEvent(event: FlowEvent.EpisodeMediaEvent) { +// Logd(TAG, "onEventMainThread() called with ${event.TAG}") + if (adapter == null) { + loadItems(true) + return + } + var i = 0 + val size: Int = event.episodes.size + val poses: MutableList = mutableListOf() + while (i < size) { + val item: Episode = event.episodes[i++] + val pos: Int = EpisodeUtil.indexOfItemWithId(queueItems, item.id) + if (pos >= 0) { + queueItems[pos] = unmanaged(queueItems[pos]) + queueItems[pos].media = item.media + poses.add(pos) + } + } + if (poses.isNotEmpty()) { + if (poses.size == 1) adapter?.notifyItemChangedCompat(poses[0]) + else adapter?.notifyDataSetChanged() + refreshInfoBar() + } } private fun onPlayEvent(event: FlowEvent.PlayEvent) { diff --git a/app/src/main/kotlin/ac/mdiq/podcini/util/event/FlowEvent.kt b/app/src/main/kotlin/ac/mdiq/podcini/util/event/FlowEvent.kt index b41fe3b3..9ee02b3c 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/util/event/FlowEvent.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/util/event/FlowEvent.kt @@ -7,6 +7,7 @@ import ac.mdiq.podcini.storage.model.Episode import ac.mdiq.podcini.storage.model.Playable import ac.mdiq.podcini.storage.model.EpisodeSortOrder import ac.mdiq.podcini.util.Logd +import ac.mdiq.podcini.util.event.FlowEvent.FeedListEvent.Action import android.content.Context import android.view.KeyEvent import androidx.core.util.Consumer @@ -173,6 +174,24 @@ sealed class FlowEvent { } } + data class EpisodeMediaEvent(val action: Action, val episodes: List) : FlowEvent() { + enum class Action { ADDED, REMOVED, UPDATED, ERROR, UNKNOWN } + companion object { + fun added(vararg episodes: Episode): EpisodeMediaEvent { + return EpisodeMediaEvent(Action.ADDED, listOf(*episodes)) + } + fun updated(episodes: List): EpisodeMediaEvent { + return EpisodeMediaEvent(Action.UPDATED, episodes) + } + fun updated(vararg episodes: Episode): EpisodeMediaEvent { + return EpisodeMediaEvent(Action.UPDATED, listOf(*episodes)) + } + fun removed(vararg episodes: Episode): EpisodeMediaEvent { + return EpisodeMediaEvent(Action.REMOVED, listOf(*episodes)) + } + } + } + data class FeedTagsChangedEvent(val dummy: Unit = Unit) : FlowEvent() data class FeedUpdatingEvent(val isRunning: Boolean) : FlowEvent() diff --git a/app/src/main/res/layout/select_queue_dialog.xml b/app/src/main/res/layout/select_queue_dialog.xml index 7a5b52c1..5a7d5d56 100644 --- a/app/src/main/res/layout/select_queue_dialog.xml +++ b/app/src/main/res/layout/select_queue_dialog.xml @@ -11,7 +11,15 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="30dp"> - + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 78c234be..ef7bca26 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -146,7 +146,8 @@ Newest unplayed Oldest unplayed - Put in queue + Add to queue... + Remove from other queues Nothing Never @@ -262,12 +263,12 @@ %d episode marked as unplayed. %d episodes marked as unplayed. - Add to queue + Add to active queue %d episode added to queue. %d episodes added to queue. - Remove from queue + Remove from active queue %d episode removed from queue. %d episodes removed from queue. diff --git a/changelog.md b/changelog.md index 4a6fd40f..7ae2419e 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,14 @@ +# 6.1.4 + +* fixed issue of "mark excluded episodes played" checkbox not being reflected from the setting +* in FeedSetting, fixed issue of auto-download options being enabled even if auto-download is not enabled +* "Put in queue" changed to "Add to queue..." and added checkbox for removing from other queues +* refined some handling on events +* fixed issue of deleted episodes not being correctly handled in some lists +* fixed issue of current media kept being played when removed or when removed from queue +* in FeedEpisodes view, fixed (mostly) issue of not being promptly filtered when an episode state changes +* when screen is turned back on during playback, PlayerUI is promptly updated + # 6.1.3 * added feed setting in the header of FeedInfo view diff --git a/fastlane/metadata/android/en-US/changelogs/3020218.txt b/fastlane/metadata/android/en-US/changelogs/3020218.txt new file mode 100644 index 00000000..b864fe98 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/3020218.txt @@ -0,0 +1,11 @@ + +Version 6.1.4 brings several changes: + +* fixed issue of "mark excluded episodes played" checkbox not being reflected from the setting +* in FeedSetting, fixed issue of auto-download options being enabled even if auto-download is not enabled +* "Put in queue" changed to "Add to queue..." and added checkbox for removing from other queues +* refined some handling on events +* fixed issue of deleted episodes not being correctly handled in some lists +* fixed issue of current media kept being played when removed or when removed from queue +* in FeedEpisodes view, fixed (mostly) issue of not being promptly filtered when an episode state changes +* when screen is turned back on during playback, PlayerUI is promptly updated