Skip to content

Commit

Permalink
6.12.3 commit
Browse files Browse the repository at this point in the history
  • Loading branch information
XilinJia committed Oct 24, 2024
1 parent d3ca132 commit 7b6d976
Show file tree
Hide file tree
Showing 18 changed files with 973 additions and 401 deletions.
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,6 @@ While podcast subscriptions' OPML files (from AntennaPod or any other sources) c

### Podcast list and Episode list

* A whole new interface of the Subscriptions page showing only the feeds with tags as filters, no longer having tags as folders in the page,
* Subscriptions page by default has a list layout and can be opted for a grid layout
* New and efficient ways of click and long-click operations on lists:
* click on title area opens the podcast/episode
Expand All @@ -99,14 +98,14 @@ While podcast subscriptions' OPML files (from AntennaPod or any other sources) c
* Left and right swipe actions on lists now have telltales and can be configured on the spot
* Played or new episodes have clearer markings
* Sort dialog no longer dims the main view
* download date can be used to sort both feeds and episodes
* Subscriptions view has a filter based on feed preferences, in the same style as episodes filter
* An all new way of filtering for both podcasts and episodes
* Subscriptions sorting is now bi-directional based on various explicit measures, and sorting info is shown on every feed (List Layout only)
* in Subscriptions view, click on cover image of a feed opens the FeedInfo view (not FeedEpisodes view)
* in all episodes list views, click on an episode image brings up the FeedInfo view
* in episode list view, if episode has no media, TTS button is shown for fetching transcript (if not exist) and then generating audio file from the transcript. TTS audio files are playable in the same way as local media (with speed setting, pause and rewind/forward)
* on action bar of FeedEpisodes view there is a direct access to Queue
* Long-press filter button in FeedEpisodes view enables/disables filters without changing filter settings
* Long-press on the action button on the right of any episode in the list brings up more options
* History view shows time of last play, and allows filters and sorts

