6.12.2 commit

This commit is contained in:
Xilin Jia 2024-10-23 22:29:58 +01:00
parent 79c1bf43f9
commit d3ca132b7c
14 changed files with 203 additions and 223 deletions

View File

@ -31,8 +31,8 @@ android {
testApplicationId "ac.mdiq.podcini.tests" testApplicationId "ac.mdiq.podcini.tests"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
versionCode 3020279 versionCode 3020280
versionName "6.12.1" versionName "6.12.2"
applicationId "ac.mdiq.podcini.R" applicationId "ac.mdiq.podcini.R"
def commit = "" def commit = ""

View File

@ -45,7 +45,7 @@ abstract class ServiceStatusHandler(private val activity: FragmentActivity) {
Logd(TAG, "statusUpdate onReceive doing updates") Logd(TAG, "statusUpdate onReceive doing updates")
MediaPlayerBase.status = info.playerStatus MediaPlayerBase.status = info.playerStatus
prevStatus = MediaPlayerBase.status prevStatus = MediaPlayerBase.status
curMedia = info.playable // curMedia = info.playable
handleStatus() handleStatus()
} }
} else { } else {
@ -177,7 +177,7 @@ abstract class ServiceStatusHandler(private val activity: FragmentActivity) {
Logd(TAG, "Querying service info") Logd(TAG, "Querying service info")
if (playbackService != null && PlaybackService.mPlayerInfo != null) { if (playbackService != null && PlaybackService.mPlayerInfo != null) {
MediaPlayerBase.status = PlaybackService.mPlayerInfo!!.playerStatus MediaPlayerBase.status = PlaybackService.mPlayerInfo!!.playerStatus
curMedia = PlaybackService.mPlayerInfo!!.playable // curMedia = PlaybackService.mPlayerInfo!!.playable
// make sure that new media is loaded if it's available // make sure that new media is loaded if it's available
mediaInfoLoaded = false mediaInfoLoaded = false
handleStatus() handleStatus()

View File

@ -271,13 +271,15 @@ abstract class MediaPlayerBase protected constructor(protected val context: Cont
Log.d(TAG, "${this.javaClass.simpleName}: Setting player status to $newStatus") Log.d(TAG, "${this.javaClass.simpleName}: Setting player status to $newStatus")
this.oldStatus = status this.oldStatus = status
status = newStatus status = newStatus
if (newMedia != null) setPlayable(newMedia) if (newMedia != null) {
if (newMedia != null && newStatus != PlayerStatus.INDETERMINATE) { setPlayable(newMedia)
if (newStatus != PlayerStatus.INDETERMINATE) {
when { when {
oldStatus == PlayerStatus.PLAYING && newStatus != PlayerStatus.PLAYING -> callback.onPlaybackPause(newMedia, position) oldStatus == PlayerStatus.PLAYING && newStatus != PlayerStatus.PLAYING -> callback.onPlaybackPause(newMedia, position)
oldStatus != PlayerStatus.PLAYING && newStatus == PlayerStatus.PLAYING -> callback.onPlaybackStart(newMedia, position) oldStatus != PlayerStatus.PLAYING && newStatus == PlayerStatus.PLAYING -> callback.onPlaybackStart(newMedia, position)
} }
} }
}
callback.statusChanged(MediaPlayerInfo(oldStatus, status, curMedia)) callback.statusChanged(MediaPlayerInfo(oldStatus, status, curMedia))
} }

View File

@ -389,15 +389,23 @@ class PlaybackService : MediaLibraryService() {
} }
} }
if (item != null) { if (item != null) {
// fun shouldSkipKeepEpisode(): Boolean {
// return appPrefs.getBoolean(UserPreferences.Prefs.prefSkipKeepsEpisode.name, true)
// }
// fun shouldFavoriteKeepEpisode(): Boolean {
// return appPrefs.getBoolean(UserPreferences.Prefs.prefFavoriteKeepsEpisode.name, true)
// }
runOnIOScope { runOnIOScope {
if (ended || smartMarkAsPlayed || autoSkipped || (skipped && !shouldSkipKeepEpisode())) { val shouldSkipKeepEpisode = appPrefs.getBoolean(UserPreferences.Prefs.prefSkipKeepsEpisode.name, true)
val shouldFavoriteKeepEpisode = appPrefs.getBoolean(UserPreferences.Prefs.prefFavoriteKeepsEpisode.name, true)
if (ended || smartMarkAsPlayed || autoSkipped || (skipped && !shouldSkipKeepEpisode)) {
Logd(TAG, "onPostPlayback ended: $ended smartMarkAsPlayed: $smartMarkAsPlayed autoSkipped: $autoSkipped skipped: $skipped") Logd(TAG, "onPostPlayback ended: $ended smartMarkAsPlayed: $smartMarkAsPlayed autoSkipped: $autoSkipped skipped: $skipped")
// only mark the item as played if we're not keeping it anyways // only mark the item as played if we're not keeping it anyways
item = setPlayStateSync(PlayState.PLAYED.code, ended || (skipped && smartMarkAsPlayed), item!!) item = setPlayStateSync(PlayState.PLAYED.code, ended || (skipped && smartMarkAsPlayed), item!!)
val action = item?.feed?.preferences?.autoDeleteAction val action = item?.feed?.preferences?.autoDeleteAction
val shouldAutoDelete = (action == AutoDeleteAction.ALWAYS || val shouldAutoDelete = (action == AutoDeleteAction.ALWAYS ||
(action == AutoDeleteAction.GLOBAL && item?.feed != null && shouldAutoDeleteItem(item!!.feed!!))) (action == AutoDeleteAction.GLOBAL && item?.feed != null && shouldAutoDeleteItem(item!!.feed!!)))
if (playable is EpisodeMedia && shouldAutoDelete && (item?.isFavorite != true || !shouldFavoriteKeepEpisode())) { if (playable is EpisodeMedia && shouldAutoDelete && (item?.isFavorite != true || !shouldFavoriteKeepEpisode)) {
item = deleteMediaSync(this@PlaybackService, item!!) item = deleteMediaSync(this@PlaybackService, item!!)
if (shouldDeleteRemoveFromQueue()) removeFromQueueSync(null, item!!) if (shouldDeleteRemoveFromQueue()) removeFromQueueSync(null, item!!)
} }
@ -407,14 +415,6 @@ class PlaybackService : MediaLibraryService() {
} }
} }
fun shouldSkipKeepEpisode(): Boolean {
return appPrefs.getBoolean(UserPreferences.Prefs.prefSkipKeepsEpisode.name, true)
}
fun shouldFavoriteKeepEpisode(): Boolean {
return appPrefs.getBoolean(UserPreferences.Prefs.prefFavoriteKeepsEpisode.name, true)
}
override fun onPlaybackStart(playable: Playable, position: Int) { override fun onPlaybackStart(playable: Playable, position: Int) {
Logd(TAG, "onPlaybackStart position: $position") Logd(TAG, "onPlaybackStart position: $position")
taskManager.startWidgetUpdater() taskManager.startWidgetUpdater()
@ -747,12 +747,10 @@ class PlaybackService : MediaLibraryService() {
override fun onDestroy() { override fun onDestroy() {
Logd(TAG, "Service is about to be destroyed") Logd(TAG, "Service is about to be destroyed")
playbackService = null playbackService = null
isRunning = false isRunning = false
currentMediaType = MediaType.UNKNOWN currentMediaType = MediaType.UNKNOWN
castStateListener.destroy() castStateListener.destroy()
currentitem = null currentitem = null
LocalMediaPlayer.cleanup() LocalMediaPlayer.cleanup()
@ -1967,8 +1965,8 @@ class PlaybackService : MediaLibraryService() {
} }
override fun onIsPlayingChanged(isPlaying: Boolean) { override fun onIsPlayingChanged(isPlaying: Boolean) {
// val stat = if (isPlaying) PlayerStatus.PLAYING else PlayerStatus.PAUSED // val stat = if (isPlaying) PlayerStatus.PLAYING else PlayerStatus.PAUSED
// TODO: test // TODO: test: changing PAUSED to STOPPED or INDETERMINATE makes resume not possible if interrupted
val stat = if (isPlaying) PlayerStatus.PLAYING else PlayerStatus.INDETERMINATE val stat = if (isPlaying) PlayerStatus.PLAYING else PlayerStatus.PAUSED
setPlayerStatus(stat, curMedia) setPlayerStatus(stat, curMedia)
Logd(TAG, "onIsPlayingChanged $isPlaying") Logd(TAG, "onIsPlayingChanged $isPlaying")
} }

View File

@ -20,6 +20,8 @@ class EpisodeFilter(vararg properties: String) : Serializable {
val showNotAutoDownloadable: Boolean = hasProperty(States.not_auto_downloadable.name) val showNotAutoDownloadable: Boolean = hasProperty(States.not_auto_downloadable.name)
val showHasMedia: Boolean = hasProperty(States.has_media.name) val showHasMedia: Boolean = hasProperty(States.has_media.name)
val showNoMedia: Boolean = hasProperty(States.no_media.name) val showNoMedia: Boolean = hasProperty(States.no_media.name)
val showHasComments: Boolean = hasProperty(States.has_comments.name)
val showNoComments: Boolean = hasProperty(States.no_comments.name)
val showIsFavorite: Boolean = hasProperty(States.is_favorite.name) val showIsFavorite: Boolean = hasProperty(States.is_favorite.name)
val showNotFavorite: Boolean = hasProperty(States.not_favorite.name) val showNotFavorite: Boolean = hasProperty(States.not_favorite.name)
@ -48,6 +50,8 @@ class EpisodeFilter(vararg properties: String) : Serializable {
showNotAutoDownloadable && item.isAutoDownloadEnabled -> return false showNotAutoDownloadable && item.isAutoDownloadEnabled -> return false
showHasMedia && item.media == null -> return false showHasMedia && item.media == null -> return false
showNoMedia && item.media != null -> return false showNoMedia && item.media != null -> return false
showHasComments && item.comment.isEmpty() -> return false
showNoComments && item.comment.isNotEmpty() -> return false
showIsFavorite && !item.isFavorite -> return false showIsFavorite && !item.isFavorite -> return false
showNotFavorite && item.isFavorite -> return false showNotFavorite && item.isFavorite -> return false
showQueued && !inAnyQueue(item) -> return false showQueued && !inAnyQueue(item) -> return false
@ -69,7 +73,7 @@ class EpisodeFilter(vararg properties: String) : Serializable {
val statements: MutableList<String> = ArrayList() val statements: MutableList<String> = ArrayList()
when { when {
showPlayed -> statements.add("playState >= ${PlayState.PLAYED.code}") showPlayed -> statements.add("playState >= ${PlayState.PLAYED.code}")
showUnplayed -> statements.add(" playState < ${PlayState.PLAYED.code}> ") // Match "New" items (read = -1) as well showUnplayed -> statements.add(" playState < ${PlayState.PLAYED.code} ") // Match "New" items (read = -1) as well
showNew -> statements.add("playState == -1 ") showNew -> statements.add("playState == -1 ")
} }
when { when {
@ -93,8 +97,12 @@ class EpisodeFilter(vararg properties: String) : Serializable {
showNoMedia -> statements.add("media == nil ") showNoMedia -> statements.add("media == nil ")
} }
when { when {
showIsFavorite -> statements.add("isFavorite == true ") showHasComments -> statements.add(" comment != '' ")
showNotFavorite -> statements.add("isFavorite == false ") showNoComments -> statements.add(" comment == '' ")
}
when {
showIsFavorite -> statements.add("rating == ${Rating.FAVORITE.code} ")
showNotFavorite -> statements.add("rating != ${Rating.FAVORITE.code} ")
} }
if (statements.isEmpty()) return "id > 0" if (statements.isEmpty()) return "id > 0"
@ -120,6 +128,8 @@ class EpisodeFilter(vararg properties: String) : Serializable {
not_favorite, not_favorite,
has_media, has_media,
no_media, no_media,
has_comments,
no_comments,
queued, queued,
not_queued, not_queued,
downloaded, downloaded,

View File

@ -11,6 +11,8 @@ class FeedFilter(vararg properties: String) : Serializable {
val showNotKeepUpdated: Boolean = hasProperty(States.not_keepUpdated.name) val showNotKeepUpdated: Boolean = hasProperty(States.not_keepUpdated.name)
val showGlobalPlaySpeed: Boolean = hasProperty(States.global_playSpeed.name) val showGlobalPlaySpeed: Boolean = hasProperty(States.global_playSpeed.name)
val showCustomPlaySpeed: Boolean = hasProperty(States.custom_playSpeed.name) val showCustomPlaySpeed: Boolean = hasProperty(States.custom_playSpeed.name)
val showHasComments: Boolean = hasProperty(States.has_comments.name)
val showNoComments: Boolean = hasProperty(States.no_comments.name)
val showHasSkips: Boolean = hasProperty(States.has_skips.name) val showHasSkips: Boolean = hasProperty(States.has_skips.name)
val showNoSkips: Boolean = hasProperty(States.no_skips.name) val showNoSkips: Boolean = hasProperty(States.no_skips.name)
val showAlwaysAutoDelete: Boolean = hasProperty(States.always_auto_delete.name) val showAlwaysAutoDelete: Boolean = hasProperty(States.always_auto_delete.name)
@ -36,6 +38,8 @@ class FeedFilter(vararg properties: String) : Serializable {
showNotKeepUpdated && feed.preferences?.keepUpdated != false -> return false showNotKeepUpdated && feed.preferences?.keepUpdated != false -> return false
showGlobalPlaySpeed && feed.preferences?.playSpeed != SPEED_USE_GLOBAL -> return false showGlobalPlaySpeed && feed.preferences?.playSpeed != SPEED_USE_GLOBAL -> return false
showCustomPlaySpeed && feed.preferences?.playSpeed == SPEED_USE_GLOBAL -> return false showCustomPlaySpeed && feed.preferences?.playSpeed == SPEED_USE_GLOBAL -> return false
showHasComments && feed.comment.isEmpty() -> return false
showNoComments && feed.comment.isEmpty() -> return false
showHasSkips && feed.preferences?.introSkip == 0 && feed.preferences?.endingSkip == 0 -> return false showHasSkips && feed.preferences?.introSkip == 0 && feed.preferences?.endingSkip == 0 -> return false
showNoSkips && (feed.preferences?.introSkip != 0 || feed.preferences?.endingSkip != 0) -> return false showNoSkips && (feed.preferences?.introSkip != 0 || feed.preferences?.endingSkip != 0) -> return false
showAlwaysAutoDelete && feed.preferences?.autoDeleteAction != FeedPreferences.AutoDeleteAction.ALWAYS -> return false showAlwaysAutoDelete && feed.preferences?.autoDeleteAction != FeedPreferences.AutoDeleteAction.ALWAYS -> return false
@ -60,6 +64,10 @@ class FeedFilter(vararg properties: String) : Serializable {
showHasSkips -> statements.add(" preferences.introSkip != 0 OR preferences.endingSkip != 0 ") showHasSkips -> statements.add(" preferences.introSkip != 0 OR preferences.endingSkip != 0 ")
showNoSkips -> statements.add(" preferences.introSkip == 0 AND preferences.endingSkip == 0 ") showNoSkips -> statements.add(" preferences.introSkip == 0 AND preferences.endingSkip == 0 ")
} }
when {
showHasComments -> statements.add(" comment != '' ")
showNoComments -> statements.add(" comment == '' ")
}
when { when {
showAlwaysAutoDelete -> statements.add(" preferences.autoDelete == ${FeedPreferences.AutoDeleteAction.ALWAYS.code} ") 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} ")
@ -89,6 +97,8 @@ class FeedFilter(vararg properties: String) : Serializable {
custom_playSpeed, custom_playSpeed,
has_skips, has_skips,
no_skips, no_skips,
has_comments,
no_comments,
// global_auto_delete, // global_auto_delete,
always_auto_delete, always_auto_delete,
never_auto_delete, never_auto_delete,

View File

@ -1,92 +1,99 @@
package ac.mdiq.podcini.ui.dialog package ac.mdiq.podcini.ui.dialog
import ac.mdiq.podcini.R import ac.mdiq.podcini.R
import ac.mdiq.podcini.databinding.FilterDialogBinding
import ac.mdiq.podcini.databinding.FilterDialogRowBinding
import ac.mdiq.podcini.storage.model.EpisodeFilter import ac.mdiq.podcini.storage.model.EpisodeFilter
import ac.mdiq.podcini.storage.model.FeedFilter
import ac.mdiq.podcini.ui.compose.CustomTheme
import ac.mdiq.podcini.ui.fragment.SubscriptionsFragment.Companion.TAG import ac.mdiq.podcini.ui.fragment.SubscriptionsFragment.Companion.TAG
import ac.mdiq.podcini.ui.fragment.SubscriptionsFragment.FeedFilterDialog.FeedFilterGroup.ItemProperties
import ac.mdiq.podcini.util.Logd import ac.mdiq.podcini.util.Logd
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Button import androidx.compose.foundation.BorderStroke
import android.widget.LinearLayout import androidx.compose.foundation.layout.*
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.google.android.material.bottomsheet.BottomSheetDialogFragment import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.google.android.material.button.MaterialButtonToggleGroup
abstract class EpisodeFilterDialog : BottomSheetDialogFragment() { abstract class EpisodeFilterDialog : BottomSheetDialogFragment() {
private lateinit var rows: LinearLayout
private var _binding: FilterDialogBinding? = null
private val binding get() = _binding!!
var filter: EpisodeFilter? = null var filter: EpisodeFilter? = null
private val buttonMap: MutableMap<String, Button> = mutableMapOf()
val filtersDisabled: MutableSet<FeedItemFilterGroup> = mutableSetOf() val filtersDisabled: MutableSet<FeedItemFilterGroup> = mutableSetOf()
private val filterValues: MutableSet<String> = mutableSetOf()
private val newFilterValues: Set<String>
get() {
val newFilterValues: MutableSet<String> = HashSet()
for (i in 0 until rows.childCount) {
if (rows.getChildAt(i) !is MaterialButtonToggleGroup) continue
val group = rows.getChildAt(i) as MaterialButtonToggleGroup
if (group.checkedButtonId == View.NO_ID) continue
val tag = group.findViewById<View>(group.checkedButtonId).tag as? String ?: continue
newFilterValues.add(tag)
}
return newFilterValues
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val layout = inflater.inflate(R.layout.filter_dialog, container, false) val composeView = ComposeView(requireContext()).apply {
_binding = FilterDialogBinding.bind(layout) setContent {
rows = binding.filterRows CustomTheme(requireContext()) {
Logd("EpisodeFilterDialog", "fragment onCreateView") MainView()
}
}
}
return composeView
}
//add filter rows @Composable
fun MainView() {
val textColor = MaterialTheme.colorScheme.onSurface
Column {
for (item in FeedItemFilterGroup.entries) { for (item in FeedItemFilterGroup.entries) {
// Logd("EpisodeFilterDialog", "FeedItemFilterGroup: ${item.values[0].filterId} ${item.values[1].filterId}")
if (item in filtersDisabled) continue if (item in filtersDisabled) continue
Row(modifier = Modifier.padding(2.dp).fillMaxWidth(), horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically) {
val rBinding = FilterDialogRowBinding.inflate(inflater) var selectedIndex by remember { mutableStateOf(-1) }
rBinding.filterButton1.setOnClickListener { onFilterChanged(newFilterValues) } LaunchedEffect(Unit) {
rBinding.filterButton2.setOnClickListener { onFilterChanged(newFilterValues) }
rBinding.filterButton1.setText(item.values[0].displayName)
rBinding.filterButton1.tag = item.values[0].filterId
buttonMap[item.values[0].filterId] = rBinding.filterButton1
rBinding.filterButton2.setText(item.values[1].displayName)
rBinding.filterButton2.tag = item.values[1].filterId
buttonMap[item.values[1].filterId] = rBinding.filterButton2
rBinding.filterButton1.maxLines = 3
rBinding.filterButton1.isSingleLine = false
rBinding.filterButton2.maxLines = 3
rBinding.filterButton2.isSingleLine = false
rows.addView(rBinding.root, rows.childCount - 1)
}
binding.confirmFiltermenu.setOnClickListener { dismiss() }
binding.resetFiltermenu.setOnClickListener {
onFilterChanged(emptySet())
for (i in 0 until rows.childCount) {
if (rows.getChildAt(i) is MaterialButtonToggleGroup) (rows.getChildAt(i) as MaterialButtonToggleGroup).clearChecked()
}
}
if (filter != null) { if (filter != null) {
for (filterId in filter!!.values) { if (item.values[0].filterId in filter!!.values) selectedIndex = 0
if (filterId.isNotEmpty()) { else if (item.values[1].filterId in filter!!.values) selectedIndex = 1
val button = buttonMap[filterId] }
if (button != null) (button.parent as MaterialButtonToggleGroup).check(button.id) }
OutlinedButton(modifier = Modifier.padding(2.dp), border = BorderStroke(2.dp, if (selectedIndex != 0) textColor else Color.Green),
onClick = {
if (selectedIndex != 0) {
selectedIndex = 0
filterValues.add(item.values[0].filterId)
filterValues.remove(item.values[1].filterId)
} else {
selectedIndex = -1
filterValues.remove(item.values[0].filterId)
}
onFilterChanged(filterValues)
},
) {
Text(text = stringResource(item.values[0].displayName), color = textColor)
}
Spacer(Modifier.width(5.dp))
OutlinedButton(modifier = Modifier.padding(2.dp), border = BorderStroke(2.dp, if (selectedIndex != 1) textColor else Color.Green),
onClick = {
if (selectedIndex != 1) {
selectedIndex = 1
filterValues.add(item.values[1].filterId)
filterValues.remove(item.values[0].filterId)
} else {
selectedIndex = -1
filterValues.remove(item.values[1].filterId)
}
onFilterChanged(filterValues)
},
) {
Text(text = stringResource(item.values[1].displayName), color = textColor)
}
} }
} }
} }
return layout
} }
override fun onDestroyView() { override fun onDestroyView() {
Logd(TAG, "onDestroyView") Logd(TAG, "onDestroyView")
_binding = null
super.onDestroyView() super.onDestroyView()
} }
@ -97,6 +104,7 @@ abstract class EpisodeFilterDialog : BottomSheetDialogFragment() {
PAUSED(ItemProperties(R.string.hide_paused_episodes_label, EpisodeFilter.States.paused.name), ItemProperties(R.string.not_paused, EpisodeFilter.States.not_paused.name)), PAUSED(ItemProperties(R.string.hide_paused_episodes_label, EpisodeFilter.States.paused.name), ItemProperties(R.string.not_paused, EpisodeFilter.States.not_paused.name)),
FAVORITE(ItemProperties(R.string.hide_is_favorite_label, EpisodeFilter.States.is_favorite.name), ItemProperties(R.string.not_favorite, EpisodeFilter.States.not_favorite.name)), FAVORITE(ItemProperties(R.string.hide_is_favorite_label, EpisodeFilter.States.is_favorite.name), ItemProperties(R.string.not_favorite, EpisodeFilter.States.not_favorite.name)),
MEDIA(ItemProperties(R.string.has_media, EpisodeFilter.States.has_media.name), ItemProperties(R.string.no_media, EpisodeFilter.States.no_media.name)), MEDIA(ItemProperties(R.string.has_media, EpisodeFilter.States.has_media.name), ItemProperties(R.string.no_media, EpisodeFilter.States.no_media.name)),
OPINION(ItemProperties(R.string.has_comments, EpisodeFilter.States.has_comments.name), ItemProperties(R.string.no_comments, EpisodeFilter.States.no_comments.name)),
QUEUED(ItemProperties(R.string.queued_label, EpisodeFilter.States.queued.name), ItemProperties(R.string.not_queued_label, EpisodeFilter.States.not_queued.name)), QUEUED(ItemProperties(R.string.queued_label, EpisodeFilter.States.queued.name), ItemProperties(R.string.not_queued_label, EpisodeFilter.States.not_queued.name)),
DOWNLOADED(ItemProperties(R.string.downloaded_label, EpisodeFilter.States.downloaded.name), ItemProperties(R.string.not_downloaded_label, EpisodeFilter.States.not_downloaded.name)), DOWNLOADED(ItemProperties(R.string.downloaded_label, EpisodeFilter.States.downloaded.name), ItemProperties(R.string.not_downloaded_label, EpisodeFilter.States.not_downloaded.name)),
AUTO_DOWNLOADABLE(ItemProperties(R.string.auto_downloadable_label, EpisodeFilter.States.auto_downloadable.name), ItemProperties(R.string.not_auto_downloadable_label, EpisodeFilter.States.not_auto_downloadable.name)); AUTO_DOWNLOADABLE(ItemProperties(R.string.auto_downloadable_label, EpisodeFilter.States.auto_downloadable.name), ItemProperties(R.string.not_auto_downloadable_label, EpisodeFilter.States.not_auto_downloadable.name));

View File

@ -6,6 +6,7 @@ import ac.mdiq.podcini.playback.PlaybackServiceStarter
import ac.mdiq.podcini.playback.ServiceStatusHandler import ac.mdiq.podcini.playback.ServiceStatusHandler
import ac.mdiq.podcini.playback.base.InTheatre.curEpisode import ac.mdiq.podcini.playback.base.InTheatre.curEpisode
import ac.mdiq.podcini.playback.base.InTheatre.curMedia import ac.mdiq.podcini.playback.base.InTheatre.curMedia
import ac.mdiq.podcini.playback.base.InTheatre.isCurrentlyPlaying
import ac.mdiq.podcini.playback.base.MediaPlayerBase.Companion.status import ac.mdiq.podcini.playback.base.MediaPlayerBase.Companion.status
import ac.mdiq.podcini.playback.base.PlayerStatus import ac.mdiq.podcini.playback.base.PlayerStatus
import ac.mdiq.podcini.playback.base.VideoMode import ac.mdiq.podcini.playback.base.VideoMode
@ -510,7 +511,7 @@ class AudioPlayerFragment : Fragment() {
fun onPositionUpdate(event: FlowEvent.PlaybackPositionEvent) { fun onPositionUpdate(event: FlowEvent.PlaybackPositionEvent) {
Logd(TAG, "onPositionUpdate") Logd(TAG, "onPositionUpdate")
if (!playButInit && playButRes == R.drawable.ic_play_48dp && curMedia is EpisodeMedia) { if (!playButInit && playButRes == R.drawable.ic_play_48dp && curMedia is EpisodeMedia) {
playButRes = R.drawable.ic_pause if (isCurrentlyPlaying(curMedia as? EpisodeMedia)) playButRes = R.drawable.ic_pause
playButInit = true playButInit = true
} }
@ -753,14 +754,15 @@ class AudioPlayerFragment : Fragment() {
Logd(TAG, "loadMediaInfo() curMedia: ${curMedia?.getIdentifier()}") Logd(TAG, "loadMediaInfo() curMedia: ${curMedia?.getIdentifier()}")
val actMain = (activity as MainActivity) val actMain = (activity as MainActivity)
var i = 0 var i = 0
while (curMedia == null && i++ < 6) runBlocking { delay(500) } // while (curMedia == null && i++ < 6) runBlocking { delay(500) }
if (curMedia == null) { if (curMedia == null) {
if (actMain.isPlayerVisible()) actMain.setPlayerVisible(false) if (actMain.isPlayerVisible()) actMain.setPlayerVisible(false)
return return
} }
if (!actMain.isPlayerVisible()) actMain.setPlayerVisible(true)
if (!loadItemsRunning) { if (!loadItemsRunning) {
loadItemsRunning = true loadItemsRunning = true
if (!actMain.isPlayerVisible()) actMain.setPlayerVisible(true) // if (!actMain.isPlayerVisible()) actMain.setPlayerVisible(true)
val curMediaChanged = currentMedia == null || curMedia?.getIdentifier() != currentMedia?.getIdentifier() val curMediaChanged = currentMedia == null || curMedia?.getIdentifier() != currentMedia?.getIdentifier()
if (curMedia?.getIdentifier() != currentMedia?.getIdentifier()) updateUi(curMedia!!) if (curMedia?.getIdentifier() != currentMedia?.getIdentifier()) updateUi(curMedia!!)
if (!isCollapsed && curMediaChanged) { if (!isCollapsed && curMediaChanged) {

View File

@ -72,6 +72,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
@ -650,7 +651,7 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
Dialog(onDismissRequest = onDismissRequest) { Dialog(onDismissRequest = onDismissRequest) {
Surface(shape = RoundedCornerShape(16.dp)) { Surface(shape = RoundedCornerShape(16.dp)) {
Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) { Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) {
for (rating in Rating.entries) { for (rating in Rating.entries.reversed()) {
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(4.dp).clickable { Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(4.dp).clickable {
for (item in selected) Feeds.setRating(item, rating.code) for (item in selected) Feeds.setRating(item, rating.code)
onDismissRequest() onDismissRequest()
@ -1105,77 +1106,72 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
} }
class FeedFilterDialog : BottomSheetDialogFragment() { class FeedFilterDialog : BottomSheetDialogFragment() {
private lateinit var rows: LinearLayout
private var _binding: FilterDialogBinding? = null
private val binding get() = _binding!!
var filter: FeedFilter? = null var filter: FeedFilter? = null
private val buttonMap: MutableMap<String, Button> = mutableMapOf() private val filterValues: MutableSet<String> = mutableSetOf()
private val newFilterValues: Set<String>
get() {
val newFilterValues: MutableSet<String> = HashSet()
for (i in 0 until rows.childCount) {
if (rows.getChildAt(i) !is MaterialButtonToggleGroup) continue
val group = rows.getChildAt(i) as MaterialButtonToggleGroup
if (group.checkedButtonId == View.NO_ID) continue
val tag = group.findViewById<View>(group.checkedButtonId).tag as? String ?: continue
newFilterValues.add(tag)
}
return newFilterValues
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val layout = inflater.inflate(R.layout.filter_dialog, container, false) val composeView = ComposeView(requireContext()).apply {
_binding = FilterDialogBinding.bind(layout) setContent {
rows = binding.filterRows CustomTheme(requireContext()) {
Logd("FeedFilterDialog", "fragment onCreateView") MainView()
}
}
}
return composeView
}
//add filter rows @Composable
fun MainView() {
val textColor = MaterialTheme.colorScheme.onSurface
Column {
for (item in FeedFilterGroup.entries) { for (item in FeedFilterGroup.entries) {
// Logd("EpisodeFilterDialog", "FeedItemFilterGroup: ${item.values[0].filterId} ${item.values[1].filterId}") Row(modifier = Modifier.padding(2.dp).fillMaxWidth(), horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically) {
val rBinding = FilterDialogRowBinding.inflate(inflater) var selectedIndex by remember { mutableStateOf(-1) }
// rowBinding.root.addOnButtonCheckedListener { _: MaterialButtonToggleGroup?, _: Int, _: Boolean -> LaunchedEffect(Unit) {
// onFilterChanged(newFilterValues)
// }
rBinding.filterButton1.setOnClickListener { onFilterChanged(newFilterValues) }
rBinding.filterButton2.setOnClickListener { onFilterChanged(newFilterValues) }
rBinding.filterButton1.setText(item.values[0].displayName)
rBinding.filterButton1.tag = item.values[0].filterId
buttonMap[item.values[0].filterId] = rBinding.filterButton1
rBinding.filterButton2.setText(item.values[1].displayName)
rBinding.filterButton2.tag = item.values[1].filterId
buttonMap[item.values[1].filterId] = rBinding.filterButton2
rBinding.filterButton1.maxLines = 3
rBinding.filterButton1.isSingleLine = false
rBinding.filterButton2.maxLines = 3
rBinding.filterButton2.isSingleLine = false
rows.addView(rBinding.root, rows.childCount - 1)
}
binding.confirmFiltermenu.setOnClickListener { dismiss() }
binding.resetFiltermenu.setOnClickListener {
onFilterChanged(emptySet())
for (i in 0 until rows.childCount) {
if (rows.getChildAt(i) is MaterialButtonToggleGroup) (rows.getChildAt(i) as MaterialButtonToggleGroup).clearChecked()
}
}
if (filter != null) { if (filter != null) {
for (filterId in filter!!.values) { if (item.values[0].filterId in filter!!.values) selectedIndex = 0
if (filterId.isNotEmpty()) { else if (item.values[1].filterId in filter!!.values) selectedIndex = 1
val button = buttonMap[filterId] }
if (button != null) (button.parent as MaterialButtonToggleGroup).check(button.id) }
OutlinedButton(modifier = Modifier.padding(2.dp), border = BorderStroke(2.dp, if (selectedIndex != 0) textColor else Color.Green),
onClick = {
if (selectedIndex != 0) {
selectedIndex = 0
filterValues.add(item.values[0].filterId)
filterValues.remove(item.values[1].filterId)
} else {
selectedIndex = -1
filterValues.remove(item.values[0].filterId)
}
onFilterChanged(filterValues)
},
) {
Text(text = stringResource(item.values[0].displayName), color = textColor)
}
Spacer(Modifier.width(5.dp))
OutlinedButton(modifier = Modifier.padding(2.dp), border = BorderStroke(2.dp, if (selectedIndex != 1) textColor else Color.Green),
onClick = {
if (selectedIndex != 1) {
selectedIndex = 1
filterValues.add(item.values[1].filterId)
filterValues.remove(item.values[0].filterId)
} else {
selectedIndex = -1
filterValues.remove(item.values[1].filterId)
}
onFilterChanged(filterValues)
},
) {
Text(text = stringResource(item.values[1].displayName), color = textColor)
}
} }
} }
} }
return layout
} }
override fun onDestroyView() { override fun onDestroyView() {
Logd(TAG, "onDestroyView") Logd(TAG, "onDestroyView")
_binding = null // _binding = null
super.onDestroyView() super.onDestroyView()
} }
@ -1188,6 +1184,7 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
enum class FeedFilterGroup(vararg values: ItemProperties) { enum class FeedFilterGroup(vararg values: ItemProperties) {
KEEP_UPDATED(ItemProperties(R.string.keep_updated, FeedFilter.States.keepUpdated.name), ItemProperties(R.string.not_keep_updated, FeedFilter.States.not_keepUpdated.name)), KEEP_UPDATED(ItemProperties(R.string.keep_updated, FeedFilter.States.keepUpdated.name), ItemProperties(R.string.not_keep_updated, FeedFilter.States.not_keepUpdated.name)),
PLAY_SPEED(ItemProperties(R.string.global_speed, FeedFilter.States.global_playSpeed.name), ItemProperties(R.string.custom_speed, FeedFilter.States.custom_playSpeed.name)), PLAY_SPEED(ItemProperties(R.string.global_speed, FeedFilter.States.global_playSpeed.name), ItemProperties(R.string.custom_speed, FeedFilter.States.custom_playSpeed.name)),
OPINION(ItemProperties(R.string.has_comments, FeedFilter.States.has_comments.name), ItemProperties(R.string.no_comments, FeedFilter.States.no_comments.name)),
SKIPS(ItemProperties(R.string.has_skips, FeedFilter.States.has_skips.name), ItemProperties(R.string.no_skips, FeedFilter.States.no_skips.name)), SKIPS(ItemProperties(R.string.has_skips, FeedFilter.States.has_skips.name), ItemProperties(R.string.no_skips, FeedFilter.States.no_skips.name)),
AUTO_DELETE(ItemProperties(R.string.always_auto_delete, FeedFilter.States.always_auto_delete.name), ItemProperties(R.string.never_auto_delete, FeedFilter.States.never_auto_delete.name)), AUTO_DELETE(ItemProperties(R.string.always_auto_delete, FeedFilter.States.always_auto_delete.name), ItemProperties(R.string.never_auto_delete, FeedFilter.States.never_auto_delete.name)),
AUTO_DOWNLOAD(ItemProperties(R.string.auto_download, FeedFilter.States.autoDownload.name), ItemProperties(R.string.not_auto_download, FeedFilter.States.not_autoDownload.name)); AUTO_DOWNLOAD(ItemProperties(R.string.auto_download, FeedFilter.States.autoDownload.name), ItemProperties(R.string.not_auto_download, FeedFilter.States.not_autoDownload.name));

View File

@ -1,43 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/filter_dialog">
<LinearLayout
android:id="@+id/filter_rows"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingLeft="24dp"
android:paddingTop="24dp"
android:paddingRight="24dp"
android:paddingBottom="8dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/resetFiltermenu"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/reset"
style="@style/Widget.MaterialComponents.Button.TextButton" />
<com.google.android.material.button.MaterialButton
android:id="@+id/confirmFiltermenu"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/confirm_label"
style="@style/Widget.MaterialComponents.Button.TextButton" />
</LinearLayout>
</LinearLayout>
</ScrollView>

View File

@ -1,25 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.button.MaterialButtonToggleGroup
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/buttonGroup"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:weightSum="2"
app:singleSelection="true">
<Button
android:id="@+id/filterButton1"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
style="@style/OutlinedButtonBetterContrast" />
<Button
android:id="@+id/filterButton2"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
style="@style/OutlinedButtonBetterContrast" />
</com.google.android.material.button.MaterialButtonToggleGroup>

View File

@ -903,6 +903,8 @@
<string name="custom_speed">Custom play speed</string> <string name="custom_speed">Custom play speed</string>
<string name="has_skips">Skips set</string> <string name="has_skips">Skips set</string>
<string name="no_skips">No Skips set</string> <string name="no_skips">No Skips set</string>
<string name="has_comments">Has commented</string>
<string name="no_comments">Not commented</string>
<string name="always_auto_delete">Always auto delete</string> <string name="always_auto_delete">Always auto delete</string>
<string name="never_auto_delete">Never auto delete</string> <string name="never_auto_delete">Never auto delete</string>
<string name="auto_download">Auto download enabled</string> <string name="auto_download">Auto download enabled</string>

View File

@ -1,3 +1,13 @@
# 6.12.2
* fixed play not resuming after interruption (watch for any side effects)
* fixed incorrect initial play button on PlayerUI
* fixed startup delay when curMedia is null
* rating list in popup for Subscriptions is reversed (favorite on top)
* first migration of Episodes and Feeds filters to Jetpack Compose
* added has/no comments in the filters
* fixed some errors in Episodes filter
# 6.12.1 # 6.12.1
* fixed circular calling functions when PlayerDetailed view is open * fixed circular calling functions when PlayerDetailed view is open

View File

@ -0,0 +1,9 @@
Version 6.12.2
* fixed play not resuming after interruption (watch for any side effects)
* fixed incorrect initial play button on PlayerUI
* fixed startup delay when curMedia is null
* rating list in popup for Subscriptions is reversed (favorite on top)
* first migration of Episodes and Feeds filters to Jetpack Compose
* added has/no comments in the filters
* fixed some errors in Episodes filter