From 86f0cb9f97df93c1c36ad6e90544d160146d9deb Mon Sep 17 00:00:00 2001 From: Xilin Jia <6257601+XilinJia@users.noreply.github.com> Date: Sat, 14 Sep 2024 12:33:40 +0100 Subject: [PATCH] 6.6.2 commit --- app/build.gradle | 4 +- .../podcini/preferences/UserPreferences.kt | 1 + .../ac/mdiq/podcini/storage/database/Feeds.kt | 2 +- .../mdiq/podcini/storage/database/Queues.kt | 36 ++++---- .../podcini/storage/model/EpisodeFilter.kt | 14 +++- .../mdiq/podcini/storage/model/FeedFilter.kt | 2 +- .../mdiq/podcini/storage/model/PlayQueue.kt | 2 +- .../ui/actions/handler/EpisodeMenuHandler.kt | 2 +- .../handler/EpisodeMultiSelectHandler.kt | 4 +- .../podcini/ui/dialog/EpisodeFilterDialog.kt | 6 +- .../ui/fragment/AllEpisodesFragment.kt | 9 +- .../podcini/ui/fragment/DownloadsFragment.kt | 78 +++++++++++------ .../podcini/ui/fragment/HistoryFragment.kt | 5 +- .../podcini/ui/fragment/QueuesFragment.kt | 2 +- .../ui/fragment/SubscriptionsFragment.kt | 84 +++++++++++++------ .../mdiq/podcini/ui/view/EpisodeViewHolder.kt | 2 +- .../kotlin/ac/mdiq/podcini/util/FlowEvent.kt | 2 + .../res/layout/fragment_subscriptions.xml | 26 ++++-- app/src/main/res/menu/downloads_completed.xml | 17 ++-- changelog.md | 9 ++ .../android/en-US/changelogs/3020247.txt | 8 ++ 21 files changed, 210 insertions(+), 105 deletions(-) create mode 100644 fastlane/metadata/android/en-US/changelogs/3020247.txt diff --git a/app/build.gradle b/app/build.gradle index ea2d6f18..708958ae 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 3020246 - versionName "6.6.1" + versionCode 3020247 + versionName "6.6.2" applicationId "ac.mdiq.podcini.R" def commit = "" diff --git a/app/src/main/kotlin/ac/mdiq/podcini/preferences/UserPreferences.kt b/app/src/main/kotlin/ac/mdiq/podcini/preferences/UserPreferences.kt index c82e7632..6265dd49 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/preferences/UserPreferences.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/preferences/UserPreferences.kt @@ -307,6 +307,7 @@ object UserPreferences { prefQueueKeepSorted, prefQueueKeepSortedOrder, prefDownloadSortedOrder, + prefDownloadsFilter, // Episodes prefEpisodesSort, 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 d15736bc..e1d709d8 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 @@ -431,7 +431,7 @@ object Feeds { feed.fileUrl = File(feedfilePath, getFeedfileName(feed)).toString() feed.preferences = FeedPreferences(feed.id, false, FeedPreferences.AutoDeleteAction.GLOBAL, VolumeAdaptionSetting.OFF, "", "") feed.preferences!!.keepUpdated = false - feed.preferences!!.queue = null + feed.preferences!!.queueId = -2L feed.preferences!!.videoModePolicy = if (video) VideoMode.WINDOW_VIEW else VideoMode.AUDIO_ONLY upsertBlk(feed) {} return feed 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 09d827ae..31c8eb6a 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 @@ -35,34 +35,28 @@ object Queues { appPrefs.edit().putBoolean(UserPreferences.Prefs.prefQueueLocked.name, locked).apply() } + /** + * Returns if the queue is in keep sorted mode. + * Enables/disables the keep sorted mode of the queue. + * @see .queueKeepSortedOrder + */ var isQueueKeepSorted: Boolean - /** - * Returns if the queue is in keep sorted mode. - * @see .queueKeepSortedOrder - */ get() = appPrefs.getBoolean(UserPreferences.Prefs.prefQueueKeepSorted.name, false) - /** - * Enables/disables the keep sorted mode of the queue. - * @see .queueKeepSortedOrder - */ set(keepSorted) { appPrefs.edit().putBoolean(UserPreferences.Prefs.prefQueueKeepSorted.name, keepSorted).apply() } + /** + * Returns the sort order for the queue keep sorted mode. + * Note: This value is stored independently from the keep sorted state. + * Sets the sort order for the queue keep sorted mode. + * @see .isQueueKeepSorted + */ var queueKeepSortedOrder: EpisodeSortOrder? - /** - * Returns the sort order for the queue keep sorted mode. - * Note: This value is stored independently from the keep sorted state. - * @see .isQueueKeepSorted - */ get() { val sortOrderStr = appPrefs.getString(UserPreferences.Prefs.prefQueueKeepSortedOrder.name, "use-default") return EpisodeSortOrder.parseWithDefault(sortOrderStr, EpisodeSortOrder.DATE_NEW_OLD) } - /** - * Sets the sort order for the queue keep sorted mode. - * @see .setQueueKeepSorted - */ set(sortOrder) { if (sortOrder == null) return appPrefs.edit().putString(UserPreferences.Prefs.prefQueueKeepSortedOrder.name, sortOrder.name).apply() @@ -344,6 +338,14 @@ object Queues { } } + fun inAnyQueue(episode: Episode): Boolean { + val queues = realm.query(PlayQueue::class).find() + for (q in queues) { + if (q.contains(episode)) return true + } + return false + } + class EnqueuePositionPolicy(private val enqueueLocation: EnqueueLocation) { /** * Determine the position (0-based) that the item(s) should be inserted to the named queue. diff --git a/app/src/main/kotlin/ac/mdiq/podcini/storage/model/EpisodeFilter.kt b/app/src/main/kotlin/ac/mdiq/podcini/storage/model/EpisodeFilter.kt index 65ac0631..305c7561 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/storage/model/EpisodeFilter.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/storage/model/EpisodeFilter.kt @@ -1,6 +1,7 @@ package ac.mdiq.podcini.storage.model import ac.mdiq.podcini.playback.base.InTheatre.curQueue +import ac.mdiq.podcini.storage.database.Queues.inAnyQueue import java.io.Serializable class EpisodeFilter(vararg properties: String) : Serializable { @@ -49,8 +50,17 @@ class EpisodeFilter(vararg properties: String) : Serializable { showNoMedia && item.media != null -> return false showIsFavorite && !item.isFavorite -> return false showNotFavorite && item.isFavorite -> return false - showQueued && curQueue.isInQueue(item) -> return false - showNotQueued && !curQueue.isInQueue(item) -> return false + showQueued && !inAnyQueue(item) -> return false + showNotQueued && inAnyQueue(item) -> return false + else -> return true + } + } + +// filter on queues does not have a query string so it's not applied on query results, need to filter separately + fun matchesForQueues(item: Episode): Boolean { + when { + showQueued && !inAnyQueue(item) -> return false + showNotQueued && inAnyQueue(item) -> return false else -> return true } } diff --git a/app/src/main/kotlin/ac/mdiq/podcini/storage/model/FeedFilter.kt b/app/src/main/kotlin/ac/mdiq/podcini/storage/model/FeedFilter.kt index 7c9b8744..e8aa2989 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/storage/model/FeedFilter.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/storage/model/FeedFilter.kt @@ -62,7 +62,7 @@ class FeedFilter(vararg properties: String) : Serializable { } when { showAlwaysAutoDelete -> statements.add(" preferences.autoDelete == ${FeedPreferences.AutoDeleteAction.ALWAYS.code} ") - showNeverAutoDelete -> statements.add(" preferences.playSpeed != ${FeedPreferences.AutoDeleteAction.NEVER.code} ") + showNeverAutoDelete -> statements.add(" preferences.playSpeed == ${FeedPreferences.AutoDeleteAction.NEVER.code} ") } when { showAutoDownload -> statements.add(" preferences.autoDownload == true ") diff --git a/app/src/main/kotlin/ac/mdiq/podcini/storage/model/PlayQueue.kt b/app/src/main/kotlin/ac/mdiq/podcini/storage/model/PlayQueue.kt index ea2a38a2..c2f199cb 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/storage/model/PlayQueue.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/storage/model/PlayQueue.kt @@ -30,7 +30,7 @@ class PlayQueue : RealmObject { var idsBinList: RealmList = realmListOf() - fun isInQueue(episode: Episode): Boolean { + fun contains(episode: Episode): Boolean { return episodeIds.contains(episode.id) } diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/actions/handler/EpisodeMenuHandler.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/actions/handler/EpisodeMenuHandler.kt index 4ea474d6..a6ed9776 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/actions/handler/EpisodeMenuHandler.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/actions/handler/EpisodeMenuHandler.kt @@ -54,7 +54,7 @@ object EpisodeMenuHandler { val hasMedia = selectedItem.media != null val isPlaying = hasMedia && InTheatre.isCurMedia(selectedItem.media) - val isInQueue: Boolean = curQueue.isInQueue(selectedItem) + val isInQueue: Boolean = curQueue.contains(selectedItem) val isLocalFile = hasMedia && selectedItem.feed?.isLocalFeed?:false val isFavorite: Boolean = selectedItem.isFavorite diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/actions/handler/EpisodeMultiSelectHandler.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/actions/handler/EpisodeMultiSelectHandler.kt index 3809e7af..e9ab4968 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/actions/handler/EpisodeMultiSelectHandler.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/actions/handler/EpisodeMultiSelectHandler.kt @@ -148,11 +148,11 @@ class EpisodeMultiSelectHandler(private val activity: MainActivity, private val val toRemove = mutableSetOf() val toRemoveCur = mutableListOf() items.forEach { e -> - if (curQueue.isInQueue(e)) toRemoveCur.add(e) + if (curQueue.contains(e)) toRemoveCur.add(e) } items.forEach { e -> for (q in queues) { - if (q.isInQueue(e)) { + if (q.contains(e)) { toRemove.add(e.id) break } diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/dialog/EpisodeFilterDialog.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/dialog/EpisodeFilterDialog.kt index 21e7a2cf..6665e536 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/dialog/EpisodeFilterDialog.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/dialog/EpisodeFilterDialog.kt @@ -27,6 +27,7 @@ abstract class EpisodeFilterDialog : BottomSheetDialogFragment() { var filter: EpisodeFilter? = null private val buttonMap: MutableMap = mutableMapOf() + val filtersDisabled: MutableSet = mutableSetOf() private val newFilterValues: Set get() { @@ -50,10 +51,9 @@ abstract class EpisodeFilterDialog : BottomSheetDialogFragment() { //add filter rows for (item in FeedItemFilterGroup.entries) { // Logd("EpisodeFilterDialog", "FeedItemFilterGroup: ${item.values[0].filterId} ${item.values[1].filterId}") + if (item in filtersDisabled) continue + val rBinding = FilterDialogRowBinding.inflate(inflater) -// rowBinding.root.addOnButtonCheckedListener { _: MaterialButtonToggleGroup?, _: Int, _: Boolean -> -// onFilterChanged(newFilterValues) -// } rBinding.filterButton1.setOnClickListener { onFilterChanged(newFilterValues) } rBinding.filterButton2.setOnClickListener { onFilterChanged(newFilterValues) } diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/AllEpisodesFragment.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/AllEpisodesFragment.kt index baaaf07d..b479509b 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/AllEpisodesFragment.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/AllEpisodesFragment.kt @@ -43,6 +43,7 @@ import kotlin.math.min toolbar.inflateMenu(R.menu.episodes) toolbar.setTitle(R.string.episodes_label) updateToolbar() + txtvInformation.visibility = View.VISIBLE txtvInformation.setOnClickListener { AllEpisodesFilterDialog.newInstance(getFilter()).show(childFragmentManager, null) } @@ -66,13 +67,15 @@ import kotlin.math.min private var loadItemsRunning = false override fun loadData(): List { + val filter = getFilter() if (!loadItemsRunning) { loadItemsRunning = true - allEpisodes = getEpisodes(0, Int.MAX_VALUE, getFilter(), allEpisodesSortOrder, false) + allEpisodes = getEpisodes(0, Int.MAX_VALUE, filter, allEpisodesSortOrder, false) Logd(TAG, "loadData ${allEpisodes.size}") loadItemsRunning = false } if (allEpisodes.isEmpty()) return listOf() + allEpisodes = allEpisodes.filter { filter.matchesForQueues(it) } return allEpisodes.subList(0, min(allEpisodes.size-1, page * EPISODES_PER_PAGE)) } @@ -144,11 +147,9 @@ import kotlin.math.min override fun updateToolbar() { swipeActions.setFilter(getFilter()) if (getFilter().values.isNotEmpty()) { - txtvInformation.visibility = View.VISIBLE - txtvInformation.text = "${adapter.totalNumberOfItems} episodes - filtered" + txtvInformation.text = "${adapter.totalNumberOfItems} episodes - ${getString(R.string.filtered_label)}" emptyView.setMessage(R.string.no_all_episodes_filtered_label) } else { - txtvInformation.visibility = View.VISIBLE txtvInformation.text = "${adapter.totalNumberOfItems} episodes" emptyView.setMessage(R.string.no_all_episodes_label) } 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 de9efd0e..33b76940 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 @@ -22,8 +22,11 @@ import ac.mdiq.podcini.ui.actions.swipeactions.SwipeActions import ac.mdiq.podcini.ui.activity.MainActivity import ac.mdiq.podcini.ui.adapter.EpisodesAdapter import ac.mdiq.podcini.ui.adapter.SelectableAdapter +import ac.mdiq.podcini.ui.dialog.EpisodeFilterDialog import ac.mdiq.podcini.ui.dialog.EpisodeSortDialog import ac.mdiq.podcini.ui.dialog.SwitchQueueDialog +import ac.mdiq.podcini.ui.fragment.AllEpisodesFragment.AllEpisodesFilterDialog +import ac.mdiq.podcini.ui.fragment.AllEpisodesFragment.Companion.prefFilterAllEpisodes import ac.mdiq.podcini.ui.utils.EmptyViewHandler import ac.mdiq.podcini.ui.utils.LiftOnScrollListener import ac.mdiq.podcini.ui.view.EpisodeViewHolder @@ -53,6 +56,7 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import org.apache.commons.lang3.StringUtils import java.io.File import java.util.* @@ -105,12 +109,8 @@ import java.util.* lifecycle.addObserver(swipeActions) swipeActions.setFilter(EpisodeFilter(EpisodeFilter.States.downloaded.name)) refreshSwipeTelltale() - binding.leftActionIcon.setOnClickListener { - swipeActions.showDialog() - } - binding.rightActionIcon.setOnClickListener { - swipeActions.showDialog() - } + binding.leftActionIcon.setOnClickListener { swipeActions.showDialog() } + binding.rightActionIcon.setOnClickListener { swipeActions.showDialog() } val animator: RecyclerView.ItemAnimator? = recyclerView.itemAnimator if (animator is SimpleItemAnimator) animator.supportsChangeAnimations = false @@ -141,8 +141,7 @@ import java.util.* adapter.endSelectMode() true } - if (arguments != null && requireArguments().getBoolean(ARG_SHOW_LOGS, false)) - DownloadLogFragment().show(childFragmentManager, null) + if (arguments != null && requireArguments().getBoolean(ARG_SHOW_LOGS, false)) DownloadLogFragment().show(childFragmentManager, null) addEmptyView() return binding.root @@ -185,7 +184,7 @@ import java.util.* @UnstableApi override fun onMenuItemClick(item: MenuItem): Boolean { when (item.itemId) { -// R.id.refresh_item -> FeedUpdateManager.runOnceOrAsk(requireContext()) + R.id.filter_items -> DownloadsFilterDialog.newInstance(getFilter()).show(childFragmentManager, null) R.id.action_download_logs -> DownloadLogFragment().show(childFragmentManager, null) R.id.action_search -> (activity as MainActivity).loadChildFragment(SearchFragment.newInstance()) R.id.downloads_sort -> DownloadsSortDialog().show(childFragmentManager, "SortDialog") @@ -196,6 +195,10 @@ import java.util.* return true } + private fun getFilter(): EpisodeFilter { + return EpisodeFilter(prefFilterDownloads) + } + private val nameEpisodeMap: MutableMap = mutableMapOf() private val filesRemoved: MutableList = mutableListOf() private fun reconsile() { @@ -203,9 +206,7 @@ import java.util.* val items = realm.query(Episode::class).query("media.episode == nil").find() Logd(TAG, "number of episode with null backlink: ${items.size}") for (item in items) { - upsert(item) { - it.media!!.episode = it - } + upsert(item) { it.media!!.episode = it } } nameEpisodeMap.clear() episodes.forEach { e -> @@ -219,9 +220,7 @@ import java.util.* Logd(TAG, "reconsile: end, episodes missing file: ${nameEpisodeMap.size}") if (nameEpisodeMap.isNotEmpty()) { for (e in nameEpisodeMap.values) { - upsertBlk(e) { - it.media?.setfileUrlOrNull(null) - } + upsertBlk(e) { it.media?.setfileUrlOrNull(null) } } } loadItems() @@ -281,6 +280,7 @@ import java.util.* Logd(TAG, "Received event: ${event.TAG}") when (event) { is FlowEvent.EpisodeEvent -> onEpisodeEvent(event) + is FlowEvent.DownloadsFilterEvent -> onFilterChanged(event) is FlowEvent.EpisodeMediaEvent -> onEpisodeMediaEvent(event) is FlowEvent.PlayerSettingsEvent -> loadItems() is FlowEvent.DownloadLogEvent -> loadItems() @@ -302,6 +302,14 @@ import java.util.* // } } + private fun onFilterChanged(event: FlowEvent.DownloadsFilterEvent) { + val fSet = event.filterValues?.toMutableSet() ?: mutableSetOf() + fSet.add(EpisodeFilter.States.downloaded.name) + prefFilterDownloads = StringUtils.join(fSet, ",") + Logd(TAG, "onFilterChanged: $prefFilterDownloads") + loadItems() + } + private fun addEmptyView() { emptyView = EmptyViewHandler(requireContext()) emptyView.setIcon(R.drawable.ic_download) @@ -324,10 +332,7 @@ import java.util.* } } // have to do this as adapter.notifyItemRemoved(pos) when pos == 0 causes crash - if (size > 0) { -// adapter.setDummyViews(0) - adapter.updateItems(episodes) - } + if (size > 0) adapter.updateItems(episodes) refreshInfoBar() } @@ -345,10 +350,7 @@ import java.util.* } } // have to do this as adapter.notifyItemRemoved(pos) when pos == 0 causes crash - if (size > 0) { -// adapter.setDummyViews(0) - adapter.updateItems(episodes) - } + if (size > 0) adapter.updateItems(episodes) refreshInfoBar() } @@ -366,7 +368,8 @@ import java.util.* try { withContext(Dispatchers.IO) { val sortOrder: EpisodeSortOrder? = downloadsSortedOrder - val downloadedItems = getEpisodes(0, Int.MAX_VALUE, EpisodeFilter(EpisodeFilter.States.downloaded.name), sortOrder) + val filter = getFilter() + val downloadedItems = getEpisodes(0, Int.MAX_VALUE, filter, sortOrder) if (runningDownloads.isEmpty()) episodes = downloadedItems.toMutableList() else { val mediaUrls: MutableList = ArrayList() @@ -378,6 +381,7 @@ import java.util.* currentDownloads.addAll(downloadedItems) episodes = currentDownloads } + episodes = episodes.filter { filter.matchesForQueues(it) }.toMutableList() } withContext(Dispatchers.Main) { // adapter.setDummyViews(0) @@ -389,9 +393,7 @@ import java.util.* // adapter.setDummyViews(0) adapter.updateItems(mutableListOf()) Log.e(TAG, Log.getStackTraceString(e)) - } finally { - loadItemsRunning = false - } + } finally { loadItemsRunning = false } } } } @@ -416,6 +418,7 @@ import java.util.* for (item in episodes) sizeMB += item.media?.size ?: 0 info += " • " + (sizeMB / 1000000) + " MB" } + if (getFilter().values.isNotEmpty()) info += " - ${getString(R.string.filtered_label)}" binding.infoBar.text = info } @@ -466,6 +469,21 @@ import java.util.* } } + class DownloadsFilterDialog : EpisodeFilterDialog() { + override fun onFilterChanged(newFilterValues: Set) { + EventFlow.postEvent(FlowEvent.DownloadsFilterEvent(newFilterValues)) + } + companion object { + fun newInstance(filter: EpisodeFilter?): DownloadsFilterDialog { + val dialog = DownloadsFilterDialog() + dialog.filter = filter + dialog.filtersDisabled.add(FeedItemFilterGroup.DOWNLOADED) + dialog.filtersDisabled.add(FeedItemFilterGroup.MEDIA) + return dialog + } + } + } + companion object { val TAG = DownloadsFragment::class.simpleName ?: "Anonymous" @@ -481,5 +499,11 @@ import java.util.* set(sortOrder) { appPrefs.edit().putString(UserPreferences.Prefs.prefDownloadSortedOrder.name, "" + sortOrder!!.code).apply() } + + var prefFilterDownloads: String + get() = appPrefs.getString(UserPreferences.Prefs.prefDownloadsFilter.name, EpisodeFilter.States.downloaded.name) ?: EpisodeFilter.States.downloaded.name + set(filter) { + appPrefs.edit().putString(UserPreferences.Prefs.prefDownloadsFilter.name, filter).apply() + } } } diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/HistoryFragment.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/HistoryFragment.kt index b8ac0f22..758b946f 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/HistoryFragment.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/HistoryFragment.kt @@ -128,12 +128,11 @@ import kotlin.math.min toolbar.menu.findItem(R.id.clear_history_item).setVisible(episodes.isNotEmpty()) swipeActions.setFilter(getFilter()) + txtvInformation.visibility = View.VISIBLE if (getFilter().values.isNotEmpty()) { - txtvInformation.visibility = View.VISIBLE - txtvInformation.text = "${adapter.totalNumberOfItems} episodes - filtered" + txtvInformation.text = "${adapter.totalNumberOfItems} episodes - ${getString(R.string.filtered_label)}" emptyView.setMessage(R.string.no_all_episodes_filtered_label) } else { - txtvInformation.visibility = View.VISIBLE txtvInformation.text = "${adapter.totalNumberOfItems} episodes" emptyView.setMessage(R.string.no_all_episodes_label) } 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 7c2305c2..99873d96 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 @@ -321,7 +321,7 @@ import kotlin.math.max } when (event.action) { FlowEvent.QueueEvent.Action.ADDED -> { - if (event.episodes.isNotEmpty() && !curQueue.isInQueue(event.episodes[0])) { + if (event.episodes.isNotEmpty() && !curQueue.contains(event.episodes[0])) { val pos = queueItems.size queueItems.addAll(event.episodes) adapter?.notifyItemRangeInserted(pos, queueItems.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 5e8bb36d..0af7584e 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 @@ -42,7 +42,6 @@ import android.net.Uri import android.os.Bundle import android.util.Log import android.view.* -import android.view.inputmethod.EditorInfo import android.widget.* import androidx.activity.result.ActivityResult import androidx.activity.result.contract.ActivityResultContracts @@ -108,18 +107,22 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener, Selec private lateinit var toolbar: MaterialToolbar private lateinit var speedDialView: SpeedDialView - private lateinit var catAdapter: ArrayAdapter + private val tags: MutableList = mutableListOf() + private val queueIds: MutableList = mutableListOf() + private lateinit var queuesAdapter: ArrayAdapter + private lateinit var tagsAdapter: ArrayAdapter + private var tagFilterIndex = 1 + private var queueFilterIndex = 1 private var infoTextFiltered = "" private var infoTextUpdate = "" - private var tagFilterIndex = 1 -// TODO: currently not used + + // TODO: currently not used private var displayedFolder: String = "" private var displayUpArrow = false private var feedList: MutableList = mutableListOf() private var feedListFiltered: List = mutableListOf() - private val tags: MutableList = mutableListOf() private var useGrid: Boolean? = null private val useGridLayout: Boolean @@ -164,10 +167,26 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener, Selec resetTags() - catAdapter = ArrayAdapter(requireContext(), android.R.layout.simple_spinner_item, tags) - catAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) - binding.categorySpinner.setAdapter(catAdapter) - binding.categorySpinner.setSelection(catAdapter.getPosition("All")) + val queues = realm.query(PlayQueue::class).find() + queueIds.addAll(queues.map { it.id }) + val spinnerTexts: MutableList = mutableListOf("All", "None") + spinnerTexts.addAll(queues.map { it.name }) + queuesAdapter = ArrayAdapter(requireContext(), android.R.layout.simple_spinner_item, spinnerTexts) + queuesAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) + binding.queuesSpinner.setAdapter(queuesAdapter) + binding.queuesSpinner.setSelection(queuesAdapter.getPosition("All")) + binding.queuesSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { + override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { + queueFilterIndex = position + loadSubscriptions() + } + override fun onNothingSelected(parent: AdapterView<*>?) {} + } + + tagsAdapter = ArrayAdapter(requireContext(), android.R.layout.simple_spinner_item, tags) + tagsAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) + binding.categorySpinner.setAdapter(tagsAdapter) + binding.categorySpinner.setSelection(tagsAdapter.getPosition("All")) binding.categorySpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { tagFilterIndex = position @@ -177,17 +196,17 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener, Selec override fun onNothingSelected(parent: AdapterView<*>?) {} } - val searchBox = binding.searchBox - searchBox.setOnEditorActionListener { _, actionId, _ -> - if (actionId == EditorInfo.IME_ACTION_SEARCH) { - val text = searchBox.text.toString().lowercase(Locale.getDefault()) - val resultList = feedListFiltered.filter { - it.title?.lowercase(Locale.getDefault())?.contains(text)?:false || it.author?.lowercase(Locale.getDefault())?.contains(text)?:false - } - adapter.setItems(resultList) - true - } else false - } +// val searchBox = binding.searchBox +// searchBox.setOnEditorActionListener { _, actionId, _ -> +// if (actionId == EditorInfo.IME_ACTION_SEARCH) { +// val text = searchBox.text.toString().lowercase(Locale.getDefault()) +// val resultList = feedListFiltered.filter { +// it.title?.lowercase(Locale.getDefault())?.contains(text)?:false || it.author?.lowercase(Locale.getDefault())?.contains(text)?:false +// } +// adapter.setItems(resultList) +// true +// } else false +// } // binding.progressBar.visibility = View.VISIBLE binding.progressBar.visibility = View.GONE @@ -291,10 +310,15 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener, Selec } } - private fun filterOnTag() { - feedListFiltered = feedList - binding.count.text = feedListFiltered.size.toString() + " / " + feedList.size.toString() - adapter.setItems(feedListFiltered) + private fun queryStringOfQueues() : String { + return when (queueFilterIndex) { + 0 -> "" // All feeds + 1 -> " preferences.queueId == -2 " + else -> { // feeds associated with the chosen queue + val qid = queueIds[queueFilterIndex-2] + " preferences.queueId == '$qid' " + } + } } private fun resetTags() { @@ -383,7 +407,10 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener, Selec withContext(Dispatchers.Main) { // We have fewer items. This can result in items being selected that are no longer visible. if (feedListFiltered.size > feedList.size) adapter.endSelectMode() - filterOnTag() +// filterOnTag() + feedListFiltered = feedList + binding.count.text = feedListFiltered.size.toString() + " / " + feedList.size.toString() + adapter.setItems(feedListFiltered) // binding.progressBar.visibility = View.GONE adapter.setItems(feedListFiltered) binding.count.text = feedListFiltered.size.toString() + " / " + feedList.size.toString() @@ -407,8 +434,11 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener, Selec } private fun filterAndSort() { + var fQueryStr = FeedFilter(feedsFilter).queryString() val tagsQueryStr = queryStringOfTags() - val fQueryStr = if (tagsQueryStr.isEmpty()) FeedFilter(feedsFilter).queryString() else FeedFilter(feedsFilter).queryString() + " AND " + tagsQueryStr + if (tagsQueryStr.isNotEmpty()) fQueryStr += " AND $tagsQueryStr" + val queuesQueryStr = queryStringOfQueues() + if (queuesQueryStr.isNotEmpty()) fQueryStr += " AND $queuesQueryStr" Logd(TAG, "sortFeeds() called $feedsFilter $fQueryStr") val feedList_ = getFeedList(fQueryStr).toMutableList() val feedOrder = feedOrderBy @@ -710,7 +740,7 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener, Selec @Composable fun AutoDeleteDialog(showDialog: Boolean, onDismissRequest: () -> Unit) { if (showDialog) { - val (selectedOption, onOptionSelected) = remember { mutableStateOf("") } + val (selectedOption, _) = remember { mutableStateOf("") } Dialog(onDismissRequest = { onDismissRequest() }) { Card( modifier = Modifier 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 6c7a4e9a..10f9fb97 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 @@ -158,7 +158,7 @@ open class EpisodeViewHolder(private val activity: MainActivity, parent: ViewGro setPubDate(item) binding.isFavorite.visibility = if (item.isFavorite) View.VISIBLE else View.GONE - isInQueue.visibility = if (curQueue.isInQueue(item)) View.VISIBLE else View.GONE + isInQueue.visibility = if (curQueue.contains(item)) View.VISIBLE else View.GONE // container.alpha = if (item.isPlayed()) 0.7f else 1.0f val newButton = EpisodeActionButton.forItem(item) diff --git a/app/src/main/kotlin/ac/mdiq/podcini/util/FlowEvent.kt b/app/src/main/kotlin/ac/mdiq/podcini/util/FlowEvent.kt index e78e56c9..4a910554 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/util/FlowEvent.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/util/FlowEvent.kt @@ -160,6 +160,8 @@ sealed class FlowEvent { data class AllEpisodesSortEvent(val dummy: Unit = Unit) : FlowEvent() + data class DownloadsFilterEvent(val filterValues: Set?) : FlowEvent() + data class EpisodeEvent(val episodes: List) : FlowEvent() { companion object { fun updated(vararg episodes: Episode): EpisodeEvent { diff --git a/app/src/main/res/layout/fragment_subscriptions.xml b/app/src/main/res/layout/fragment_subscriptions.xml index ceac477d..36ed2349 100644 --- a/app/src/main/res/layout/fragment_subscriptions.xml +++ b/app/src/main/res/layout/fragment_subscriptions.xml @@ -82,16 +82,28 @@ android:orientation="horizontal" android:layout_below="@id/appbar"> - + + + + + + + + + + + + android:dropDownWidth="200dp" + android:padding="8dp" + android:paddingBottom="20dp" + android:textColor="?android:attr/textColorPrimary" + android:textSize="@dimen/text_size_micro" + android:spinnerMode="dropdown"/> - - + + @@ -35,4 +37,9 @@ android:id="@+id/switch_queue" android:title="@string/switch_queue" /> + + diff --git a/changelog.md b/changelog.md index d17336f6..11ebfb92 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,12 @@ +# 6.6.2 + +* fixed issue of filter "never auto delete" in Subscriptions view +* corrected issue of "queued/not queued" filter for episodes +* filter on "queued/not queued" is checked for all queues (not only the active queue) +* added filtering feature to Downloads view +* changed associated queue of Youtube Syndicate to None (rather than active set previously) +* in Subscriptions view, the "search" box is changed to a "Queues" spinner to filter by associated queue + # 6.6.1 * the confirm dialog is more responsive when receiving a youtube media share diff --git a/fastlane/metadata/android/en-US/changelogs/3020247.txt b/fastlane/metadata/android/en-US/changelogs/3020247.txt new file mode 100644 index 00000000..bc444a95 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/3020247.txt @@ -0,0 +1,8 @@ + Version 6.6.2: + +* fixed issue of filter "never auto delete" in Subscriptions view +* corrected issue of "queued/not queued" filter for episodes +* filter on "queued/not queued" is checked for all queues (not only the active queue) +* added filtering feature to Downloads view +* changed associated queue of Youtube Syndicate to None (rather than active set previously) +* in Subscriptions view, the "search" box is changed to a "Queues" spinner to filter by associated queue