### Podcast/Episode
Expand Down
4 changes: 2 additions & 2 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ android {
testApplicationId "ac.mdiq.podcini.tests"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

versionCode 3020280
versionName "6.12.2"
versionCode 3020281
versionName "6.12.3"

applicationId "ac.mdiq.podcini.R"
def commit = ""
Expand Down
247 changes: 174 additions & 73 deletions app/src/main/kotlin/ac/mdiq/podcini/storage/model/EpisodeFilter.kt

Large diffs are not rendered by default.

178 changes: 120 additions & 58 deletions app/src/main/kotlin/ac/mdiq/podcini/storage/model/FeedFilter.kt
Original file line number Diff line number Diff line change
@@ -1,91 +1,115 @@
package ac.mdiq.podcini.storage.model

import ac.mdiq.podcini.R
import ac.mdiq.podcini.storage.model.Feed.Companion.MAX_SYNTHETIC_ID
import ac.mdiq.podcini.storage.model.FeedPreferences.Companion.SPEED_USE_GLOBAL
import ac.mdiq.podcini.util.Logd
import java.io.Serializable

class FeedFilter(vararg properties: String) : Serializable {

private val properties: Array<String> = arrayOf(*properties.filter { it.isNotEmpty() }.map {it.trim()}.toTypedArray())

val showKeepUpdated: Boolean = hasProperty(States.keepUpdated.name)
val showNotKeepUpdated: Boolean = hasProperty(States.not_keepUpdated.name)
val showGlobalPlaySpeed: Boolean = hasProperty(States.global_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 showNoSkips: Boolean = hasProperty(States.no_skips.name)
val showAlwaysAutoDelete: Boolean = hasProperty(States.always_auto_delete.name)
val showNeverAutoDelete: Boolean = hasProperty(States.never_auto_delete.name)
val showAutoDownload: Boolean = hasProperty(States.autoDownload.name)
val showNotAutoDownload: Boolean = hasProperty(States.not_autoDownload.name)
class FeedFilter(vararg properties_: String) : Serializable {
val properties: HashSet<String> = setOf(*properties_).filter { it.isNotEmpty() }.map {it.trim()}.toHashSet()

constructor(properties: String) : this(*(properties.split(",").toTypedArray()))

private fun hasProperty(property: String): Boolean {
return listOf(*properties).contains(property)
}

val values: Array<String>
get() = properties.clone()

val valuesList: List<String>
get() = listOf(*properties)
// fun matches(feed: Feed): Boolean {
// when {
// properties.contains(States.keepUpdated.name) && feed.preferences?.keepUpdated != true -> return false
// properties.contains(States.not_keepUpdated.name) && feed.preferences?.keepUpdated != false -> return false
// properties.contains(States.global_playSpeed.name) && feed.preferences?.playSpeed != SPEED_USE_GLOBAL -> return false
// properties.contains(States.custom_playSpeed.name) && feed.preferences?.playSpeed == SPEED_USE_GLOBAL -> return false
// properties.contains(States.has_comments.name) && feed.comment.isEmpty() -> return false
// properties.contains(States.no_comments.name) && feed.comment.isNotEmpty() -> return false
// properties.contains(States.has_skips.name) && feed.preferences?.introSkip == 0 && feed.preferences?.endingSkip == 0 -> return false
// properties.contains(States.no_skips.name) && (feed.preferences?.introSkip != 0 || feed.preferences?.endingSkip != 0) -> return false
// properties.contains(States.global_auto_delete.name) && feed.preferences?.autoDeleteAction != FeedPreferences.AutoDeleteAction.GLOBAL -> return false
// properties.contains(States.always_auto_delete.name) && feed.preferences?.autoDeleteAction != FeedPreferences.AutoDeleteAction.ALWAYS -> return false
// properties.contains(States.never_auto_delete.name) && feed.preferences?.autoDeleteAction != FeedPreferences.AutoDeleteAction.NEVER -> return false
// properties.contains(States.autoDownload.name) && feed.preferences?.autoDownload != true -> return false
// properties.contains(States.not_autoDownload.name) && feed.preferences?.autoDownload != false -> return false
// properties.contains(States.unrated.name) && feed.rating != Rating.UNRATED.code -> return false
// properties.contains(States.trash.name) && feed.rating != Rating.TRASH.code -> return false
// properties.contains(States.bad.name) && feed.rating != Rating.BAD.code -> return false
// properties.contains(States.neutral.name) && feed.rating != Rating.NEUTRAL.code -> return false
// properties.contains(States.good.name) && feed.rating != Rating.GOOD.code -> return false
// properties.contains(States.favorite.name) && feed.rating != Rating.FAVORITE.code -> return false
// else -> return true
// }
// }

fun matches(feed: Feed): Boolean {
fun queryString(): String {
val statements: MutableList<String> = mutableListOf()
when {
showKeepUpdated && feed.preferences?.keepUpdated != true -> return false
showNotKeepUpdated && feed.preferences?.keepUpdated != false -> return false
showGlobalPlaySpeed && 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
showNoSkips && (feed.preferences?.introSkip != 0 || feed.preferences?.endingSkip != 0) -> return false
showAlwaysAutoDelete && feed.preferences?.autoDeleteAction != FeedPreferences.AutoDeleteAction.ALWAYS -> return false
showNeverAutoDelete && feed.preferences?.autoDeleteAction != FeedPreferences.AutoDeleteAction.NEVER -> return false
showAutoDownload && feed.preferences?.autoDownload != true -> return false
showNotAutoDownload && feed.preferences?.autoDownload != false -> return false
else -> return true
properties.contains(States.keepUpdated.name) -> statements.add("preferences.keepUpdated == true ")
properties.contains(States.not_keepUpdated.name) -> statements.add(" preferences.keepUpdated == false ")
}
}

fun queryString(): String {
val statements: MutableList<String> = ArrayList()
when {
showKeepUpdated -> statements.add("preferences.keepUpdated == true ")
showNotKeepUpdated -> statements.add(" preferences.keepUpdated == false ")
properties.contains(States.global_playSpeed.name) -> statements.add(" preferences.playSpeed == $SPEED_USE_GLOBAL ")
properties.contains(States.custom_playSpeed.name) -> statements.add(" preferences.playSpeed != $SPEED_USE_GLOBAL ")
}
when {
showGlobalPlaySpeed -> statements.add(" preferences.playSpeed == ${SPEED_USE_GLOBAL} ")
showCustomPlaySpeed -> statements.add(" preferences.playSpeed != $SPEED_USE_GLOBAL ")
properties.contains(States.has_skips.name) -> statements.add(" preferences.introSkip != 0 OR preferences.endingSkip != 0 ")
properties.contains(States.no_skips.name) -> statements.add(" preferences.introSkip == 0 AND preferences.endingSkip == 0 ")
}
when {
showHasSkips -> statements.add(" preferences.introSkip != 0 OR preferences.endingSkip != 0 ")
showNoSkips -> statements.add(" preferences.introSkip == 0 AND preferences.endingSkip == 0 ")
properties.contains(States.has_comments.name) -> statements.add(" comment != '' ")
properties.contains(States.no_comments.name) -> statements.add(" comment == '' ")
}
when {
showHasComments -> statements.add(" comment != '' ")
showNoComments -> statements.add(" comment == '' ")
properties.contains(States.synthetic.name) -> statements.add(" id < $MAX_SYNTHETIC_ID ")
properties.contains(States.normal.name) -> statements.add(" id > $MAX_SYNTHETIC_ID ")
}
when {
showAlwaysAutoDelete -> statements.add(" preferences.autoDelete == ${FeedPreferences.AutoDeleteAction.ALWAYS.code} ")
showNeverAutoDelete -> statements.add(" preferences.playSpeed == ${FeedPreferences.AutoDeleteAction.NEVER.code} ")
properties.contains(States.has_video.name) -> statements.add(" hasVideoMedia == true ")
properties.contains(States.no_video.name) -> statements.add(" hasVideoMedia == false ")
}
when {
showAutoDownload -> statements.add(" preferences.autoDownload == true ")
showNotAutoDownload -> statements.add(" preferences.autoDownload == false ")
properties.contains(States.youtube.name) -> statements.add(" downloadUrl CONTAINS[c] 'youtube' OR link CONTAINS[c] 'youtube' OR downloadUrl CONTAINS[c] 'youtu.be' OR link CONTAINS[c] 'youtu.be' ")
properties.contains(States.rss.name) -> statements.add(" downloadUrl NOT CONTAINS[c] 'youtube' AND link NOT CONTAINS[c] 'youtube' AND downloadUrl NOT CONTAINS[c] 'youtu.be' AND link NOT CONTAINS[c] 'youtu.be' ")
}

val ratingQuerys = mutableListOf<String>()
if (properties.contains(States.unrated.name)) ratingQuerys.add(" rating == ${Rating.UNRATED.code} ")
if (properties.contains(States.trash.name)) ratingQuerys.add(" rating == ${Rating.TRASH.code} ")
if (properties.contains(States.bad.name)) ratingQuerys.add(" rating == ${Rating.BAD.code} ")
if (properties.contains(States.neutral.name)) ratingQuerys.add(" rating == ${Rating.NEUTRAL.code} ")
if (properties.contains(States.good.name)) ratingQuerys.add(" rating == ${Rating.GOOD.code} ")
if (properties.contains(States.favorite.name)) ratingQuerys.add(" rating == ${Rating.FAVORITE.code} ")
if (ratingQuerys.isNotEmpty()) {
val query = StringBuilder(" (" + ratingQuerys[0])
if (ratingQuerys.size > 1) for (r in statements.subList(1, ratingQuerys.size)) {
query.append(" OR ")
query.append(r)
}
query.append(") ")
statements.add(query.toString())
}

val audoDeleteQuerys = mutableListOf<String>()
if (properties.contains(States.global_auto_delete.name)) audoDeleteQuerys.add(" preferences.autoDelete == ${FeedPreferences.AutoDeleteAction.GLOBAL.code} ")
if (properties.contains(States.always_auto_delete.name)) audoDeleteQuerys.add(" preferences.autoDelete == ${FeedPreferences.AutoDeleteAction.ALWAYS.code} ")
if (properties.contains(States.never_auto_delete.name)) audoDeleteQuerys.add(" preferences.playSpeed == ${FeedPreferences.AutoDeleteAction.NEVER.code} ")
if (audoDeleteQuerys.isNotEmpty()) {
val query = StringBuilder(" (" + audoDeleteQuerys[0])
if (audoDeleteQuerys.size > 1) for (r in statements.subList(1, audoDeleteQuerys.size)) {
query.append(" OR ")
query.append(r)
}
query.append(") ")
Logd("FeedFilter", "audoDeleteQueues: ${query}")
statements.add(query.toString())
}
when {
properties.contains(States.autoDownload.name) -> statements.add(" preferences.autoDownload == true ")
properties.contains(States.not_autoDownload.name) -> statements.add(" preferences.autoDownload == false ")
}
if (statements.isEmpty()) return "id > 0"

val query = StringBuilder(" (" + statements[0])
for (r in statements.subList(1, statements.size)) {
if (statements.size > 1) for (r in statements.subList(1, statements.size)) {
query.append(" AND ")
query.append(r)
}
query.append(") ")

return query.toString()
}

Expand All @@ -99,13 +123,51 @@ class FeedFilter(vararg properties: String) : Serializable {
no_skips,
has_comments,
no_comments,
// global_auto_delete,
has_video,
no_video,
youtube,
rss,
synthetic,
normal,
global_auto_delete,
always_auto_delete,
never_auto_delete,
autoDownload,
not_autoDownload,
unrated,
trash,
bad,
neutral,
good,
favorite,
}

enum class FeedFilterGroup(val nameRes: Int, vararg values: ItemProperties) {
KEEP_UPDATED(R.string.keep_updated, ItemProperties(R.string.yes, States.keepUpdated.name), ItemProperties(R.string.no, States.not_keepUpdated.name)),
PLAY_SPEED(R.string.play_speed, ItemProperties(R.string.global_speed, States.global_playSpeed.name), ItemProperties(R.string.custom_speed, States.custom_playSpeed.name)),
OPINION(R.string.commented, ItemProperties(R.string.yes, States.has_comments.name), ItemProperties(R.string.no, States.no_comments.name)),
HAS_VIDEO(R.string.has_video, ItemProperties(R.string.yes, States.has_video.name), ItemProperties(R.string.no, States.no_video.name)),
ORIGIN(R.string.feed_origin, ItemProperties(R.string.youtube, States.youtube.name), ItemProperties(R.string.rss, States.rss.name)),
TYPE(R.string.feed_type, ItemProperties(R.string.synthetic, States.synthetic.name), ItemProperties(R.string.normal, States.normal.name)),
SKIPS(R.string.has_skips, ItemProperties(R.string.yes, States.has_skips.name), ItemProperties(R.string.no, States.no_skips.name)),
RATING(R.string.rating_label, ItemProperties(R.string.unrated, States.unrated.name),
ItemProperties(R.string.trash, States.trash.name),
ItemProperties(R.string.bad, States.bad.name),
ItemProperties(R.string.neutral, States.neutral.name),
ItemProperties(R.string.good, States.good.name),
ItemProperties(R.string.favorite, States.favorite.name),
),
AUTO_DELETE(R.string.auto_delete, ItemProperties(R.string.always, States.always_auto_delete.name),
ItemProperties(R.string.never, States.never_auto_delete.name),
ItemProperties(R.string.global, States.global_auto_delete.name), ),
AUTO_DOWNLOAD(R.string.auto_download, ItemProperties(R.string.yes, States.autoDownload.name), ItemProperties(R.string.no, States.not_autoDownload.name));

@JvmField
val values: Array<ItemProperties> = arrayOf(*values)

class ItemProperties(@JvmField val displayName: Int, @JvmField val filterId: String)
}

companion object {
@JvmStatic
fun unfiltered(): FeedFilter {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ enum class MediaType {
// "application/x-flac"
// ))

private val AUDIO_APPLICATION_MIME_STRINGS: HashSet<String> = hashSetOf(
val AUDIO_APPLICATION_MIME_STRINGS: HashSet<String> = hashSetOf(
"application/ogg",
"application/opus",
"application/x-flac"
Expand Down
19 changes: 19 additions & 0 deletions app/src/main/kotlin/ac/mdiq/podcini/ui/compose/Composables.kt
Original file line number Diff line number Diff line change
Expand Up @@ -121,3 +121,22 @@ fun LargeTextEditingDialog(textState: TextFieldValue, onTextChange: (TextFieldVa
}
}
}

@Composable
fun NonlazyGrid(columns: Int, itemCount: Int, modifier: Modifier = Modifier, content: @Composable() (Int) -> Unit) {
Column(modifier = modifier) {
var rows = (itemCount / columns)
if (itemCount.mod(columns) > 0) rows += 1
for (rowId in 0 until rows) {
val firstIndex = rowId * columns
Row {
for (columnId in 0 until columns) {
val index = firstIndex + columnId
Box(modifier = Modifier.fillMaxWidth().weight(1f)) {
if (index < itemCount) content(index)
}
}
}
}
}
}
Loading

0 comments on commit 7b6d976

Please sign in to comment.