6.12.4 commit
This commit is contained in:
parent
7b6d976c9a
commit
15bc1efdca
|
@ -31,8 +31,8 @@ android {
|
|||
testApplicationId "ac.mdiq.podcini.tests"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
versionCode 3020281
|
||||
versionName "6.12.3"
|
||||
versionCode 3020282
|
||||
versionName "6.12.4"
|
||||
|
||||
applicationId "ac.mdiq.podcini.R"
|
||||
def commit = ""
|
||||
|
|
|
@ -89,6 +89,7 @@ class Episode : RealmObject {
|
|||
var isFavorite: Boolean = (rating == Rating.FAVORITE.code)
|
||||
private set
|
||||
|
||||
@FullText
|
||||
var comment: String = ""
|
||||
|
||||
@Ignore
|
||||
|
|
|
@ -2,7 +2,6 @@ package ac.mdiq.podcini.storage.model
|
|||
|
||||
import ac.mdiq.podcini.R
|
||||
import ac.mdiq.podcini.storage.database.Queues.inAnyQueue
|
||||
import ac.mdiq.podcini.storage.model.MediaType.Companion.AUDIO_APPLICATION_MIME_STRINGS
|
||||
import java.io.Serializable
|
||||
|
||||
class EpisodeFilter(vararg properties_: String) : Serializable {
|
||||
|
@ -20,29 +19,6 @@ class EpisodeFilter(vararg properties_: String) : Serializable {
|
|||
|
||||
constructor(properties: String) : this(*(properties.split(",").toTypedArray()))
|
||||
|
||||
// fun matches(item: Episode): Boolean {
|
||||
// when {
|
||||
// showNew && !item.isNew -> return false
|
||||
// showPlayed && item.playState < PlayState.PLAYED.code -> return false
|
||||
// showUnplayed && item.playState >= PlayState.PLAYED.code -> return false
|
||||
// properties.contains(States.paused.name) && !item.isInProgress -> return false
|
||||
// properties.contains(States.not_paused.name) && item.isInProgress -> return false
|
||||
// showDownloaded && !item.isDownloaded -> return false
|
||||
// showNotDownloaded && item.isDownloaded -> return false
|
||||
// properties.contains(States.auto_downloadable.name) && !item.isAutoDownloadEnabled -> return false
|
||||
// properties.contains(States.not_auto_downloadable.name) && item.isAutoDownloadEnabled -> return false
|
||||
// properties.contains(States.has_media.name) && item.media == null -> return false
|
||||
// properties.contains(States.no_media.name) && item.media != null -> return false
|
||||
// properties.contains(States.has_comments.name) && item.comment.isEmpty() -> return false
|
||||
// properties.contains(States.no_comments.name) && item.comment.isNotEmpty() -> return false
|
||||
// showIsFavorite && !item.isFavorite -> return false
|
||||
// showNotFavorite && item.isFavorite -> 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 {
|
||||
return when {
|
||||
|
@ -64,10 +40,10 @@ class EpisodeFilter(vararg properties_: String) : Serializable {
|
|||
if (properties.contains(States.unknown.name)) mediaTypeQuerys.add(" media == nil OR media.mimeType == nil OR media.mimeType == '' ")
|
||||
if (properties.contains(States.audio.name)) mediaTypeQuerys.add(" media.mimeType BEGINSWITH 'audio' ")
|
||||
if (properties.contains(States.video.name)) mediaTypeQuerys.add(" media.mimeType BEGINSWITH 'video' ")
|
||||
if (properties.contains(States.audio_app.name)) mediaTypeQuerys.add(" media.mimeType IN ${AUDIO_APPLICATION_MIME_STRINGS.toList()} ")
|
||||
if (properties.contains(States.audio_app.name)) mediaTypeQuerys.add(" media.mimeType IN {\"application/ogg\", \"application/opus\", \"application/x-flac\"} ")
|
||||
if (mediaTypeQuerys.isNotEmpty()) {
|
||||
val query = StringBuilder(" (" + mediaTypeQuerys[0])
|
||||
if (mediaTypeQuerys.size > 1) for (r in statements.subList(1, mediaTypeQuerys.size)) {
|
||||
if (mediaTypeQuerys.size > 1) for (r in mediaTypeQuerys.subList(1, mediaTypeQuerys.size)) {
|
||||
query.append(" OR ")
|
||||
query.append(r)
|
||||
}
|
||||
|
@ -84,7 +60,7 @@ class EpisodeFilter(vararg properties_: String) : Serializable {
|
|||
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)) {
|
||||
if (ratingQuerys.size > 1) for (r in ratingQuerys.subList(1, ratingQuerys.size)) {
|
||||
query.append(" OR ")
|
||||
query.append(r)
|
||||
}
|
||||
|
@ -106,7 +82,7 @@ class EpisodeFilter(vararg properties_: String) : Serializable {
|
|||
if (properties.contains(States.ignored.name)) stateQuerys.add(" playState == ${PlayState.IGNORED.code} ")
|
||||
if (stateQuerys.isNotEmpty()) {
|
||||
val query = StringBuilder(" (" + stateQuerys[0])
|
||||
if (stateQuerys.size > 1) for (r in statements.subList(1, stateQuerys.size)) {
|
||||
if (stateQuerys.size > 1) for (r in stateQuerys.subList(1, stateQuerys.size)) {
|
||||
query.append(" OR ")
|
||||
query.append(r)
|
||||
}
|
||||
|
@ -198,11 +174,10 @@ class EpisodeFilter(vararg properties_: String) : Serializable {
|
|||
favorite,
|
||||
}
|
||||
|
||||
enum class EpisodesFilterGroup(val nameRes: Int, vararg values: ItemProperties) {
|
||||
enum class EpisodesFilterGroup(val nameRes: Int, vararg values_: ItemProperties) {
|
||||
// PLAYED(ItemProperties(R.string.hide_played_episodes_label, States.played.name), ItemProperties(R.string.not_played, States.unplayed.name)),
|
||||
// PAUSED(ItemProperties(R.string.hide_paused_episodes_label, States.paused.name), ItemProperties(R.string.not_paused, States.not_paused.name)),
|
||||
// FAVORITE(ItemProperties(R.string.hide_is_favorite_label, States.is_favorite.name), ItemProperties(R.string.not_favorite, States.not_favorite.name)),
|
||||
MEDIA(R.string.has_media, ItemProperties(R.string.yes, States.has_media.name), ItemProperties(R.string.no, States.no_media.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),
|
||||
|
@ -224,6 +199,7 @@ class EpisodeFilter(vararg properties_: String) : Serializable {
|
|||
),
|
||||
OPINION(R.string.has_comments, ItemProperties(R.string.yes, States.has_comments.name), ItemProperties(R.string.no, States.no_comments.name)),
|
||||
// QUEUED(ItemProperties(R.string.queued_label, States.queued.name), ItemProperties(R.string.not_queued_label, States.not_queued.name)),
|
||||
MEDIA(R.string.has_media, ItemProperties(R.string.yes, States.has_media.name), ItemProperties(R.string.no, States.no_media.name)),
|
||||
DOWNLOADED(R.string.downloaded_label, ItemProperties(R.string.yes, States.downloaded.name), ItemProperties(R.string.no, States.not_downloaded.name)),
|
||||
CHAPTERS(R.string.has_chapters, ItemProperties(R.string.yes, States.has_chapters.name), ItemProperties(R.string.no, States.no_chapters.name)),
|
||||
MEDIA_TYPE(R.string.media_type, ItemProperties(R.string.unknown, States.unknown.name),
|
||||
|
@ -233,10 +209,9 @@ class EpisodeFilter(vararg properties_: String) : Serializable {
|
|||
),
|
||||
AUTO_DOWNLOADABLE(R.string.auto_downloadable_label, ItemProperties(R.string.yes, States.auto_downloadable.name), ItemProperties(R.string.no, States.not_auto_downloadable.name));
|
||||
|
||||
@JvmField
|
||||
val values: Array<ItemProperties> = arrayOf(*values)
|
||||
val values: Array<ItemProperties> = arrayOf(*values_)
|
||||
|
||||
class ItemProperties(@JvmField val displayName: Int, @JvmField val filterId: String)
|
||||
class ItemProperties(val displayName: Int, val filterId: String)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
@ -49,6 +49,7 @@ class Feed : RealmObject {
|
|||
|
||||
@FullText
|
||||
var author: String? = null
|
||||
|
||||
var imageUrl: String? = null
|
||||
|
||||
var episodes: RealmList<Episode> = realmListOf()
|
||||
|
@ -98,6 +99,7 @@ class Feed : RealmObject {
|
|||
|
||||
var rating: Int = Rating.NEUTRAL.code
|
||||
|
||||
@FullText
|
||||
var comment: String = ""
|
||||
|
||||
/**
|
||||
|
|
|
@ -11,37 +11,17 @@ class FeedFilter(vararg properties_: String) : Serializable {
|
|||
|
||||
constructor(properties: String) : this(*(properties.split(",").toTypedArray()))
|
||||
|
||||
// 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 queryString(): String {
|
||||
val statements: MutableList<String> = mutableListOf()
|
||||
when {
|
||||
properties.contains(States.keepUpdated.name) -> statements.add("preferences.keepUpdated == true ")
|
||||
properties.contains(States.not_keepUpdated.name) -> statements.add(" preferences.keepUpdated == false ")
|
||||
}
|
||||
when {
|
||||
properties.contains(States.pref_streaming.name) -> statements.add("preferences.prefStreamOverDownload == true ")
|
||||
properties.contains(States.not_pref_streaming.name) -> statements.add(" preferences.prefStreamOverDownload == false ")
|
||||
}
|
||||
|
||||
when {
|
||||
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 ")
|
||||
|
@ -64,7 +44,7 @@ class FeedFilter(vararg properties_: String) : Serializable {
|
|||
}
|
||||
when {
|
||||
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' ")
|
||||
properties.contains(States.rss.name) -> statements.add(" !(downloadUrl CONTAINS[c] 'youtube' OR link CONTAINS[c] 'youtube' OR downloadUrl CONTAINS[c] 'youtu.be' OR link CONTAINS[c] 'youtu.be') ")
|
||||
}
|
||||
|
||||
val ratingQuerys = mutableListOf<String>()
|
||||
|
@ -76,7 +56,7 @@ class FeedFilter(vararg properties_: String) : Serializable {
|
|||
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)) {
|
||||
if (ratingQuerys.size > 1) for (r in ratingQuerys.subList(1, ratingQuerys.size)) {
|
||||
query.append(" OR ")
|
||||
query.append(r)
|
||||
}
|
||||
|
@ -90,7 +70,7 @@ class FeedFilter(vararg properties_: String) : Serializable {
|
|||
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)) {
|
||||
if (audoDeleteQuerys.size > 1) for (r in audoDeleteQuerys.subList(1, audoDeleteQuerys.size)) {
|
||||
query.append(" OR ")
|
||||
query.append(r)
|
||||
}
|
||||
|
@ -110,6 +90,7 @@ class FeedFilter(vararg properties_: String) : Serializable {
|
|||
query.append(r)
|
||||
}
|
||||
query.append(") ")
|
||||
Logd("queryString", "${query}")
|
||||
return query.toString()
|
||||
}
|
||||
|
||||
|
@ -117,6 +98,8 @@ class FeedFilter(vararg properties_: String) : Serializable {
|
|||
enum class States {
|
||||
keepUpdated,
|
||||
not_keepUpdated,
|
||||
pref_streaming,
|
||||
not_pref_streaming,
|
||||
global_playSpeed,
|
||||
custom_playSpeed,
|
||||
has_skips,
|
||||
|
@ -142,14 +125,9 @@ class FeedFilter(vararg properties_: String) : Serializable {
|
|||
favorite,
|
||||
}
|
||||
|
||||
enum class FeedFilterGroup(val nameRes: Int, vararg values: ItemProperties) {
|
||||
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),
|
||||
|
@ -157,15 +135,20 @@ class FeedFilter(vararg properties_: String) : Serializable {
|
|||
ItemProperties(R.string.good, States.good.name),
|
||||
ItemProperties(R.string.favorite, States.favorite.name),
|
||||
),
|
||||
HAS_VIDEO(R.string.has_video, ItemProperties(R.string.yes, States.has_video.name), ItemProperties(R.string.no, States.no_video.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)),
|
||||
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)),
|
||||
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), ),
|
||||
PREF_STREAMING(R.string.pref_stream_over_download_title, ItemProperties(R.string.yes, States.pref_streaming.name), ItemProperties(R.string.no, States.not_pref_streaming.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)
|
||||
val values: Array<ItemProperties> = arrayOf(*values_)
|
||||
|
||||
class ItemProperties(@JvmField val displayName: Int, @JvmField val filterId: String)
|
||||
class ItemProperties(val displayName: Int, val filterId: String)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
@ -4,12 +4,6 @@ enum class MediaType {
|
|||
AUDIO, VIDEO, UNKNOWN;
|
||||
|
||||
companion object {
|
||||
// private val AUDIO_APPLICATION_MIME_STRINGS: Set<String> = HashSet(mutableListOf(
|
||||
// "application/ogg",
|
||||
// "application/opus",
|
||||
// "application/x-flac"
|
||||
// ))
|
||||
|
||||
val AUDIO_APPLICATION_MIME_STRINGS: HashSet<String> = hashSetOf(
|
||||
"application/ogg",
|
||||
"application/opus",
|
||||
|
|
|
@ -40,9 +40,6 @@ import org.apache.commons.io.input.BOMInputStream
|
|||
import java.io.InputStreamReader
|
||||
import java.io.Reader
|
||||
|
||||
/**
|
||||
* Activity for Opml Import.
|
||||
*/
|
||||
class OpmlImportActivity : AppCompatActivity() {
|
||||
private var uri: Uri? = null
|
||||
private var _binding: OpmlSelectionBinding? = null
|
||||
|
@ -82,6 +79,7 @@ class OpmlImportActivity : AppCompatActivity() {
|
|||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
_binding = OpmlSelectionBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
Logd(TAG, "onCreate")
|
||||
|
||||
binding.feedlist.choiceMode = ListView.CHOICE_MODE_MULTIPLE
|
||||
binding.feedlist.onItemClickListener = OnItemClickListener { _: AdapterView<*>?, _: View?, _: Int, _: Long ->
|
||||
|
@ -106,7 +104,6 @@ class OpmlImportActivity : AppCompatActivity() {
|
|||
}
|
||||
binding.butConfirm.setOnClickListener {
|
||||
binding.progressBar.visibility = View.VISIBLE
|
||||
|
||||
lifecycleScope.launch {
|
||||
try {
|
||||
withContext(Dispatchers.IO) {
|
||||
|
|
|
@ -943,7 +943,7 @@ fun EpisodesFilterDialog(filter: EpisodeFilter? = null, filtersDisabled: Mutable
|
|||
window.setGravity(Gravity.BOTTOM)
|
||||
window.setDimAmount(0f)
|
||||
}
|
||||
Surface(modifier = Modifier.fillMaxWidth().height(500.dp), shape = RoundedCornerShape(16.dp)) {
|
||||
Surface(modifier = Modifier.fillMaxWidth().height(350.dp), color = MaterialTheme.colorScheme.surface.copy(alpha = 0.8f), shape = RoundedCornerShape(16.dp)) {
|
||||
val textColor = MaterialTheme.colorScheme.onSurface
|
||||
val scrollState = rememberScrollState()
|
||||
Column(Modifier.fillMaxSize().verticalScroll(scrollState)) {
|
||||
|
|
|
@ -1,147 +0,0 @@
|
|||
package ac.mdiq.podcini.ui.dialog
|
||||
|
||||
import ac.mdiq.podcini.R
|
||||
import ac.mdiq.podcini.storage.model.EpisodeFilter
|
||||
import ac.mdiq.podcini.ui.compose.CustomTheme
|
||||
import ac.mdiq.podcini.ui.compose.NonlazyGrid
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.Button
|
||||
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.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
|
||||
// TODO: to be removed
|
||||
abstract class EpisodeFilterDialog : BottomSheetDialogFragment() {
|
||||
|
||||
var filter: EpisodeFilter? = null
|
||||
val filtersDisabled: MutableSet<EpisodeFilter.EpisodesFilterGroup> = mutableSetOf()
|
||||
private val filterValues: MutableSet<String> = mutableSetOf()
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
val composeView = ComposeView(requireContext()).apply {
|
||||
setContent {
|
||||
CustomTheme(requireContext()) {
|
||||
MainView()
|
||||
}
|
||||
}
|
||||
}
|
||||
return composeView
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MainView() {
|
||||
val textColor = MaterialTheme.colorScheme.onSurface
|
||||
val scrollState = rememberScrollState()
|
||||
Column(Modifier.fillMaxSize().verticalScroll(scrollState)) {
|
||||
var selectNone by remember { mutableStateOf(false) }
|
||||
for (item in EpisodeFilter.EpisodesFilterGroup.entries) {
|
||||
if (item in filtersDisabled) continue
|
||||
if (item.values.size == 2) {
|
||||
Row(modifier = Modifier.padding(2.dp).fillMaxWidth(), horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically) {
|
||||
var selectedIndex by remember { mutableStateOf(-1) }
|
||||
if (selectNone) selectedIndex = -1
|
||||
LaunchedEffect(Unit) {
|
||||
if (filter != null) {
|
||||
if (item.values[0].filterId in filter!!.properties) selectedIndex = 0
|
||||
else if (item.values[1].filterId in filter!!.properties) selectedIndex = 1
|
||||
}
|
||||
}
|
||||
Text(stringResource(item.nameRes) + " :", fontWeight = FontWeight.Bold, style = MaterialTheme.typography.headlineSmall, color = textColor, modifier = Modifier.padding(end = 10.dp))
|
||||
Spacer(Modifier.weight(0.3f))
|
||||
OutlinedButton(
|
||||
modifier = Modifier.padding(2.dp), border = BorderStroke(2.dp, if (selectedIndex != 0) textColor else Color.Green),
|
||||
onClick = {
|
||||
if (selectedIndex != 0) {
|
||||
selectNone = false
|
||||
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.weight(0.1f))
|
||||
OutlinedButton(
|
||||
modifier = Modifier.padding(2.dp), border = BorderStroke(2.dp, if (selectedIndex != 1) textColor else Color.Green),
|
||||
onClick = {
|
||||
if (selectedIndex != 1) {
|
||||
selectNone = false
|
||||
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)
|
||||
}
|
||||
Spacer(Modifier.weight(0.5f))
|
||||
}
|
||||
} else {
|
||||
Column(modifier = Modifier.padding(start = 5.dp, bottom = 2.dp).fillMaxWidth()) {
|
||||
Text(stringResource(item.nameRes) + " :", fontWeight = FontWeight.Bold, style = MaterialTheme.typography.headlineSmall, color = textColor)
|
||||
NonlazyGrid(columns = 3, itemCount = item.values.size) { index ->
|
||||
var selected by remember { mutableStateOf(false) }
|
||||
if (selectNone) selected = false
|
||||
OutlinedButton(modifier = Modifier.padding(0.dp).heightIn(min = 20.dp).widthIn(min = 20.dp).wrapContentWidth(),
|
||||
border = BorderStroke(2.dp, if (selected) Color.Green else textColor),
|
||||
onClick = {
|
||||
selectNone = false
|
||||
selected = !selected
|
||||
if (selected) filterValues.add(item.values[index].filterId)
|
||||
else filterValues.remove(item.values[index].filterId)
|
||||
onFilterChanged(filterValues)
|
||||
},
|
||||
) {
|
||||
Text(text = stringResource(item.values[index].displayName), maxLines = 1, color = textColor)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Row {
|
||||
Spacer(Modifier.weight(0.3f))
|
||||
Button(onClick = {
|
||||
selectNone = true
|
||||
onFilterChanged(setOf(""))
|
||||
}) {
|
||||
Text(stringResource(R.string.reset))
|
||||
}
|
||||
Spacer(Modifier.weight(0.4f))
|
||||
Button(onClick = {
|
||||
dismiss()
|
||||
}) {
|
||||
Text(stringResource(R.string.close_label))
|
||||
}
|
||||
Spacer(Modifier.weight(0.3f))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract fun onFilterChanged(newFilterValues: Set<String>)
|
||||
|
||||
}
|
|
@ -93,12 +93,12 @@ class AllEpisodesFragment : BaseEpisodesFragment() {
|
|||
showFilterDialog = true
|
||||
// AllEpisodesFilterDialog.newInstance(getFilter()).show(childFragmentManager, null)
|
||||
}
|
||||
R.id.action_favorites -> {
|
||||
val filter = getFilter().properties.toMutableSet()
|
||||
if (filter.contains(EpisodeFilter.States.is_favorite.name)) filter.remove(EpisodeFilter.States.is_favorite.name)
|
||||
else filter.add(EpisodeFilter.States.is_favorite.name)
|
||||
onFilterChanged(FlowEvent.AllEpisodesFilterEvent(HashSet(filter)))
|
||||
}
|
||||
// R.id.action_favorites -> {
|
||||
// val filter = getFilter().properties.toMutableSet()
|
||||
// if (filter.contains(EpisodeFilter.States.is_favorite.name)) filter.remove(EpisodeFilter.States.is_favorite.name)
|
||||
// else filter.add(EpisodeFilter.States.is_favorite.name)
|
||||
// onFilterChanged(FlowEvent.AllEpisodesFilterEvent(HashSet(filter)))
|
||||
// }
|
||||
R.id.episodes_sort -> AllEpisodesSortDialog().show(childFragmentManager.beginTransaction(), "SortDialog")
|
||||
R.id.switch_queue -> SwitchQueueDialog(activity as MainActivity).show()
|
||||
else -> return false
|
||||
|
@ -117,7 +117,7 @@ class AllEpisodesFragment : BaseEpisodesFragment() {
|
|||
EventFlow.events.collectLatest { event ->
|
||||
Logd(TAG, "Received event: ${event.TAG}")
|
||||
when (event) {
|
||||
is FlowEvent.AllEpisodesFilterEvent -> onFilterChanged(event)
|
||||
// is FlowEvent.AllEpisodesFilterEvent -> onFilterChanged(event)
|
||||
is FlowEvent.AllEpisodesSortEvent -> {
|
||||
page = 1
|
||||
loadItems()
|
||||
|
@ -128,11 +128,11 @@ class AllEpisodesFragment : BaseEpisodesFragment() {
|
|||
}
|
||||
}
|
||||
|
||||
private fun onFilterChanged(event: FlowEvent.AllEpisodesFilterEvent) {
|
||||
prefFilterAllEpisodes = StringUtils.join(event.filterValues, ",")
|
||||
page = 1
|
||||
loadItems()
|
||||
}
|
||||
// private fun onFilterChanged(event: FlowEvent.AllEpisodesFilterEvent) {
|
||||
// prefFilterAllEpisodes = StringUtils.join(event.filterValues, ",")
|
||||
// page = 1
|
||||
// loadItems()
|
||||
// }
|
||||
|
||||
override fun updateToolbar() {
|
||||
swipeActions.setFilter(getFilter())
|
||||
|
@ -142,11 +142,14 @@ class AllEpisodesFragment : BaseEpisodesFragment() {
|
|||
emptyView.setMessage(R.string.no_all_episodes_filtered_label)
|
||||
} else emptyView.setMessage(R.string.no_all_episodes_label)
|
||||
infoBarText.value = info
|
||||
toolbar.menu?.findItem(R.id.action_favorites)?.setIcon(if (getFilter().showIsFavorite) R.drawable.ic_star else R.drawable.ic_star_border)
|
||||
// toolbar.menu?.findItem(R.id.action_favorites)?.setIcon(if (getFilter().showIsFavorite) R.drawable.ic_star else R.drawable.ic_star_border)
|
||||
}
|
||||
|
||||
override fun onFilterChanged(filterValues: Set<String>) {
|
||||
EventFlow.postEvent(FlowEvent.AllEpisodesFilterEvent(filterValues))
|
||||
// EventFlow.postEvent(FlowEvent.AllEpisodesFilterEvent(filterValues))
|
||||
prefFilterAllEpisodes = StringUtils.join(filterValues, ",")
|
||||
page = 1
|
||||
loadItems()
|
||||
}
|
||||
|
||||
class AllEpisodesSortDialog : EpisodeSortDialog() {
|
||||
|
|
|
@ -100,7 +100,12 @@ import java.util.*
|
|||
if (showFilterDialog) EpisodesFilterDialog(filter = getFilter(),
|
||||
filtersDisabled = mutableSetOf(EpisodeFilter.EpisodesFilterGroup.DOWNLOADED, EpisodeFilter.EpisodesFilterGroup.MEDIA),
|
||||
onDismissRequest = { showFilterDialog = false } ) {
|
||||
EventFlow.postEvent(FlowEvent.DownloadsFilterEvent(it))
|
||||
// EventFlow.postEvent(FlowEvent.DownloadsFilterEvent(it))
|
||||
val fSet = it.toMutableSet()
|
||||
fSet.add(EpisodeFilter.States.downloaded.name)
|
||||
prefFilterDownloads = StringUtils.join(fSet, ",")
|
||||
Logd(TAG, "onFilterChanged: $prefFilterDownloads")
|
||||
loadItems()
|
||||
}
|
||||
Column {
|
||||
InforBar(infoBarText, leftAction = leftActionState, rightAction = rightActionState, actionConfig = {swipeActions.showDialog()})
|
||||
|
@ -261,7 +266,7 @@ import java.util.*
|
|||
Logd(TAG, "Received event: ${event.TAG}")
|
||||
when (event) {
|
||||
is FlowEvent.EpisodeEvent -> onEpisodeEvent(event)
|
||||
is FlowEvent.DownloadsFilterEvent -> onFilterChanged(event)
|
||||
// is FlowEvent.DownloadsFilterEvent -> onFilterChanged(event)
|
||||
is FlowEvent.EpisodeMediaEvent -> onEpisodeMediaEvent(event)
|
||||
is FlowEvent.PlayerSettingsEvent -> loadItems()
|
||||
is FlowEvent.DownloadLogEvent -> loadItems()
|
||||
|
@ -283,13 +288,13 @@ 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 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())
|
||||
|
@ -434,21 +439,6 @@ import java.util.*
|
|||
}
|
||||
}
|
||||
|
||||
// class DownloadsFilterDialog : EpisodeFilterDialog() {
|
||||
// override fun onFilterChanged(newFilterValues: Set<String>) {
|
||||
// EventFlow.postEvent(FlowEvent.DownloadsFilterEvent(newFilterValues))
|
||||
// }
|
||||
// companion object {
|
||||
// fun newInstance(filter: EpisodeFilter?): DownloadsFilterDialog {
|
||||
// val dialog = DownloadsFilterDialog()
|
||||
// dialog.filter = filter
|
||||
// dialog.filtersDisabled.add(EpisodeFilter.EpisodesFilterGroup.DOWNLOADED)
|
||||
// dialog.filtersDisabled.add(EpisodeFilter.EpisodesFilterGroup.MEDIA)
|
||||
// return dialog
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
companion object {
|
||||
val TAG = DownloadsFragment::class.simpleName ?: "Anonymous"
|
||||
|
||||
|
|
|
@ -42,7 +42,6 @@ import kotlin.math.min
|
|||
|
||||
@UnstableApi override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
val root = super.onCreateView(inflater, container, savedInstanceState)
|
||||
|
||||
Logd(TAG, "fragment onCreateView")
|
||||
toolbar.inflateMenu(R.menu.playback_history)
|
||||
toolbar.setTitle(R.string.playback_history_label)
|
||||
|
@ -50,7 +49,6 @@ import kotlin.math.min
|
|||
emptyView.setIcon(R.drawable.ic_history)
|
||||
emptyView.setTitle(R.string.no_history_head_label)
|
||||
emptyView.setMessage(R.string.no_history_label)
|
||||
|
||||
return root
|
||||
}
|
||||
|
||||
|
@ -130,7 +128,7 @@ import kotlin.math.min
|
|||
emptyView.setMessage(R.string.no_all_episodes_filtered_label)
|
||||
} else emptyView.setMessage(R.string.no_all_episodes_label)
|
||||
infoBarText.value = info
|
||||
toolbar.menu?.findItem(R.id.action_favorites)?.setIcon(if (getFilter().showIsFavorite) R.drawable.ic_star else R.drawable.ic_star_border)
|
||||
// toolbar.menu?.findItem(R.id.action_favorites)?.setIcon(if (getFilter().showIsFavorite) R.drawable.ic_star else R.drawable.ic_star_border)
|
||||
}
|
||||
|
||||
private var eventSink: Job? = null
|
||||
|
|
|
@ -720,14 +720,14 @@ class OnlineFeedFragment : Fragment() {
|
|||
updateToolbar()
|
||||
return root
|
||||
}
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
procFlowEvents()
|
||||
}
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
cancelFlowEvents()
|
||||
}
|
||||
// override fun onStart() {
|
||||
// super.onStart()
|
||||
//// procFlowEvents()
|
||||
// }
|
||||
// override fun onStop() {
|
||||
// super.onStop()
|
||||
//// cancelFlowEvents()
|
||||
// }
|
||||
override fun onDestroyView() {
|
||||
episodeList.clear()
|
||||
super.onDestroyView()
|
||||
|
@ -750,7 +750,7 @@ class OnlineFeedFragment : Fragment() {
|
|||
binding.toolbar.menu.findItem(R.id.episodes_sort).setVisible(false)
|
||||
// binding.toolbar.menu.findItem(R.id.refresh_item).setVisible(false)
|
||||
binding.toolbar.menu.findItem(R.id.action_search).setVisible(false)
|
||||
binding.toolbar.menu.findItem(R.id.action_favorites).setVisible(false)
|
||||
// binding.toolbar.menu.findItem(R.id.action_favorites).setVisible(false)
|
||||
binding.toolbar.menu.findItem(R.id.filter_items).setVisible(false)
|
||||
infoBarText.value = "${episodes.size} episodes"
|
||||
}
|
||||
|
@ -760,23 +760,23 @@ class OnlineFeedFragment : Fragment() {
|
|||
else -> return false
|
||||
}
|
||||
}
|
||||
private var eventSink: Job? = null
|
||||
private fun cancelFlowEvents() {
|
||||
eventSink?.cancel()
|
||||
eventSink = null
|
||||
}
|
||||
private fun procFlowEvents() {
|
||||
if (eventSink != null) return
|
||||
eventSink = lifecycleScope.launch {
|
||||
EventFlow.events.collectLatest { event ->
|
||||
Logd(TAG, "Received event: ${event.TAG}")
|
||||
when (event) {
|
||||
is FlowEvent.AllEpisodesFilterEvent -> page = 1
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// private var eventSink: Job? = null
|
||||
// private fun cancelFlowEvents() {
|
||||
// eventSink?.cancel()
|
||||
// eventSink = null
|
||||
// }
|
||||
// private fun procFlowEvents() {
|
||||
// if (eventSink != null) return
|
||||
// eventSink = lifecycleScope.launch {
|
||||
// EventFlow.events.collectLatest { event ->
|
||||
// Logd(TAG, "Received event: ${event.TAG}")
|
||||
// when (event) {
|
||||
// is FlowEvent.AllEpisodesFilterEvent -> page = 1
|
||||
// else -> {}
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
companion object {
|
||||
const val PREF_NAME: String = "EpisodesListFragment"
|
||||
|
|
|
@ -2,49 +2,60 @@ package ac.mdiq.podcini.ui.fragment
|
|||
|
||||
|
||||
import ac.mdiq.podcini.R
|
||||
import ac.mdiq.podcini.databinding.HorizontalFeedItemBinding
|
||||
import ac.mdiq.podcini.databinding.SearchFragmentBinding
|
||||
import ac.mdiq.podcini.net.download.DownloadStatus
|
||||
import ac.mdiq.podcini.net.feed.discovery.CombinedSearcher
|
||||
import ac.mdiq.podcini.storage.database.RealmDB.realm
|
||||
import ac.mdiq.podcini.storage.model.Episode
|
||||
import ac.mdiq.podcini.storage.model.Feed
|
||||
import ac.mdiq.podcini.storage.model.Rating
|
||||
import ac.mdiq.podcini.storage.utils.EpisodeUtil
|
||||
import ac.mdiq.podcini.ui.actions.MenuItemUtils
|
||||
import ac.mdiq.podcini.ui.activity.MainActivity
|
||||
import ac.mdiq.podcini.ui.compose.CustomTheme
|
||||
import ac.mdiq.podcini.ui.compose.EpisodeLazyColumn
|
||||
import ac.mdiq.podcini.ui.compose.EpisodeVM
|
||||
import ac.mdiq.podcini.ui.dialog.CustomFeedNameDialog
|
||||
import ac.mdiq.podcini.ui.dialog.RemoveFeedDialog
|
||||
import ac.mdiq.podcini.ui.dialog.TagSettingsDialog
|
||||
import ac.mdiq.podcini.ui.utils.EmptyViewHandler
|
||||
import ac.mdiq.podcini.ui.view.SquareImageView
|
||||
import ac.mdiq.podcini.ui.compose.NonlazyGrid
|
||||
import ac.mdiq.podcini.util.EventFlow
|
||||
import ac.mdiq.podcini.util.FlowEvent
|
||||
import ac.mdiq.podcini.util.Logd
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.util.Log
|
||||
import android.util.Pair
|
||||
import android.view.*
|
||||
import android.view.LayoutInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.Button
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.appcompat.widget.SearchView
|
||||
import androidx.cardview.widget.CardView
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyRow
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material3.Checkbox
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
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.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.constraintlayout.compose.ConstraintLayout
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import coil.ImageLoader
|
||||
import coil.load
|
||||
import coil.compose.AsyncImage
|
||||
import coil.request.CachePolicy
|
||||
import coil.request.ImageRequest
|
||||
import com.google.android.material.appbar.MaterialToolbar
|
||||
import com.google.android.material.chip.Chip
|
||||
|
@ -53,7 +64,7 @@ import kotlinx.coroutines.Job
|
|||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.lang.ref.WeakReference
|
||||
import java.text.NumberFormat
|
||||
|
||||
/**
|
||||
* Performs a search operation on all feeds or one specific feed and displays the search result.
|
||||
|
@ -63,12 +74,11 @@ class SearchFragment : Fragment() {
|
|||
private var _binding: SearchFragmentBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
|
||||
private lateinit var adapterFeeds: HorizontalFeedListAdapter
|
||||
private lateinit var emptyViewHandler: EmptyViewHandler
|
||||
private lateinit var searchView: SearchView
|
||||
private lateinit var chip: Chip
|
||||
private lateinit var automaticSearchDebouncer: Handler
|
||||
|
||||
private val resultFeeds = mutableStateListOf<Feed>()
|
||||
private val results = mutableListOf<Episode>()
|
||||
private val vms = mutableStateListOf<EpisodeVM>()
|
||||
|
||||
|
@ -86,34 +96,20 @@ class SearchFragment : Fragment() {
|
|||
Logd(TAG, "fragment onCreateView")
|
||||
setupToolbar(binding.toolbar)
|
||||
|
||||
binding.lazyColumn.setContent {
|
||||
binding.resultsListView.setContent {
|
||||
CustomTheme(requireContext()) {
|
||||
EpisodeLazyColumn(activity as MainActivity, vms = vms)
|
||||
Column {
|
||||
CriteriaList()
|
||||
FeedsRow()
|
||||
EpisodeLazyColumn(activity as MainActivity, vms = vms)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val recyclerViewFeeds = binding.recyclerViewFeeds
|
||||
val layoutManagerFeeds = LinearLayoutManager(activity)
|
||||
layoutManagerFeeds.orientation = RecyclerView.HORIZONTAL
|
||||
recyclerViewFeeds.layoutManager = layoutManagerFeeds
|
||||
adapterFeeds = object : HorizontalFeedListAdapter(activity as MainActivity) {
|
||||
override fun onCreateContextMenu(contextMenu: ContextMenu, view: View, contextMenuInfo: ContextMenu.ContextMenuInfo?) {
|
||||
super.onCreateContextMenu(contextMenu, view, contextMenuInfo)
|
||||
MenuItemUtils.setOnClickListeners(contextMenu) { item: MenuItem -> this@SearchFragment.onContextItemSelected(item) }
|
||||
}
|
||||
}
|
||||
recyclerViewFeeds.adapter = adapterFeeds
|
||||
|
||||
emptyViewHandler = EmptyViewHandler(requireContext())
|
||||
// emptyViewHandler.attachToRecyclerView(recyclerView)
|
||||
emptyViewHandler.setIcon(R.drawable.ic_search)
|
||||
emptyViewHandler.setTitle(R.string.search_status_no_results)
|
||||
emptyViewHandler.setMessage(R.string.type_to_search)
|
||||
|
||||
chip = binding.feedTitleChip
|
||||
chip.setOnCloseIconClickListener {
|
||||
requireArguments().putLong(ARG_FEED, 0)
|
||||
searchWithProgressBar()
|
||||
search()
|
||||
}
|
||||
chip.visibility = if (requireArguments().getLong(ARG_FEED, 0) == 0L) View.GONE else View.VISIBLE
|
||||
chip.text = requireArguments().getString(ARG_FEED_NAME, "")
|
||||
|
@ -139,6 +135,7 @@ class SearchFragment : Fragment() {
|
|||
Logd(TAG, "onDestroyView")
|
||||
_binding = null
|
||||
results.clear()
|
||||
resultFeeds.clear()
|
||||
vms.clear()
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
@ -157,7 +154,7 @@ class SearchFragment : Fragment() {
|
|||
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
|
||||
@UnstableApi override fun onQueryTextSubmit(s: String): Boolean {
|
||||
searchView.clearFocus()
|
||||
searchWithProgressBar()
|
||||
search()
|
||||
return true
|
||||
}
|
||||
@UnstableApi override fun onQueryTextChange(s: String): Boolean {
|
||||
|
@ -181,24 +178,24 @@ class SearchFragment : Fragment() {
|
|||
})
|
||||
}
|
||||
|
||||
override fun onContextItemSelected(item: MenuItem): Boolean {
|
||||
val selectedFeedItem: Feed? = adapterFeeds.longPressedItem
|
||||
if (selectedFeedItem != null && onMenuItemClicked(this, item.itemId, selectedFeedItem) {}) return true
|
||||
return super.onContextItemSelected(item)
|
||||
}
|
||||
// override fun onContextItemSelected(item: MenuItem): Boolean {
|
||||
//// val selectedFeedItem: Feed? = adapterFeeds.longPressedItem
|
||||
//// if (selectedFeedItem != null && onMenuItemClicked(this, item.itemId, selectedFeedItem) {}) return true
|
||||
// return super.onContextItemSelected(item)
|
||||
// }
|
||||
|
||||
private fun onMenuItemClicked(fragment: Fragment, menuItemId: Int, selectedFeed: Feed, callback: Runnable): Boolean {
|
||||
val context = fragment.requireContext()
|
||||
when (menuItemId) {
|
||||
// R.id.rename_folder_item -> CustomFeedNameDialog(fragment.activity as Activity, selectedFeed).show()
|
||||
R.id.edit_tags -> if (selectedFeed.preferences != null) TagSettingsDialog.newInstance(listOf(selectedFeed))
|
||||
.show(fragment.childFragmentManager, TagSettingsDialog.TAG)
|
||||
R.id.rename_item -> CustomFeedNameDialog(fragment.activity as Activity, selectedFeed).show()
|
||||
R.id.remove_feed -> RemoveFeedDialog.show(context, selectedFeed, null)
|
||||
else -> return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
// private fun onMenuItemClicked(fragment: Fragment, menuItemId: Int, selectedFeed: Feed, callback: Runnable): Boolean {
|
||||
// val context = fragment.requireContext()
|
||||
// when (menuItemId) {
|
||||
//// R.id.rename_folder_item -> CustomFeedNameDialog(fragment.activity as Activity, selectedFeed).show()
|
||||
// R.id.edit_tags -> if (selectedFeed.preferences != null) TagSettingsDialog.newInstance(listOf(selectedFeed))
|
||||
// .show(fragment.childFragmentManager, TagSettingsDialog.TAG)
|
||||
// R.id.rename_item -> CustomFeedNameDialog(fragment.activity as Activity, selectedFeed).show()
|
||||
// R.id.remove_feed -> RemoveFeedDialog.show(context, selectedFeed, null)
|
||||
// else -> return false
|
||||
// }
|
||||
// return true
|
||||
// }
|
||||
|
||||
private var eventSink: Job? = null
|
||||
private var eventStickySink: Job? = null
|
||||
|
@ -240,14 +237,9 @@ class SearchFragment : Fragment() {
|
|||
}
|
||||
}
|
||||
|
||||
@UnstableApi private fun searchWithProgressBar() {
|
||||
emptyViewHandler.hide()
|
||||
search()
|
||||
}
|
||||
|
||||
@SuppressLint("StringFormatMatches")
|
||||
@UnstableApi private fun search() {
|
||||
adapterFeeds.setEndButton(R.string.search_online) { this.searchOnline() }
|
||||
// adapterFeeds.setEndButton(R.string.search_online) { this.searchOnline() }
|
||||
chip.visibility = if ((requireArguments().getLong(ARG_FEED, 0) == 0L)) View.GONE else View.VISIBLE
|
||||
|
||||
lifecycleScope.launch {
|
||||
|
@ -262,15 +254,43 @@ class SearchFragment : Fragment() {
|
|||
for (e in first_) { vms.add(EpisodeVM(e)) }
|
||||
}
|
||||
if (requireArguments().getLong(ARG_FEED, 0) == 0L) {
|
||||
if (results_.second != null) adapterFeeds.updateData(results_.second!!)
|
||||
} else adapterFeeds.updateData(emptyList())
|
||||
if (searchView.query.toString().isEmpty()) emptyViewHandler.setMessage(R.string.type_to_search)
|
||||
else emptyViewHandler.setMessage(getString(R.string.no_results_for_query, searchView.query))
|
||||
if (results_.second != null) {
|
||||
resultFeeds.clear()
|
||||
resultFeeds.addAll(results_.second!!)
|
||||
}
|
||||
} else resultFeeds.clear()
|
||||
}
|
||||
} catch (e: Throwable) { Log.e(TAG, Log.getStackTraceString(e)) }
|
||||
}
|
||||
}
|
||||
|
||||
enum class SearchBy(val nameRes: Int, var selected: Boolean = true) {
|
||||
TITLE(R.string.title),
|
||||
AUTHOR(R.string.author),
|
||||
DESCRIPTION(R.string.description_label),
|
||||
COMMENT(R.string.my_opinion_label),
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun CriteriaList() {
|
||||
val textColor = MaterialTheme.colorScheme.onSurface
|
||||
NonlazyGrid(columns = 2, itemCount = SearchBy.entries.size) { index ->
|
||||
val c = SearchBy.entries[index]
|
||||
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(start = 10.dp, end = 10.dp)) {
|
||||
var isChecked by remember { mutableStateOf(true) }
|
||||
Checkbox(
|
||||
checked = isChecked,
|
||||
onCheckedChange = { newValue ->
|
||||
c.selected = newValue
|
||||
isChecked = newValue
|
||||
}
|
||||
)
|
||||
Spacer(modifier = Modifier.width(2.dp))
|
||||
Text(stringResource(c.nameRes), color = textColor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@UnstableApi private fun performSearch(): Pair<List<Episode>, List<Feed>> {
|
||||
val query = searchView.query.toString()
|
||||
if (query.isEmpty()) return Pair<List<Episode>, List<Feed>>(emptyList(), emptyList())
|
||||
|
@ -282,45 +302,41 @@ class SearchFragment : Fragment() {
|
|||
return Pair<List<Episode>, List<Feed>>(items, feeds)
|
||||
}
|
||||
|
||||
private fun prepareFeedQueryString(query: String): String {
|
||||
private fun searchFeeds(query: String): List<Feed> {
|
||||
Logd(TAG, "searchFeeds called ${SearchBy.AUTHOR.selected}")
|
||||
val queryWords = query.split("\\s+".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
||||
val sb = StringBuilder()
|
||||
for (i in queryWords.indices) {
|
||||
sb.append("(")
|
||||
.append("eigenTitle TEXT '${queryWords[i]}'")
|
||||
.append(" OR ")
|
||||
.append("customTitle TEXT '${queryWords[i]}'")
|
||||
.append(" OR ")
|
||||
.append("author TEXT '${queryWords[i]}'")
|
||||
.append(" OR ")
|
||||
.append("description TEXT '${queryWords[i]}'")
|
||||
.append(") ")
|
||||
var isStart = true
|
||||
if (SearchBy.TITLE.selected) {
|
||||
sb.append("eigenTitle TEXT '${queryWords[i]}'")
|
||||
sb.append(" OR ")
|
||||
sb.append("customTitle TEXT '${queryWords[i]}'")
|
||||
isStart = false
|
||||
}
|
||||
if (SearchBy.AUTHOR.selected) {
|
||||
if (!isStart) sb.append(" OR ")
|
||||
sb.append("author TEXT '${queryWords[i]}'")
|
||||
isStart = false
|
||||
}
|
||||
if (SearchBy.DESCRIPTION.selected) {
|
||||
if (!isStart) sb.append(" OR ")
|
||||
sb.append("description TEXT '${queryWords[i]}'")
|
||||
isStart = false
|
||||
}
|
||||
if (SearchBy.COMMENT.selected) {
|
||||
if (!isStart) sb.append(" OR ")
|
||||
sb.append("comment TEXT '${queryWords[i]}'")
|
||||
}
|
||||
sb.append(") ")
|
||||
if (i != queryWords.size - 1) sb.append("AND ")
|
||||
}
|
||||
return sb.toString()
|
||||
}
|
||||
|
||||
private fun searchFeeds(query: String): List<Feed> {
|
||||
Logd(TAG, "searchFeeds called")
|
||||
val queryString = prepareFeedQueryString(query)
|
||||
val queryString = sb.toString()
|
||||
Logd(TAG, "searchFeeds queryString: $queryString")
|
||||
return realm.query(Feed::class).query(queryString).find()
|
||||
}
|
||||
|
||||
private fun prepareEpisodeQueryString(query: String): String {
|
||||
val queryWords = query.split("\\s+".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
||||
val sb = StringBuilder()
|
||||
for (i in queryWords.indices) {
|
||||
sb.append("(")
|
||||
.append("description TEXT '${queryWords[i]}'")
|
||||
.append(" OR ")
|
||||
.append("title TEXT '${queryWords[i]}'" )
|
||||
.append(") ")
|
||||
if (i != queryWords.size - 1) sb.append("AND ")
|
||||
}
|
||||
return sb.toString()
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches the FeedItems of a specific Feed for a given string.
|
||||
* @param feedID The id of the feed whose episodes should be searched.
|
||||
|
@ -330,7 +346,30 @@ class SearchFragment : Fragment() {
|
|||
*/
|
||||
private fun searchEpisodes(feedID: Long, query: String): List<Episode> {
|
||||
Logd(TAG, "searchEpisodes called")
|
||||
var queryString = prepareEpisodeQueryString(query)
|
||||
val queryWords = query.split("\\s+".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
||||
val sb = StringBuilder()
|
||||
for (i in queryWords.indices) {
|
||||
sb.append("(")
|
||||
var isStart = true
|
||||
if (SearchBy.TITLE.selected) {
|
||||
sb.append("title TEXT '${queryWords[i]}'" )
|
||||
isStart = false
|
||||
}
|
||||
if (SearchBy.DESCRIPTION.selected) {
|
||||
if (!isStart) sb.append(" OR ")
|
||||
sb.append("description TEXT '${queryWords[i]}'")
|
||||
sb.append(" OR ")
|
||||
sb.append("transcript TEXT '${queryWords[i]}'")
|
||||
isStart = false
|
||||
}
|
||||
if (SearchBy.COMMENT.selected) {
|
||||
if (!isStart) sb.append(" OR ")
|
||||
sb.append("comment TEXT '${queryWords[i]}'")
|
||||
}
|
||||
sb.append(") ")
|
||||
if (i != queryWords.size - 1) sb.append("AND ")
|
||||
}
|
||||
var queryString = sb.toString()
|
||||
if (feedID != 0L) queryString = "(feedId == $feedID) AND $queryString"
|
||||
Logd(TAG, "searchEpisodes queryString: $queryString")
|
||||
return realm.query(Episode::class).query(queryString).find()
|
||||
|
@ -354,89 +393,49 @@ class SearchFragment : Fragment() {
|
|||
(activity as MainActivity).loadChildFragment(SearchResultsFragment.newInstance(CombinedSearcher::class.java, query))
|
||||
}
|
||||
|
||||
open class HorizontalFeedListAdapter(mainActivity: MainActivity)
|
||||
: RecyclerView.Adapter<HorizontalFeedListAdapter.Holder>(), View.OnCreateContextMenuListener {
|
||||
|
||||
private val mainActivityRef: WeakReference<MainActivity> = WeakReference<MainActivity>(mainActivity)
|
||||
private val data: MutableList<Feed> = ArrayList()
|
||||
private var dummyViews = 0
|
||||
var longPressedItem: Feed? = null
|
||||
@StringRes
|
||||
private var endButtonText = 0
|
||||
private var endButtonAction: Runnable? = null
|
||||
|
||||
fun updateData(newData: List<Feed>?) {
|
||||
data.clear()
|
||||
data.addAll(newData!!)
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
|
||||
val convertView = View.inflate(mainActivityRef.get(), R.layout.horizontal_feed_item, null)
|
||||
return Holder(convertView)
|
||||
}
|
||||
@UnstableApi override fun onBindViewHolder(holder: Holder, position: Int) {
|
||||
if (position == itemCount - 1 && endButtonAction != null) {
|
||||
holder.cardView.visibility = View.GONE
|
||||
holder.actionButton.visibility = View.VISIBLE
|
||||
holder.actionButton.setText(endButtonText)
|
||||
holder.actionButton.setOnClickListener { endButtonAction!!.run() }
|
||||
return
|
||||
}
|
||||
holder.cardView.visibility = View.VISIBLE
|
||||
holder.actionButton.visibility = View.GONE
|
||||
if (position >= data.size) {
|
||||
holder.itemView.alpha = 0.1f
|
||||
// Glide.with(mainActivityRef.get()!!).clear(holder.imageView)
|
||||
val imageLoader = ImageLoader.Builder(mainActivityRef.get()!!).build()
|
||||
imageLoader.enqueue(ImageRequest.Builder(mainActivityRef.get()!!).data(null).target(holder.imageView).build())
|
||||
holder.imageView.setImageResource(R.color.medium_gray)
|
||||
return
|
||||
}
|
||||
holder.itemView.alpha = 1.0f
|
||||
val podcast: Feed = data[position]
|
||||
holder.imageView.setContentDescription(podcast.title)
|
||||
holder.imageView.setOnClickListener {
|
||||
mainActivityRef.get()?.loadChildFragment(FeedEpisodesFragment.newInstance(podcast.id))
|
||||
}
|
||||
holder.imageView.setOnCreateContextMenuListener(this)
|
||||
holder.imageView.setOnLongClickListener {
|
||||
val currentItemPosition = holder.bindingAdapterPosition
|
||||
longPressedItem = data[currentItemPosition]
|
||||
false
|
||||
}
|
||||
holder.imageView.load(podcast.imageUrl) {
|
||||
placeholder(R.color.light_gray)
|
||||
error(R.mipmap.ic_launcher)
|
||||
}
|
||||
}
|
||||
override fun getItemId(position: Int): Long {
|
||||
if (position >= data.size) return RecyclerView.NO_ID // Dummy views
|
||||
return data[position].id
|
||||
}
|
||||
override fun getItemCount(): Int {
|
||||
return dummyViews + data.size + (if ((endButtonAction == null)) 0 else 1)
|
||||
}
|
||||
override fun onCreateContextMenu(contextMenu: ContextMenu, view: View, contextMenuInfo: ContextMenu.ContextMenuInfo?) {
|
||||
val inflater: MenuInflater = mainActivityRef.get()!!.menuInflater
|
||||
if (longPressedItem == null) return
|
||||
inflater.inflate(R.menu.feed_context, contextMenu)
|
||||
contextMenu.setHeaderTitle(longPressedItem!!.title)
|
||||
}
|
||||
fun setEndButton(@StringRes text: Int, action: Runnable?) {
|
||||
endButtonAction = action
|
||||
endButtonText = text
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
class Holder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
val binding = HorizontalFeedItemBinding.bind(itemView)
|
||||
var imageView: SquareImageView = binding.discoveryCover
|
||||
var cardView: CardView
|
||||
var actionButton: Button
|
||||
|
||||
init {
|
||||
imageView.setDirection(SquareImageView.DIRECTION_HEIGHT)
|
||||
actionButton = binding.actionButton
|
||||
cardView = binding.cardView
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun FeedsRow() {
|
||||
val context = LocalContext.current
|
||||
val lazyGridState = rememberLazyListState()
|
||||
LazyRow (state = lazyGridState, horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||
contentPadding = PaddingValues(start = 12.dp, top = 16.dp, end = 12.dp, bottom = 16.dp)
|
||||
) {
|
||||
items(resultFeeds.size, key = {index -> resultFeeds[index].id}) { index ->
|
||||
val feed by remember { mutableStateOf(resultFeeds[index]) }
|
||||
ConstraintLayout {
|
||||
val (coverImage, episodeCount, rating, error) = createRefs()
|
||||
val imgLoc = remember(feed) { feed.imageUrl }
|
||||
AsyncImage(model = ImageRequest.Builder(context).data(imgLoc)
|
||||
.memoryCachePolicy(CachePolicy.ENABLED).placeholder(R.mipmap.ic_launcher).error(R.mipmap.ic_launcher).build(),
|
||||
contentDescription = "coverImage",
|
||||
modifier = Modifier.height(100.dp).aspectRatio(1f)
|
||||
.constrainAs(coverImage) {
|
||||
top.linkTo(parent.top)
|
||||
bottom.linkTo(parent.bottom)
|
||||
start.linkTo(parent.start)
|
||||
}.combinedClickable(onClick = {
|
||||
Logd(SubscriptionsFragment.TAG, "clicked: ${feed.title}")
|
||||
(activity as MainActivity).loadChildFragment(FeedEpisodesFragment.newInstance(feed.id))
|
||||
}, onLongClick = {
|
||||
Logd(SubscriptionsFragment.TAG, "long clicked: ${feed.title}")
|
||||
// val inflater: MenuInflater = (activity as MainActivity).menuInflater
|
||||
// inflater.inflate(R.menu.feed_context, contextMenu)
|
||||
// contextMenu.setHeaderTitle(feed.title)
|
||||
})
|
||||
)
|
||||
Text(NumberFormat.getInstance().format(feed.episodes.size.toLong()), color = Color.Green,
|
||||
modifier = Modifier.background(Color.Gray).constrainAs(episodeCount) {
|
||||
end.linkTo(parent.end)
|
||||
top.linkTo(coverImage.top)
|
||||
})
|
||||
if (feed.rating != Rating.UNRATED.code)
|
||||
Icon(imageVector = ImageVector.vectorResource(Rating.fromCode(feed.rating).res), tint = MaterialTheme.colorScheme.tertiary, contentDescription = "rating",
|
||||
modifier = Modifier.background(MaterialTheme.colorScheme.tertiaryContainer).constrainAs(rating) {
|
||||
start.linkTo(parent.start)
|
||||
centerVerticallyTo(coverImage)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -285,7 +285,7 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
|||
Logd(TAG, "Received event: ${event.TAG}")
|
||||
when (event) {
|
||||
is FlowEvent.FeedListEvent, is FlowEvent.FeedsSortedEvent -> loadSubscriptions()
|
||||
is FlowEvent.FeedsFilterEvent -> loadSubscriptions()
|
||||
// is FlowEvent.FeedsFilterEvent -> loadSubscriptions()
|
||||
is FlowEvent.EpisodePlayedEvent -> loadSubscriptions()
|
||||
is FlowEvent.FeedTagsChangedEvent -> loadSubscriptions()
|
||||
is FlowEvent.FeedPrefsChangeEvent -> loadSubscriptions()
|
||||
|
@ -1089,7 +1089,8 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
|||
fun onFilterChanged(newFilterValues: Set<String>) {
|
||||
feedsFilter = StringUtils.join(newFilterValues, ",")
|
||||
Logd(TAG, "onFilterChanged: $feedsFilter")
|
||||
EventFlow.postEvent(FlowEvent.FeedsFilterEvent(newFilterValues))
|
||||
loadSubscriptions()
|
||||
// EventFlow.postEvent(FlowEvent.FeedsFilterEvent(newFilterValues))
|
||||
}
|
||||
Dialog(properties = DialogProperties(usePlatformDefaultWidth = false), onDismissRequest = { onDismissRequest() }) {
|
||||
val dialogWindowProvider = LocalView.current.parent as? DialogWindowProvider
|
||||
|
@ -1097,7 +1098,7 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
|||
window.setGravity(Gravity.BOTTOM)
|
||||
window.setDimAmount(0f)
|
||||
}
|
||||
Surface(modifier = Modifier.fillMaxWidth().height(500.dp), shape = RoundedCornerShape(16.dp)) {
|
||||
Surface(modifier = Modifier.fillMaxWidth().height(350.dp), color = MaterialTheme.colorScheme.surface.copy(alpha = 0.8f), shape = RoundedCornerShape(16.dp)) {
|
||||
val textColor = MaterialTheme.colorScheme.onSurface
|
||||
val scrollState = rememberScrollState()
|
||||
Column(Modifier.fillMaxSize().verticalScroll(scrollState)) {
|
||||
|
@ -1109,8 +1110,8 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
|||
if (selectNone) selectedIndex = -1
|
||||
LaunchedEffect(Unit) {
|
||||
if (filter != null) {
|
||||
if (item.values[0].filterId in filter!!.properties) selectedIndex = 0
|
||||
else if (item.values[1].filterId in filter!!.properties) selectedIndex = 1
|
||||
if (item.values[0].filterId in filter.properties) selectedIndex = 0
|
||||
else if (item.values[1].filterId in filter.properties) selectedIndex = 1
|
||||
}
|
||||
}
|
||||
Text(stringResource(item.nameRes) + " :", fontWeight = FontWeight.Bold, style = MaterialTheme.typography.headlineSmall, color = textColor, modifier = Modifier.padding(end = 10.dp))
|
||||
|
@ -1231,137 +1232,6 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
|||
}
|
||||
}
|
||||
|
||||
// class FeedFilterDialog : BottomSheetDialogFragment() {
|
||||
// var filter: FeedFilter? = null
|
||||
// private val filterValues: MutableSet<String> = mutableSetOf()
|
||||
//
|
||||
// override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
// val composeView = ComposeView(requireContext()).apply {
|
||||
// setContent {
|
||||
// CustomTheme(requireContext()) {
|
||||
// MainView()
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// return composeView
|
||||
// }
|
||||
//
|
||||
// @Composable
|
||||
// fun MainView() {
|
||||
// val textColor = MaterialTheme.colorScheme.onSurface
|
||||
// val scrollState = rememberScrollState()
|
||||
// Column(Modifier.fillMaxSize().verticalScroll(scrollState)) {
|
||||
// var selectNone by remember { mutableStateOf(false) }
|
||||
// for (item in FeedFilter.FeedFilterGroup.entries) {
|
||||
// if (item.values.size == 2) {
|
||||
// Row(modifier = Modifier.padding(start = 5.dp).fillMaxWidth(), horizontalArrangement = Arrangement.Absolute.Left, verticalAlignment = Alignment.CenterVertically) {
|
||||
// var selectedIndex by remember { mutableStateOf(-1) }
|
||||
// if (selectNone) selectedIndex = -1
|
||||
// LaunchedEffect(Unit) {
|
||||
// if (filter != null) {
|
||||
// if (item.values[0].filterId in filter!!.properties) selectedIndex = 0
|
||||
// else if (item.values[1].filterId in filter!!.properties) selectedIndex = 1
|
||||
// }
|
||||
// }
|
||||
// Text(stringResource(item.nameRes) + " :", fontWeight = FontWeight.Bold, style = MaterialTheme.typography.headlineSmall, color = textColor, modifier = Modifier.padding(end = 10.dp))
|
||||
// Spacer(Modifier.weight(0.3f))
|
||||
// OutlinedButton(
|
||||
// modifier = Modifier.padding(2.dp).heightIn(min = 20.dp).widthIn(min = 20.dp), border = BorderStroke(2.dp, if (selectedIndex != 0) textColor else Color.Green),
|
||||
// onClick = {
|
||||
// if (selectedIndex != 0) {
|
||||
// selectNone = false
|
||||
// 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.weight(0.1f))
|
||||
// OutlinedButton(
|
||||
// modifier = Modifier.padding(2.dp).heightIn(min = 20.dp).widthIn(min = 20.dp), border = BorderStroke(2.dp, if (selectedIndex != 1) textColor else Color.Green),
|
||||
// onClick = {
|
||||
// if (selectedIndex != 1) {
|
||||
// selectNone = false
|
||||
// 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)
|
||||
// }
|
||||
// Spacer(Modifier.weight(0.5f))
|
||||
// }
|
||||
// } else {
|
||||
// Column(modifier = Modifier.padding(start = 5.dp, bottom = 2.dp).fillMaxWidth()) {
|
||||
// Text(stringResource(item.nameRes) + " :", fontWeight = FontWeight.Bold, style = MaterialTheme.typography.headlineSmall, color = textColor)
|
||||
// val lazyGridState = rememberLazyGridState()
|
||||
// LazyVerticalGrid(state = lazyGridState, columns = GridCells.Adaptive(100.dp),
|
||||
// verticalArrangement = Arrangement.spacedBy(2.dp), horizontalArrangement = Arrangement.spacedBy(2.dp)) {
|
||||
// items(item.values.size) { index ->
|
||||
// var selected by remember { mutableStateOf(false) }
|
||||
// if (selectNone) selected = false
|
||||
// OutlinedButton(
|
||||
// modifier = Modifier.padding(2.dp).heightIn(min = 20.dp).widthIn(min = 20.dp).wrapContentWidth(),
|
||||
// border = BorderStroke(2.dp, if (selected) Color.Green else textColor),
|
||||
// onClick = {
|
||||
// selectNone = false
|
||||
// selected = !selected
|
||||
// if (selected) filterValues.add(item.values[index].filterId)
|
||||
// else filterValues.remove(item.values[index].filterId)
|
||||
// onFilterChanged(filterValues)
|
||||
// },
|
||||
// ) {
|
||||
// Text(text = stringResource(item.values[index].displayName), maxLines = 1, color = textColor)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// Row {
|
||||
// Spacer(Modifier.weight(0.3f))
|
||||
// Button(onClick = {
|
||||
// selectNone = true
|
||||
// onFilterChanged(setOf(""))
|
||||
// }) {
|
||||
// Text(stringResource(R.string.reset))
|
||||
// }
|
||||
// Spacer(Modifier.weight(0.4f))
|
||||
// Button(onClick = {
|
||||
// dismiss()
|
||||
// }) {
|
||||
// Text(stringResource(R.string.close_label))
|
||||
// }
|
||||
// Spacer(Modifier.weight(0.3f))
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// private fun onFilterChanged(newFilterValues: Set<String>) {
|
||||
// feedsFilter = StringUtils.join(newFilterValues, ",")
|
||||
// Logd(TAG, "onFilterChanged: $feedsFilter")
|
||||
// EventFlow.postEvent(FlowEvent.FeedsFilterEvent(newFilterValues))
|
||||
// }
|
||||
//
|
||||
// companion object {
|
||||
// fun newInstance(filter: FeedFilter?): FeedFilterDialog {
|
||||
// val dialog = FeedFilterDialog()
|
||||
// dialog.filter = filter
|
||||
// return dialog
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
companion object {
|
||||
val TAG = SubscriptionsFragment::class.simpleName ?: "Anonymous"
|
||||
|
||||
|
|
|
@ -141,7 +141,7 @@ sealed class FlowEvent {
|
|||
|
||||
data class FeedsSortedEvent(val dummy: Unit = Unit) : FlowEvent()
|
||||
|
||||
data class FeedsFilterEvent(val filterValues: Set<String?>?) : FlowEvent()
|
||||
// data class FeedsFilterEvent(val filterValues: Set<String?>?) : FlowEvent()
|
||||
|
||||
// data class SkipIntroEndingChangedEvent(val skipIntro: Int, val skipEnding: Int, val feedId: Long) : FlowEvent()
|
||||
|
||||
|
@ -167,11 +167,11 @@ sealed class FlowEvent {
|
|||
|
||||
data class RatingEvent(val episode: Episode, val rating: Int = Rating.FAVORITE.code) : FlowEvent()
|
||||
|
||||
data class AllEpisodesFilterEvent(val filterValues: Set<String?>?) : FlowEvent()
|
||||
// data class AllEpisodesFilterEvent(val filterValues: Set<String?>?) : FlowEvent()
|
||||
|
||||
data class AllEpisodesSortEvent(val dummy: Unit = Unit) : FlowEvent()
|
||||
|
||||
data class DownloadsFilterEvent(val filterValues: Set<String?>?) : FlowEvent()
|
||||
// data class DownloadsFilterEvent(val filterValues: Set<String?>?) : FlowEvent()
|
||||
|
||||
data class EpisodeEvent(val episodes: List<Episode>) : FlowEvent() {
|
||||
companion object {
|
||||
|
|
|
@ -1,43 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:squareImageView="http://schemas.android.com/apk/ac.mdiq.podcini"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="96dp"
|
||||
android:orientation="vertical"
|
||||
android:id="@+id/horizontal_feed_item"
|
||||
android:padding="4dp"
|
||||
android:clipToPadding="false"
|
||||
android:clipToOutline="false"
|
||||
android:clipChildren="false">
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/cardView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:cardBackgroundColor="@color/non_square_icon_background"
|
||||
app:cardCornerRadius="16dp"
|
||||
app:cardPreventCornerOverlap="false"
|
||||
app:cardElevation="2dp">
|
||||
|
||||
<ac.mdiq.podcini.ui.view.SquareImageView
|
||||
android:id="@+id/discovery_cover"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="96dp"
|
||||
android:elevation="4dp"
|
||||
android:outlineProvider="bounds"
|
||||
android:foreground="?android:attr/selectableItemBackground"
|
||||
android:background="?android:attr/colorBackground"
|
||||
squareImageView:direction="height" />
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<Button
|
||||
android:id="@+id/actionButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
style="@style/Widget.Material3.Button.OutlinedButton" />
|
||||
|
||||
</LinearLayout>
|
|
@ -27,16 +27,8 @@
|
|||
android:visibility="gone"
|
||||
app:closeIconVisible="true" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recyclerViewFeeds"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="12dp"
|
||||
android:paddingRight="12dp"
|
||||
android:clipToPadding="false" />
|
||||
|
||||
<androidx.compose.ui.platform.ComposeView
|
||||
android:id="@+id/lazyColumn"
|
||||
android:id="@+id/resultsListView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
|
|
|
@ -9,12 +9,12 @@
|
|||
custom:showAsAction="always"
|
||||
android:title="@string/search_label"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/action_favorites"
|
||||
android:icon="@drawable/ic_star_border"
|
||||
android:menuCategory="container"
|
||||
android:title="@string/favorite_episodes_label"
|
||||
custom:showAsAction="always"/>
|
||||
<!-- <item-->
|
||||
<!-- android:id="@+id/action_favorites"-->
|
||||
<!-- android:icon="@drawable/ic_star_border"-->
|
||||
<!-- android:menuCategory="container"-->
|
||||
<!-- android:title="@string/favorite_episodes_label"-->
|
||||
<!-- custom:showAsAction="always"/>-->
|
||||
|
||||
<item
|
||||
android:id="@+id/filter_items"
|
||||
|
|
|
@ -419,6 +419,8 @@
|
|||
<string name="duration">Duration</string>
|
||||
<string name="episode_title">Episode title</string>
|
||||
<string name="feed_title">Podcast title</string>
|
||||
<string name="title">Title</string>
|
||||
<string name="author">Author</string>
|
||||
<string name="random">Random</string>
|
||||
<string name="smart_shuffle">Smart shuffle</string>
|
||||
<string name="size">Size</string>
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
# 6.12.4
|
||||
|
||||
* bug fixes and enhancements in filters routines
|
||||
* in SearchFragment, added search criteria options: title, author(feed only), description(including transcript in episodes), and comment (My opinion)
|
||||
* feed list in SearchFragment is in Compose
|
||||
|
||||
# 6.12.3
|
||||
|
||||
* reworked and expanded the filters routines for episodes and feeds
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
Version 6.12.4
|
||||
|
||||
* bug fixes and enhancements in filters routines
|
||||
* in SearchFragment, added search criteria options: title, author(feed only), description(including transcript in episodes), and comment (My opinion)
|
||||
* feed list in SearchFragment is in Compose
|
Loading…
Reference in New Issue