6.12.6 commit

This commit is contained in:
Xilin Jia 2024-10-26 19:45:43 +01:00
parent 974bea78cf
commit 6b29cc3f5d
18 changed files with 294 additions and 127 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 3020283 versionCode 3020284
versionName "6.12.5" versionName "6.12.6"
applicationId "ac.mdiq.podcini.R" applicationId "ac.mdiq.podcini.R"
def commit = "" def commit = ""

View File

@ -247,7 +247,7 @@ import kotlin.math.min
// only push downloaded items // only push downloaded items
val pausedItems = getEpisodes(0, Int.MAX_VALUE, EpisodeFilter(EpisodeFilter.States.paused.name), EpisodeSortOrder.DATE_NEW_OLD) val pausedItems = getEpisodes(0, Int.MAX_VALUE, EpisodeFilter(EpisodeFilter.States.paused.name), EpisodeSortOrder.DATE_NEW_OLD)
val readItems = getEpisodes(0, Int.MAX_VALUE, EpisodeFilter(EpisodeFilter.States.played.name), EpisodeSortOrder.DATE_NEW_OLD) val readItems = getEpisodes(0, Int.MAX_VALUE, EpisodeFilter(EpisodeFilter.States.played.name), EpisodeSortOrder.DATE_NEW_OLD)
val favoriteItems = getEpisodes(0, Int.MAX_VALUE, EpisodeFilter(EpisodeFilter.States.is_favorite.name), EpisodeSortOrder.DATE_NEW_OLD) val favoriteItems = getEpisodes(0, Int.MAX_VALUE, EpisodeFilter(EpisodeFilter.States.favorite.name), EpisodeSortOrder.DATE_NEW_OLD)
val comItems = mutableSetOf<Episode>() val comItems = mutableSetOf<Episode>()
comItems.addAll(pausedItems) comItems.addAll(pausedItems)
comItems.addAll(readItems) comItems.addAll(readItems)

View File

@ -959,7 +959,7 @@ class ImportExportPreferencesFragment : PreferenceFragmentCompat() {
val queuedEpisodeActions: MutableList<EpisodeAction> = mutableListOf() val queuedEpisodeActions: MutableList<EpisodeAction> = mutableListOf()
val pausedItems = getEpisodes(0, Int.MAX_VALUE, EpisodeFilter(EpisodeFilter.States.paused.name), EpisodeSortOrder.DATE_NEW_OLD) val pausedItems = getEpisodes(0, Int.MAX_VALUE, EpisodeFilter(EpisodeFilter.States.paused.name), EpisodeSortOrder.DATE_NEW_OLD)
val readItems = getEpisodes(0, Int.MAX_VALUE, EpisodeFilter(EpisodeFilter.States.played.name), EpisodeSortOrder.DATE_NEW_OLD) val readItems = getEpisodes(0, Int.MAX_VALUE, EpisodeFilter(EpisodeFilter.States.played.name), EpisodeSortOrder.DATE_NEW_OLD)
val favoriteItems = getEpisodes(0, Int.MAX_VALUE, EpisodeFilter(EpisodeFilter.States.is_favorite.name), EpisodeSortOrder.DATE_NEW_OLD) val favoriteItems = getEpisodes(0, Int.MAX_VALUE, EpisodeFilter(EpisodeFilter.States.favorite.name), EpisodeSortOrder.DATE_NEW_OLD)
val comItems = mutableSetOf<Episode>() val comItems = mutableSetOf<Episode>()
comItems.addAll(pausedItems) comItems.addAll(pausedItems)
comItems.addAll(readItems) comItems.addAll(readItems)
@ -1018,7 +1018,7 @@ class ImportExportPreferencesFragment : PreferenceFragmentCompat() {
val favTemplate = IOUtils.toString(favTemplateStream, UTF_8) val favTemplate = IOUtils.toString(favTemplateStream, UTF_8)
val feedTemplateStream = context.assets.open(FEED_TEMPLATE) val feedTemplateStream = context.assets.open(FEED_TEMPLATE)
val feedTemplate = IOUtils.toString(feedTemplateStream, UTF_8) val feedTemplate = IOUtils.toString(feedTemplateStream, UTF_8)
val allFavorites = getEpisodes(0, Int.MAX_VALUE, EpisodeFilter(EpisodeFilter.States.is_favorite.name), EpisodeSortOrder.DATE_NEW_OLD) val allFavorites = getEpisodes(0, Int.MAX_VALUE, EpisodeFilter(EpisodeFilter.States.favorite.name), EpisodeSortOrder.DATE_NEW_OLD)
val favoritesByFeed = buildFeedMap(allFavorites) val favoritesByFeed = buildFeedMap(allFavorites)
writer!!.append(templateParts[0]) writer!!.append(templateParts[0])
for (feedId in favoritesByFeed.keys) { for (feedId in favoritesByFeed.keys) {

View File

@ -19,6 +19,9 @@ import ac.mdiq.podcini.util.EventFlow
import ac.mdiq.podcini.util.FlowEvent import ac.mdiq.podcini.util.FlowEvent
import android.util.Log import android.util.Log
import androidx.annotation.OptIn import androidx.annotation.OptIn
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.media3.common.util.UnstableApi import androidx.media3.common.util.UnstableApi
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import java.util.* import java.util.*
@ -30,12 +33,6 @@ object Queues {
BACK, FRONT, AFTER_CURRENTLY_PLAYING, RANDOM BACK, FRONT, AFTER_CURRENTLY_PLAYING, RANDOM
} }
var isQueueLocked: Boolean
get() = appPrefs.getBoolean(UserPreferences.Prefs.prefQueueLocked.name, false)
set(locked) {
appPrefs.edit().putBoolean(UserPreferences.Prefs.prefQueueLocked.name, locked).apply()
}
/** /**
* Returns if the queue is in keep sorted mode. * Returns if the queue is in keep sorted mode.
* Enables/disables the keep sorted mode of the queue. * Enables/disables the keep sorted mode of the queue.
@ -66,8 +63,7 @@ object Queues {
var enqueueLocation: EnqueueLocation var enqueueLocation: EnqueueLocation
get() { get() {
val valStr = appPrefs.getString(UserPreferences.Prefs.prefEnqueueLocation.name, EnqueueLocation.BACK.name) val valStr = appPrefs.getString(UserPreferences.Prefs.prefEnqueueLocation.name, EnqueueLocation.BACK.name)
try { try { return EnqueueLocation.valueOf(valStr!!)
return EnqueueLocation.valueOf(valStr!!)
} catch (t: Throwable) { } catch (t: Throwable) {
// should never happen but just in case // should never happen but just in case
Log.e(TAG, "getEnqueueLocation: invalid value '$valStr' Use default.", t) Log.e(TAG, "getEnqueueLocation: invalid value '$valStr' Use default.", t)

View File

@ -2,20 +2,21 @@ package ac.mdiq.podcini.storage.model
import ac.mdiq.podcini.R import ac.mdiq.podcini.R
import ac.mdiq.podcini.storage.database.Queues.inAnyQueue import ac.mdiq.podcini.storage.database.Queues.inAnyQueue
import ac.mdiq.podcini.util.Logd
import java.io.Serializable import java.io.Serializable
class EpisodeFilter(vararg properties_: String) : Serializable { class EpisodeFilter(vararg properties_: String) : Serializable {
val properties: HashSet<String> = setOf(*properties_).filter { it.isNotEmpty() }.map {it.trim()}.toHashSet() val properties: HashSet<String> = setOf(*properties_).filter { it.isNotEmpty() }.map {it.trim()}.toHashSet()
val showPlayed: Boolean = properties.contains(States.played.name) // val showPlayed: Boolean = properties.contains(States.played.name)
val showUnplayed: Boolean = properties.contains(States.unplayed.name) // val showUnplayed: Boolean = properties.contains(States.unplayed.name)
val showNew: Boolean = properties.contains(States.new.name) // val showNew: Boolean = properties.contains(States.new.name)
val showQueued: Boolean = properties.contains(States.queued.name) val showQueued: Boolean = properties.contains(States.queued.name)
val showNotQueued: Boolean = properties.contains(States.not_queued.name) val showNotQueued: Boolean = properties.contains(States.not_queued.name)
val showDownloaded: Boolean = properties.contains(States.downloaded.name) val showDownloaded: Boolean = properties.contains(States.downloaded.name)
val showNotDownloaded: Boolean = properties.contains(States.not_downloaded.name) val showNotDownloaded: Boolean = properties.contains(States.not_downloaded.name)
val showIsFavorite: Boolean = properties.contains(States.is_favorite.name) // val showIsFavorite: Boolean = properties.contains(States.is_favorite.name)
val showNotFavorite: Boolean = properties.contains(States.not_favorite.name) // val showNotFavorite: Boolean = properties.contains(States.not_favorite.name)
constructor(properties: String) : this(*(properties.split(",").toTypedArray())) constructor(properties: String) : this(*(properties.split(",").toTypedArray()))
@ -30,11 +31,11 @@ class EpisodeFilter(vararg properties_: String) : Serializable {
fun queryString(): String { fun queryString(): String {
val statements: MutableList<String> = mutableListOf() val statements: MutableList<String> = mutableListOf()
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 ")
} // }
val mediaTypeQuerys = mutableListOf<String>() val mediaTypeQuerys = mutableListOf<String>()
if (properties.contains(States.unknown.name)) mediaTypeQuerys.add(" media == nil OR media.mimeType == nil OR media.mimeType == '' ") if (properties.contains(States.unknown.name)) mediaTypeQuerys.add(" media == nil OR media.mimeType == nil OR media.mimeType == '' ")
@ -118,10 +119,10 @@ class EpisodeFilter(vararg properties_: String) : Serializable {
properties.contains(States.has_comments.name) -> statements.add(" comment != '' ") properties.contains(States.has_comments.name) -> statements.add(" comment != '' ")
properties.contains(States.no_comments.name) -> statements.add(" comment == '' ") properties.contains(States.no_comments.name) -> statements.add(" comment == '' ")
} }
when { // when {
showIsFavorite -> statements.add("rating == ${Rating.FAVORITE.code} ") // showIsFavorite -> statements.add("rating == ${Rating.FAVORITE.code} ")
showNotFavorite -> 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"
val query = StringBuilder(" (" + statements[0]) val query = StringBuilder(" (" + statements[0])
@ -130,6 +131,7 @@ class EpisodeFilter(vararg properties_: String) : Serializable {
query.append(r) query.append(r)
} }
query.append(") ") query.append(") ")
Logd("EpisodeFilter", "queryString: $query")
return query.toString() return query.toString()
} }
@ -154,8 +156,8 @@ class EpisodeFilter(vararg properties_: String) : Serializable {
audio_app, audio_app,
paused, paused,
not_paused, not_paused,
is_favorite, // is_favorite,
not_favorite, // not_favorite,
has_media, has_media,
no_media, no_media,
has_comments, has_comments,

View File

@ -28,6 +28,7 @@ import ac.mdiq.podcini.ui.fragment.*
import ac.mdiq.podcini.ui.utils.LocalDeleteModal.deleteEpisodesWarnLocal import ac.mdiq.podcini.ui.utils.LocalDeleteModal.deleteEpisodesWarnLocal
import ac.mdiq.podcini.util.EventFlow import ac.mdiq.podcini.util.EventFlow
import ac.mdiq.podcini.util.FlowEvent import ac.mdiq.podcini.util.FlowEvent
import ac.mdiq.podcini.util.Logd
import android.content.Context import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import android.util.TypedValue import android.util.TypedValue
@ -92,6 +93,7 @@ open class SwipeActions(private val fragment: Fragment, private val tag: String)
// EventFlow.postEvent(FlowEvent.SwipeActionsChangedEvent()) // EventFlow.postEvent(FlowEvent.SwipeActionsChangedEvent())
// } // }
// }) // })
Logd("SwipeActions", "showDialog()")
val composeView = ComposeView(fragment.requireContext()).apply { val composeView = ComposeView(fragment.requireContext()).apply {
setContent { setContent {
val showDialog = remember { mutableStateOf(true) } val showDialog = remember { mutableStateOf(true) }
@ -101,6 +103,7 @@ open class SwipeActions(private val fragment: Fragment, private val tag: String)
(fragment.view as? ViewGroup)?.removeView(this@apply) (fragment.view as? ViewGroup)?.removeView(this@apply)
}) { }) {
actions = getPrefs(this@SwipeActions.tag) actions = getPrefs(this@SwipeActions.tag)
// TODO: remove the need of event
EventFlow.postEvent(FlowEvent.SwipeActionsChangedEvent()) EventFlow.postEvent(FlowEvent.SwipeActionsChangedEvent())
} }
} }
@ -149,7 +152,8 @@ open class SwipeActions(private val fragment: Fragment, private val tag: String)
} }
override fun willRemove(filter: EpisodeFilter, item: Episode): Boolean { override fun willRemove(filter: EpisodeFilter, item: Episode): Boolean {
return filter.showQueued || filter.showNew return false
// return filter.showQueued || filter.showNew
} }
} }
@ -208,7 +212,8 @@ open class SwipeActions(private val fragment: Fragment, private val tag: String)
} }
override fun willRemove(filter: EpisodeFilter, item: Episode): Boolean { override fun willRemove(filter: EpisodeFilter, item: Episode): Boolean {
return filter.showQueued || filter.showNew return false
// return filter.showQueued || filter.showNew
} }
} }
@ -274,7 +279,8 @@ open class SwipeActions(private val fragment: Fragment, private val tag: String)
} }
override fun willRemove(filter: EpisodeFilter, item: Episode): Boolean { override fun willRemove(filter: EpisodeFilter, item: Episode): Boolean {
return filter.showIsFavorite || filter.showNotFavorite return false
// return filter.showIsFavorite || filter.showNotFavorite
} }
} }
@ -479,8 +485,9 @@ open class SwipeActions(private val fragment: Fragment, private val tag: String)
} }
override fun willRemove(filter: EpisodeFilter, item: Episode): Boolean { override fun willRemove(filter: EpisodeFilter, item: Episode): Boolean {
return if (item.playState == PlayState.NEW.code) filter.showPlayed || filter.showNew return false
else filter.showUnplayed || filter.showPlayed || filter.showNew // return if (item.playState == PlayState.NEW.code) filter.showPlayed || filter.showNew
// else filter.showUnplayed || filter.showPlayed || filter.showNew
} }
} }

View File

@ -55,8 +55,7 @@ import android.view.Gravity
import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.tween import androidx.compose.animation.core.tween
import androidx.compose.foundation.* import androidx.compose.foundation.*
import androidx.compose.foundation.gestures.detectHorizontalDragGestures import androidx.compose.foundation.gestures.*
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.itemsIndexed
@ -77,6 +76,7 @@ import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.input.pointer.util.VelocityTracker import androidx.compose.ui.input.pointer.util.VelocityTracker
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalView import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource import androidx.compose.ui.res.vectorResource
@ -410,10 +410,10 @@ fun ShelveDialog(selected: List<Episode>, onDismissRequest: () -> Unit) {
} }
} }
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class) @OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class)
@Composable @Composable
fun EpisodeLazyColumn(activity: MainActivity, vms: List<EpisodeVM>, feed: Feed? = null, fun EpisodeLazyColumn(activity: MainActivity, vms: MutableList<EpisodeVM>, feed: Feed? = null,
isDraggable: Boolean = false, dragCB: ((Int, Int)->Unit)? = null,
refreshCB: (()->Unit)? = null, leftSwipeCB: ((Episode) -> Unit)? = null, rightSwipeCB: ((Episode) -> Unit)? = null, refreshCB: (()->Unit)? = null, leftSwipeCB: ((Episode) -> Unit)? = null, rightSwipeCB: ((Episode) -> Unit)? = null,
actionButton_: ((Episode)-> EpisodeActionButton)? = null) { actionButton_: ((Episode)-> EpisodeActionButton)? = null) {
val TAG = "EpisodeLazyColumn" val TAG = "EpisodeLazyColumn"
@ -457,10 +457,7 @@ fun EpisodeLazyColumn(activity: MainActivity, vms: List<EpisodeVM>, feed: Feed?
Text(stringResource(R.string.feed_delete_reason_msg)) Text(stringResource(R.string.feed_delete_reason_msg))
BasicTextField(value = textState, onValueChange = { textState = it }, BasicTextField(value = textState, onValueChange = { textState = it },
textStyle = TextStyle(fontSize = 16.sp, color = textColor), textStyle = TextStyle(fontSize = 16.sp, color = textColor),
modifier = Modifier modifier = Modifier.fillMaxWidth().height(100.dp).padding(start = 10.dp, end = 10.dp, bottom = 10.dp)
.fillMaxWidth()
.height(100.dp)
.padding(start = 10.dp, end = 10.dp, bottom = 10.dp)
.border(1.dp, MaterialTheme.colorScheme.primary, MaterialTheme.shapes.small) .border(1.dp, MaterialTheme.colorScheme.primary, MaterialTheme.shapes.small)
) )
Button(onClick = { Button(onClick = {
@ -649,19 +646,26 @@ fun EpisodeLazyColumn(activity: MainActivity, vms: List<EpisodeVM>, feed: Feed?
} }
@Composable @Composable
fun MainRow(vm: EpisodeVM, index: Int) { fun MainRow(vm: EpisodeVM, index: Int, isBeingDragged: Boolean, yOffset: Float, onDragStart: () -> Unit, onDrag: (Float) -> Unit, onDragEnd: () -> Unit) {
val textColor = MaterialTheme.colorScheme.onSurface val textColor = MaterialTheme.colorScheme.onSurface
fun toggleSelected() { fun toggleSelected() {
vm.isSelected = !vm.isSelected vm.isSelected = !vm.isSelected
if (vm.isSelected) selected.add(vms[index].episode) if (vm.isSelected) selected.add(vms[index].episode)
else selected.remove(vms[index].episode) else selected.remove(vms[index].episode)
} }
Row(Modifier.background(if (vm.isSelected) MaterialTheme.colorScheme.secondaryContainer else MaterialTheme.colorScheme.surface)) { val density = LocalDensity.current
if (false) { Row(Modifier.background(if (vm.isSelected) MaterialTheme.colorScheme.secondaryContainer else MaterialTheme.colorScheme.surface)
.offset(y = with(density) { yOffset.toDp() })) {
if (isDraggable) {
val typedValue = TypedValue() val typedValue = TypedValue()
LocalContext.current.theme.resolveAttribute(R.attr.dragview_background, typedValue, true) context.theme.resolveAttribute(R.attr.dragview_background, typedValue, true)
Icon(imageVector = ImageVector.vectorResource(typedValue.resourceId), tint = textColor, contentDescription = "drag handle", Icon(imageVector = ImageVector.vectorResource(typedValue.resourceId), tint = textColor, contentDescription = "drag handle",
modifier = Modifier.width(16.dp).align(Alignment.CenterVertically)) modifier = Modifier.width(50.dp).align(Alignment.CenterVertically).padding(start = 10.dp, end = 15.dp)
.draggable(orientation = Orientation.Vertical,
state = rememberDraggableState { delta -> onDrag(delta) },
onDragStarted = { onDragStart() },
onDragStopped = { onDragEnd() }
))
} }
ConstraintLayout(modifier = Modifier.width(56.dp).height(56.dp)) { ConstraintLayout(modifier = Modifier.width(56.dp).height(56.dp)) {
val (imgvCover, checkMark) = createRefs() val (imgvCover, checkMark) = createRefs()
@ -727,7 +731,7 @@ fun EpisodeLazyColumn(activity: MainActivity, vms: List<EpisodeVM>, feed: Feed?
val durText = remember { DurationConverter.getDurationStringLong(dur) } val durText = remember { DurationConverter.getDurationStringLong(dur) }
val dateSizeText = " · " + formatAbbrev(curContext, vm.episode.getPubDate()) + " · " + durText + " · " + val dateSizeText = " · " + formatAbbrev(curContext, vm.episode.getPubDate()) + " · " + durText + " · " +
if ((vm.episode.media?.size ?: 0) > 0) Formatter.formatShortFileSize(curContext, vm.episode.media?.size ?: 0) else "" if ((vm.episode.media?.size ?: 0) > 0) Formatter.formatShortFileSize(curContext, vm.episode.media?.size ?: 0) else ""
Text(dateSizeText, color = textColor, style = MaterialTheme.typography.bodyMedium) Text(dateSizeText, color = textColor, style = MaterialTheme.typography.bodyMedium, maxLines = 1, overflow = TextOverflow.Ellipsis)
} }
Text(vm.episode.title ?: "", color = textColor, maxLines = 2, overflow = TextOverflow.Ellipsis) Text(vm.episode.title ?: "", color = textColor, maxLines = 2, overflow = TextOverflow.Ellipsis)
} }
@ -774,7 +778,7 @@ fun EpisodeLazyColumn(activity: MainActivity, vms: List<EpisodeVM>, feed: Feed?
val dur = remember { vm.episode.media?.getDuration() ?: 0 } val dur = remember { vm.episode.media?.getDuration() ?: 0 }
val durText = remember { DurationConverter.getDurationStringLong(dur) } val durText = remember { DurationConverter.getDurationStringLong(dur) }
vm.prog = if (dur > 0 && pos >= 0 && dur >= pos) 1.0f * pos / dur else 0f vm.prog = if (dur > 0 && pos >= 0 && dur >= pos) 1.0f * pos / dur else 0f
Logd(TAG, "$index vm.prog: ${vm.prog}") // Logd(TAG, "$index vm.prog: ${vm.prog}")
Row { Row {
Text(DurationConverter.getDurationStringLong(vm.positionState), color = textColor, style = MaterialTheme.typography.bodySmall) Text(DurationConverter.getDurationStringLong(vm.positionState), color = textColor, style = MaterialTheme.typography.bodySmall)
LinearProgressIndicator(progress = { vm.prog }, modifier = Modifier.weight(1f).height(4.dp).align(Alignment.CenterVertically)) LinearProgressIndicator(progress = { vm.prog }, modifier = Modifier.weight(1f).height(4.dp).align(Alignment.CenterVertically))
@ -789,6 +793,13 @@ fun EpisodeLazyColumn(activity: MainActivity, vms: List<EpisodeVM>, feed: Feed?
refreshCB?.invoke() refreshCB?.invoke()
refreshing = false refreshing = false
}) { }) {
fun <T> MutableList<T>.move(fromIndex: Int, toIndex: Int) {
if (fromIndex != toIndex && fromIndex in indices && toIndex in indices) {
val item = removeAt(fromIndex)
add(toIndex, item)
}
}
val rowHeightPx = with(LocalDensity.current) { 56.dp.toPx() }
LazyColumn(state = lazyListState, modifier = Modifier.padding(start = 10.dp, end = 10.dp, top = 10.dp, bottom = 10.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { LazyColumn(state = lazyListState, modifier = Modifier.padding(start = 10.dp, end = 10.dp, top = 10.dp, bottom = 10.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
itemsIndexed(vms, key = { _, vm -> vm.episode.id}) { index, vm -> itemsIndexed(vms, key = { _, vm -> vm.episode.id}) { index, vm ->
vm.startMonitoring() vm.startMonitoring()
@ -801,7 +812,6 @@ fun EpisodeLazyColumn(activity: MainActivity, vms: List<EpisodeVM>, feed: Feed?
val velocityTracker = remember { VelocityTracker() } val velocityTracker = remember { VelocityTracker() }
val offsetX = remember { Animatable(0f) } val offsetX = remember { Animatable(0f) }
Box(modifier = Modifier.fillMaxWidth().pointerInput(Unit) { Box(modifier = Modifier.fillMaxWidth().pointerInput(Unit) {
Logd(TAG, "top box")
detectHorizontalDragGestures(onDragStart = { velocityTracker.resetTracking() }, detectHorizontalDragGestures(onDragStart = { velocityTracker.resetTracking() },
onHorizontalDrag = { change, dragAmount -> onHorizontalDrag = { change, dragAmount ->
Logd(TAG, "onHorizontalDrag $dragAmount") Logd(TAG, "onHorizontalDrag $dragAmount")
@ -829,7 +839,25 @@ fun EpisodeLazyColumn(activity: MainActivity, vms: List<EpisodeVM>, feed: Feed?
Logd(TAG, "LaunchedEffect $index ${vm.isSelected} ${selected.size}") Logd(TAG, "LaunchedEffect $index ${vm.isSelected} ${selected.size}")
} }
Column { Column {
MainRow(vm, index) var yOffset by remember { mutableStateOf(0f) }
var draggedIndex by remember { mutableStateOf<Int?>(null) }
MainRow(vm, index, isBeingDragged = draggedIndex == index,
yOffset = if (draggedIndex == index) yOffset else 0f,
onDragStart = { draggedIndex = index },
onDrag = { delta -> yOffset += delta },
onDragEnd = {
draggedIndex?.let { startIndex ->
val newIndex = (startIndex + (yOffset / rowHeightPx).toInt()).coerceIn(0, vms.lastIndex)
Logd(TAG, "onDragEnd draggedIndex: $draggedIndex newIndex: $newIndex")
if (newIndex != startIndex) {
dragCB?.invoke(startIndex, newIndex)
val item = vms.removeAt(startIndex)
vms.add(newIndex, item)
}
}
draggedIndex = null
yOffset = 0f
})
ProgressRow(vm, index) ProgressRow(vm, index)
} }
} }
@ -842,9 +870,7 @@ fun EpisodeLazyColumn(activity: MainActivity, vms: List<EpisodeVM>, feed: Feed?
modifier = Modifier.width(35.dp).height(35.dp).padding(end = 10.dp) modifier = Modifier.width(35.dp).height(35.dp).padding(end = 10.dp)
.clickable(onClick = { .clickable(onClick = {
selected.clear() selected.clear()
for (i in 0..longPressIndex) { for (i in 0..longPressIndex) selected.add(vms[i].episode)
selected.add(vms[i].episode)
}
selectedSize = selected.size selectedSize = selected.size
Logd(TAG, "selectedIds: ${selected.size}") Logd(TAG, "selectedIds: ${selected.size}")
})) }))
@ -852,9 +878,7 @@ fun EpisodeLazyColumn(activity: MainActivity, vms: List<EpisodeVM>, feed: Feed?
modifier = Modifier.width(35.dp).height(35.dp).padding(end = 10.dp) modifier = Modifier.width(35.dp).height(35.dp).padding(end = 10.dp)
.clickable(onClick = { .clickable(onClick = {
selected.clear() selected.clear()
for (i in longPressIndex..<vms.size) { for (i in longPressIndex..<vms.size) selected.add(vms[i].episode)
selected.add(vms[i].episode)
}
selectedSize = selected.size selectedSize = selected.size
Logd(TAG, "selectedIds: ${selected.size}") Logd(TAG, "selectedIds: ${selected.size}")
})) }))
@ -863,9 +887,7 @@ fun EpisodeLazyColumn(activity: MainActivity, vms: List<EpisodeVM>, feed: Feed?
.clickable(onClick = { .clickable(onClick = {
if (selectedSize != vms.size) { if (selectedSize != vms.size) {
selected.clear() selected.clear()
for (vm in vms) { for (vm in vms) selected.add(vm.episode)
selected.add(vm.episode)
}
selectAllRes = R.drawable.ic_select_none selectAllRes = R.drawable.ic_select_none
} else { } else {
selected.clear() selected.clear()
@ -924,8 +946,7 @@ fun ConfirmAddYoutubeEpisode(sharedUrls: List<String>, showDialog: Boolean, onDi
}) { }) {
Text("Confirm") Text("Confirm")
} }
} else CircularProgressIndicator(progress = { 0.6f }, strokeWidth = 4.dp, } else CircularProgressIndicator(progress = { 0.6f }, strokeWidth = 4.dp, modifier = Modifier.padding(start = 20.dp, end = 20.dp).width(30.dp).height(30.dp))
modifier = Modifier.padding(start = 20.dp, end = 20.dp).width(30.dp).height(30.dp))
} }
} }
} }
@ -936,7 +957,6 @@ fun ConfirmAddYoutubeEpisode(sharedUrls: List<String>, showDialog: Boolean, onDi
fun EpisodesFilterDialog(filter: EpisodeFilter? = null, filtersDisabled: MutableSet<EpisodeFilter.EpisodesFilterGroup> = mutableSetOf(), fun EpisodesFilterDialog(filter: EpisodeFilter? = null, filtersDisabled: MutableSet<EpisodeFilter.EpisodesFilterGroup> = mutableSetOf(),
onDismissRequest: () -> Unit, onFilterChanged: (Set<String>) -> Unit) { onDismissRequest: () -> Unit, onFilterChanged: (Set<String>) -> Unit) {
val filterValues: MutableSet<String> = mutableSetOf() val filterValues: MutableSet<String> = mutableSetOf()
Dialog(properties = DialogProperties(usePlatformDefaultWidth = false), onDismissRequest = { onDismissRequest() }) { Dialog(properties = DialogProperties(usePlatformDefaultWidth = false), onDismissRequest = { onDismissRequest() }) {
val dialogWindowProvider = LocalView.current.parent as? DialogWindowProvider val dialogWindowProvider = LocalView.current.parent as? DialogWindowProvider
dialogWindowProvider?.window?.let { window -> dialogWindowProvider?.window?.let { window ->
@ -1001,21 +1021,74 @@ fun EpisodesFilterDialog(filter: EpisodeFilter? = null, filtersDisabled: Mutable
} }
} else { } else {
Column(modifier = Modifier.padding(start = 5.dp, bottom = 2.dp).fillMaxWidth()) { 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 selectedList = remember { MutableList(item.values.size) { mutableStateOf(false)} }
NonlazyGrid(columns = 3, itemCount = item.values.size) { index -> var expandRow by remember { mutableStateOf(false) }
var selected by remember { mutableStateOf(false) } Row {
if (selectNone) selected = false Text(stringResource(item.nameRes) + ".. :", fontWeight = FontWeight.Bold, style = MaterialTheme.typography.headlineSmall, color = textColor, modifier = Modifier.clickable {
expandRow = !expandRow
})
var lowerSelected by remember { mutableStateOf(false) }
var higherSelected by remember { mutableStateOf(false) }
Spacer(Modifier.weight(1f))
if (expandRow) Text("<<<", color = if (lowerSelected) Color.Green else textColor, style = MaterialTheme.typography.headlineSmall, modifier = Modifier.clickable {
val hIndex = selectedList.indexOfLast { it.value }
if (hIndex < 0) return@clickable
if (!lowerSelected) {
for (i in 0..hIndex) selectedList[i].value = true
} else {
for (i in 0..hIndex) selectedList[i].value = false
selectedList[hIndex].value = true
}
lowerSelected = !lowerSelected
for (i in item.values.indices) {
if (selectedList[i].value) filterValues.add(item.values[i].filterId)
else filterValues.remove(item.values[i].filterId)
}
onFilterChanged(filterValues)
})
Spacer(Modifier.weight(1f))
if (expandRow) Text("X", color = textColor, style = MaterialTheme.typography.headlineSmall, modifier = Modifier.clickable {
lowerSelected = false
higherSelected = false
for (i in item.values.indices) {
selectedList[i].value = false
filterValues.remove(item.values[i].filterId)
}
onFilterChanged(filterValues)
})
Spacer(Modifier.weight(1f))
if (expandRow) Text(">>>", color = if (higherSelected) Color.Green else textColor, style = MaterialTheme.typography.headlineSmall, modifier = Modifier.clickable {
val lIndex = selectedList.indexOfFirst { it.value }
if (lIndex < 0) return@clickable
if (!higherSelected) {
for (i in lIndex..item.values.size - 1) selectedList[i].value = true
} else {
for (i in lIndex..item.values.size - 1) selectedList[i].value = false
selectedList[lIndex].value = true
}
higherSelected = !higherSelected
for (i in item.values.indices) {
if (selectedList[i].value) filterValues.add(item.values[i].filterId)
else filterValues.remove(item.values[i].filterId)
}
onFilterChanged(filterValues)
})
Spacer(Modifier.weight(1f))
}
if (expandRow) NonlazyGrid(columns = 3, itemCount = item.values.size) { index ->
if (selectNone) selectedList[index].value = false
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
if (filter != null) { if (filter != null) {
if (item.values[index].filterId in filter.properties) selected = true if (item.values[index].filterId in filter.properties) selectedList[index].value = true
} }
} }
OutlinedButton(modifier = Modifier.padding(0.dp).heightIn(min = 20.dp).widthIn(min = 20.dp).wrapContentWidth(), OutlinedButton(
border = BorderStroke(2.dp, if (selected) Color.Green else textColor), modifier = Modifier.padding(0.dp).heightIn(min = 20.dp).widthIn(min = 20.dp).wrapContentWidth(),
border = BorderStroke(2.dp, if (selectedList[index].value) Color.Green else textColor),
onClick = { onClick = {
selectNone = false selectNone = false
selected = !selected selectedList[index].value = !selectedList[index].value
if (selected) filterValues.add(item.values[index].filterId) if (selectedList[index].value) filterValues.add(item.values[index].filterId)
else filterValues.remove(item.values[index].filterId) else filterValues.remove(item.values[index].filterId)
onFilterChanged(filterValues) onFilterChanged(filterValues)
}, },

View File

@ -92,11 +92,11 @@ abstract class BaseEpisodesFragment : Fragment(), Toolbar.OnMenuItemClickListene
EpisodeLazyColumn( EpisodeLazyColumn(
activity as MainActivity, vms = vms, activity as MainActivity, vms = vms,
leftSwipeCB = { leftSwipeCB = {
if (leftActionState.value == NoActionSwipeAction()) swipeActions.showDialog() if (leftActionState.value is NoActionSwipeAction) swipeActions.showDialog()
else leftActionState.value.performAction(it, this@BaseEpisodesFragment, swipeActions.filter ?: EpisodeFilter()) else leftActionState.value.performAction(it, this@BaseEpisodesFragment, swipeActions.filter ?: EpisodeFilter())
}, },
rightSwipeCB = { rightSwipeCB = {
if (rightActionState.value == NoActionSwipeAction()) swipeActions.showDialog() if (rightActionState.value is NoActionSwipeAction) swipeActions.showDialog()
else rightActionState.value.performAction(it, this@BaseEpisodesFragment, swipeActions.filter ?: EpisodeFilter()) else rightActionState.value.performAction(it, this@BaseEpisodesFragment, swipeActions.filter ?: EpisodeFilter())
}, },
) )

View File

@ -111,11 +111,11 @@ import java.util.*
InforBar(infoBarText, leftAction = leftActionState, rightAction = rightActionState, actionConfig = {swipeActions.showDialog()}) InforBar(infoBarText, leftAction = leftActionState, rightAction = rightActionState, actionConfig = {swipeActions.showDialog()})
EpisodeLazyColumn(activity as MainActivity, vms = vms, EpisodeLazyColumn(activity as MainActivity, vms = vms,
leftSwipeCB = { leftSwipeCB = {
if (leftActionState.value == NoActionSwipeAction()) swipeActions.showDialog() if (leftActionState.value is NoActionSwipeAction) swipeActions.showDialog()
else leftActionState.value.performAction(it, this@DownloadsFragment, swipeActions.filter ?: EpisodeFilter()) else leftActionState.value.performAction(it, this@DownloadsFragment, swipeActions.filter ?: EpisodeFilter())
}, },
rightSwipeCB = { rightSwipeCB = {
if (rightActionState.value == NoActionSwipeAction()) swipeActions.showDialog() if (rightActionState.value is NoActionSwipeAction) swipeActions.showDialog()
else rightActionState.value.performAction(it, this@DownloadsFragment, swipeActions.filter ?: EpisodeFilter()) else rightActionState.value.performAction(it, this@DownloadsFragment, swipeActions.filter ?: EpisodeFilter())
}, },
actionButton_ = { DeleteActionButton(it) }) actionButton_ = { DeleteActionButton(it) })

View File

@ -190,11 +190,12 @@ import java.util.concurrent.Semaphore
EpisodeLazyColumn(activity as MainActivity, vms = vms, feed = feed, EpisodeLazyColumn(activity as MainActivity, vms = vms, feed = feed,
refreshCB = { FeedUpdateManager.runOnceOrAsk(requireContext(), feed) }, refreshCB = { FeedUpdateManager.runOnceOrAsk(requireContext(), feed) },
leftSwipeCB = { leftSwipeCB = {
if (leftActionState.value == NoActionSwipeAction()) swipeActions.showDialog() if (leftActionState.value is NoActionSwipeAction) swipeActions.showDialog()
else leftActionState.value.performAction(it, this@FeedEpisodesFragment, swipeActions.filter ?: EpisodeFilter()) else leftActionState.value.performAction(it, this@FeedEpisodesFragment, swipeActions.filter ?: EpisodeFilter())
}, },
rightSwipeCB = { rightSwipeCB = {
if (rightActionState.value == NoActionSwipeAction()) swipeActions.showDialog() Logd(TAG, "rightActionState: ${rightActionState.value.getId()}")
if (rightActionState.value is NoActionSwipeAction) swipeActions.showDialog()
else rightActionState.value.performAction(it, this@FeedEpisodesFragment, swipeActions.filter ?: EpisodeFilter()) else rightActionState.value.performAction(it, this@FeedEpisodesFragment, swipeActions.filter ?: EpisodeFilter())
}, },
) )
@ -402,7 +403,7 @@ import java.util.concurrent.Semaphore
} }
when (item.itemId) { when (item.itemId) {
R.id.visit_website_item -> if (feed!!.link != null) IntentUtils.openInBrowser(requireContext(), feed!!.link!!) R.id.visit_website_item -> if (feed!!.link != null) IntentUtils.openInBrowser(requireContext(), feed!!.link!!)
R.id.share_feed -> ShareUtils.shareFeedLink(requireContext(), feed!!) R.id.share_feed -> ShareUtils.shareFeedLinkNew(requireContext(), feed!!)
R.id.refresh_feed -> FeedUpdateManager.runOnceOrAsk(requireContext(), feed) R.id.refresh_feed -> FeedUpdateManager.runOnceOrAsk(requireContext(), feed)
R.id.refresh_complete_item -> { R.id.refresh_complete_item -> {
Thread { Thread {

View File

@ -347,7 +347,7 @@ class FeedInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
override fun onMenuItemClick(item: MenuItem): Boolean { override fun onMenuItemClick(item: MenuItem): Boolean {
when (item.itemId) { when (item.itemId) {
R.id.visit_website_item -> if (feed.link != null) IntentUtils.openInBrowser(requireContext(), feed.link!!) R.id.visit_website_item -> if (feed.link != null) IntentUtils.openInBrowser(requireContext(), feed.link!!)
R.id.share_item -> ShareUtils.shareFeedLink(requireContext(), feed) R.id.share_item -> ShareUtils.shareFeedLinkNew(requireContext(), feed)
R.id.reconnect_local_folder -> { R.id.reconnect_local_folder -> {
val alert = MaterialAlertDialogBuilder(requireContext()) val alert = MaterialAlertDialogBuilder(requireContext())
alert.setMessage(R.string.reconnect_local_folder_warning) alert.setMessage(R.string.reconnect_local_folder_warning)

View File

@ -11,9 +11,10 @@ import ac.mdiq.podcini.playback.service.PlaybackService
import ac.mdiq.podcini.playback.service.PlaybackService.Companion.mediaBrowser import ac.mdiq.podcini.playback.service.PlaybackService.Companion.mediaBrowser
import ac.mdiq.podcini.playback.service.PlaybackService.Companion.playbackService import ac.mdiq.podcini.playback.service.PlaybackService.Companion.playbackService
import ac.mdiq.podcini.preferences.UserPreferences import ac.mdiq.podcini.preferences.UserPreferences
import ac.mdiq.podcini.preferences.UserPreferences.appPrefs
import ac.mdiq.podcini.storage.database.Queues.clearQueue import ac.mdiq.podcini.storage.database.Queues.clearQueue
import ac.mdiq.podcini.storage.database.Queues.isQueueKeepSorted import ac.mdiq.podcini.storage.database.Queues.isQueueKeepSorted
import ac.mdiq.podcini.storage.database.Queues.isQueueLocked import ac.mdiq.podcini.storage.database.Queues.moveInQueue
import ac.mdiq.podcini.storage.database.Queues.queueKeepSortedOrder import ac.mdiq.podcini.storage.database.Queues.queueKeepSortedOrder
import ac.mdiq.podcini.storage.database.RealmDB.realm import ac.mdiq.podcini.storage.database.RealmDB.realm
import ac.mdiq.podcini.storage.database.RealmDB.runOnIOScope import ac.mdiq.podcini.storage.database.RealmDB.runOnIOScope
@ -33,7 +34,6 @@ import ac.mdiq.podcini.ui.activity.MainActivity
import ac.mdiq.podcini.ui.compose.* import ac.mdiq.podcini.ui.compose.*
import ac.mdiq.podcini.ui.dialog.ConfirmationDialog import ac.mdiq.podcini.ui.dialog.ConfirmationDialog
import ac.mdiq.podcini.ui.dialog.EpisodeSortDialog import ac.mdiq.podcini.ui.dialog.EpisodeSortDialog
import ac.mdiq.podcini.ui.utils.EmptyViewHandler
import ac.mdiq.podcini.util.EventFlow import ac.mdiq.podcini.util.EventFlow
import ac.mdiq.podcini.util.FlowEvent import ac.mdiq.podcini.util.FlowEvent
import ac.mdiq.podcini.util.Logd import ac.mdiq.podcini.util.Logd
@ -88,7 +88,7 @@ import kotlin.math.max
private var _binding: ComposeFragmentBinding? = null private var _binding: ComposeFragmentBinding? = null
private val binding get() = _binding!! private val binding get() = _binding!!
private lateinit var emptyViewHandler: EmptyViewHandler // private lateinit var emptyViewHandler: EmptyViewHandler
private lateinit var toolbar: MaterialToolbar private lateinit var toolbar: MaterialToolbar
private lateinit var swipeActions: SwipeActions private lateinit var swipeActions: SwipeActions
private lateinit var swipeActionsBin: SwipeActions private lateinit var swipeActionsBin: SwipeActions
@ -102,6 +102,8 @@ import kotlin.math.max
private var leftActionStateBin = mutableStateOf<SwipeAction>(NoActionSwipeAction()) private var leftActionStateBin = mutableStateOf<SwipeAction>(NoActionSwipeAction())
private var rightActionStateBin = mutableStateOf<SwipeAction>(NoActionSwipeAction()) private var rightActionStateBin = mutableStateOf<SwipeAction>(NoActionSwipeAction())
var isQueueLocked = appPrefs.getBoolean(UserPreferences.Prefs.prefQueueLocked.name, true)
private lateinit var spinnerLayout: View private lateinit var spinnerLayout: View
private lateinit var queueNames: Array<String> private lateinit var queueNames: Array<String>
private lateinit var spinnerTexts: MutableList<String> private lateinit var spinnerTexts: MutableList<String>
@ -115,7 +117,7 @@ import kotlin.math.max
private var showBin by mutableStateOf(false) private var showBin by mutableStateOf(false)
// private var dragDropEnabled: Boolean = !(isQueueKeepSorted || isQueueLocked) private var dragDropEnabled by mutableStateOf(!(isQueueKeepSorted || isQueueLocked))
private lateinit var browserFuture: ListenableFuture<MediaBrowser> private lateinit var browserFuture: ListenableFuture<MediaBrowser>
@ -190,11 +192,11 @@ import kotlin.math.max
Column { Column {
InforBar(infoBarText, leftAction = leftActionStateBin, rightAction = rightActionStateBin, actionConfig = { swipeActionsBin.showDialog() }) InforBar(infoBarText, leftAction = leftActionStateBin, rightAction = rightActionStateBin, actionConfig = { swipeActionsBin.showDialog() })
val leftCB = { episode: Episode -> val leftCB = { episode: Episode ->
if (leftActionStateBin.value == NoActionSwipeAction()) swipeActionsBin.showDialog() if (leftActionStateBin.value is NoActionSwipeAction) swipeActionsBin.showDialog()
else leftActionStateBin.value.performAction(episode, this@QueuesFragment, swipeActionsBin.filter ?: EpisodeFilter()) else leftActionStateBin.value.performAction(episode, this@QueuesFragment, swipeActionsBin.filter ?: EpisodeFilter())
} }
val rightCB = { episode: Episode -> val rightCB = { episode: Episode ->
if (rightActionStateBin.value == NoActionSwipeAction()) swipeActionsBin.showDialog() if (rightActionStateBin.value is NoActionSwipeAction) swipeActionsBin.showDialog()
else rightActionStateBin.value.performAction(episode, this@QueuesFragment, swipeActionsBin.filter ?: EpisodeFilter()) else rightActionStateBin.value.performAction(episode, this@QueuesFragment, swipeActionsBin.filter ?: EpisodeFilter())
} }
EpisodeLazyColumn(activity as MainActivity, vms = vms, leftSwipeCB = { leftCB(it) }, rightSwipeCB = { rightCB(it) }) EpisodeLazyColumn(activity as MainActivity, vms = vms, leftSwipeCB = { leftCB(it) }, rightSwipeCB = { rightCB(it) })
@ -203,14 +205,16 @@ import kotlin.math.max
Column { Column {
InforBar(infoBarText, leftAction = leftActionState, rightAction = rightActionState, actionConfig = { swipeActions.showDialog() }) InforBar(infoBarText, leftAction = leftActionState, rightAction = rightActionState, actionConfig = { swipeActions.showDialog() })
val leftCB = { episode: Episode -> val leftCB = { episode: Episode ->
if (leftActionState.value == NoActionSwipeAction()) swipeActions.showDialog() if (leftActionState.value is NoActionSwipeAction) swipeActions.showDialog()
else leftActionState.value.performAction(episode, this@QueuesFragment, swipeActions.filter ?: EpisodeFilter()) else leftActionState.value.performAction(episode, this@QueuesFragment, swipeActions.filter ?: EpisodeFilter())
} }
val rightCB = { episode: Episode -> val rightCB = { episode: Episode ->
if (rightActionState.value == NoActionSwipeAction()) swipeActions.showDialog() if (rightActionState.value is NoActionSwipeAction) swipeActions.showDialog()
else rightActionState.value.performAction(episode, this@QueuesFragment, swipeActions.filter ?: EpisodeFilter()) else rightActionState.value.performAction(episode, this@QueuesFragment, swipeActions.filter ?: EpisodeFilter())
} }
EpisodeLazyColumn(activity as MainActivity, vms = vms, leftSwipeCB = { leftCB(it) }, rightSwipeCB = { rightCB(it) }) EpisodeLazyColumn(activity as MainActivity, vms = vms,
isDraggable = dragDropEnabled, dragCB = { iFrom, iTo -> moveInQueue(iFrom, iTo, true) },
leftSwipeCB = { leftCB(it) }, rightSwipeCB = { rightCB(it) })
} }
} }
} }
@ -219,13 +223,6 @@ import kotlin.math.max
lifecycle.addObserver(swipeActions) lifecycle.addObserver(swipeActions)
refreshSwipeTelltale() refreshSwipeTelltale()
emptyViewHandler = EmptyViewHandler(requireContext())
// emptyViewHandler.attachToRecyclerView(recyclerView)
emptyViewHandler.setIcon(R.drawable.ic_playlist_play)
emptyViewHandler.setTitle(R.string.no_items_header_label)
emptyViewHandler.setMessage(R.string.no_items_label)
// emptyViewHandler.updateAdapter(adapter)
return binding.root return binding.root
} }
@ -612,10 +609,10 @@ import kotlin.math.max
@UnstableApi private fun toggleQueueLock() { @UnstableApi private fun toggleQueueLock() {
val isLocked: Boolean = isQueueLocked val isLocked: Boolean = isQueueLocked
if (isLocked) setQueueLocked(false) if (isLocked) setQueueLock(false)
else { else {
val shouldShowLockWarning: Boolean = prefs!!.getBoolean(PREF_SHOW_LOCK_WARNING, true) val shouldShowLockWarning: Boolean = prefs!!.getBoolean(PREF_SHOW_LOCK_WARNING, true)
if (!shouldShowLockWarning) setQueueLocked(true) if (!shouldShowLockWarning) setQueueLock(true)
else { else {
val builder = MaterialAlertDialogBuilder(requireContext()) val builder = MaterialAlertDialogBuilder(requireContext())
builder.setTitle(R.string.lock_queue) builder.setTitle(R.string.lock_queue)
@ -628,7 +625,7 @@ import kotlin.math.max
builder.setPositiveButton(R.string.lock_queue) { _: DialogInterface?, _: Int -> builder.setPositiveButton(R.string.lock_queue) { _: DialogInterface?, _: Int ->
prefs!!.edit().putBoolean(PREF_SHOW_LOCK_WARNING, !checkDoNotShowAgain.isChecked).apply() prefs!!.edit().putBoolean(PREF_SHOW_LOCK_WARNING, !checkDoNotShowAgain.isChecked).apply()
setQueueLocked(true) setQueueLock(true)
} }
builder.setNegativeButton(R.string.cancel_label, null) builder.setNegativeButton(R.string.cancel_label, null)
builder.show() builder.show()
@ -636,8 +633,10 @@ import kotlin.math.max
} }
} }
@UnstableApi private fun setQueueLocked(locked: Boolean) { @UnstableApi private fun setQueueLock(locked: Boolean) {
isQueueLocked = locked isQueueLocked = locked
appPrefs.edit().putBoolean(UserPreferences.Prefs.prefQueueLocked.name, locked).apply()
dragDropEnabled = !(isQueueKeepSorted || isQueueLocked)
refreshMenuItems() refreshMenuItems()
// adapter?.updateDragDropEnabled() // adapter?.updateDragDropEnabled()
@ -677,7 +676,7 @@ import kotlin.math.max
loadItemsRunning = true loadItemsRunning = true
Logd(TAG, "loadCurQueue() called ${curQueue.name}") Logd(TAG, "loadCurQueue() called ${curQueue.name}")
while (curQueue.name.isEmpty()) runBlocking { delay(100) } while (curQueue.name.isEmpty()) runBlocking { delay(100) }
if (queueItems.isNotEmpty()) emptyViewHandler.hide() // if (queueItems.isNotEmpty()) emptyViewHandler.hide()
queueItems.clear() queueItems.clear()
vms.clear() vms.clear()
if (showBin) queueItems.addAll(realm.query(Episode::class, "id IN $0", curQueue.idsBinList) if (showBin) queueItems.addAll(realm.query(Episode::class, "id IN $0", curQueue.idsBinList)
@ -748,7 +747,6 @@ import kotlin.math.max
val TAG = QueuesFragment::class.simpleName ?: "Anonymous" val TAG = QueuesFragment::class.simpleName ?: "Anonymous"
private const val KEY_UP_ARROW = "up_arrow" private const val KEY_UP_ARROW = "up_arrow"
private const val PREFS = "QueueFragment" private const val PREFS = "QueueFragment"
private const val PREF_SHOW_LOCK_WARNING = "show_lock_warning" private const val PREF_SHOW_LOCK_WARNING = "show_lock_warning"

View File

@ -36,10 +36,7 @@ import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.Checkbox import androidx.compose.material3.*
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@ -272,7 +269,17 @@ class SearchFragment : Fragment() {
@Composable @Composable
fun CriteriaList() { fun CriteriaList() {
val textColor = MaterialTheme.colorScheme.onSurface val textColor = MaterialTheme.colorScheme.onSurface
NonlazyGrid(columns = 2, itemCount = SearchBy.entries.size) { index -> var showGrid by remember { mutableStateOf(false) }
Column {
Row {
Button(onClick = {showGrid = !showGrid}) {
Text(stringResource(R.string.show_criteria))
}
Button(onClick = { searchOnline() }) {
Text(stringResource(R.string.search_online))
}
}
if (showGrid) NonlazyGrid(columns = 2, itemCount = SearchBy.entries.size) { index ->
val c = SearchBy.entries[index] val c = SearchBy.entries[index]
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(start = 10.dp, end = 10.dp)) { Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(start = 10.dp, end = 10.dp)) {
var isChecked by remember { mutableStateOf(true) } var isChecked by remember { mutableStateOf(true) }
@ -288,6 +295,7 @@ class SearchFragment : Fragment() {
} }
} }
} }
}
@OptIn(ExperimentalFoundationApi::class) @OptIn(ExperimentalFoundationApi::class)
@Composable @Composable

View File

@ -1155,21 +1155,73 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
} }
} else { } else {
Column(modifier = Modifier.padding(start = 5.dp, bottom = 2.dp).fillMaxWidth()) { 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 selectedList = remember { MutableList(item.values.size) { mutableStateOf(false)} }
NonlazyGrid(columns = 3, itemCount = item.values.size) { index -> var expandRow by remember { mutableStateOf(false) }
var selected by remember { mutableStateOf(false) } Row {
if (selectNone) selected = false Text(stringResource(item.nameRes) + ".. :", fontWeight = FontWeight.Bold, style = MaterialTheme.typography.headlineSmall, color = textColor, modifier = Modifier.clickable {
expandRow = !expandRow
})
var lowerSelected by remember { mutableStateOf(false) }
var higherSelected by remember { mutableStateOf(false) }
Spacer(Modifier.weight(1f))
if (expandRow) Text("<<<", color = if (lowerSelected) Color.Green else textColor, style = MaterialTheme.typography.headlineSmall, modifier = Modifier.clickable {
val hIndex = selectedList.indexOfLast { it.value }
if (hIndex < 0) return@clickable
if (!lowerSelected) {
for (i in 0..hIndex) selectedList[i].value = true
} else {
for (i in 0..hIndex) selectedList[i].value = false
selectedList[hIndex].value = true
}
lowerSelected = !lowerSelected
for (i in item.values.indices) {
if (selectedList[i].value) filterValues.add(item.values[i].filterId)
else filterValues.remove(item.values[i].filterId)
}
onFilterChanged(filterValues)
})
Spacer(Modifier.weight(1f))
if (expandRow) Text("X", color = textColor, style = MaterialTheme.typography.headlineSmall, modifier = Modifier.clickable {
lowerSelected = false
higherSelected = false
for (i in item.values.indices) {
selectedList[i].value = false
filterValues.remove(item.values[i].filterId)
}
onFilterChanged(filterValues)
})
Spacer(Modifier.weight(1f))
if (expandRow) Text(">>>", color = if (higherSelected) Color.Green else textColor, style = MaterialTheme.typography.headlineSmall, modifier = Modifier.clickable {
val lIndex = selectedList.indexOfFirst { it.value }
if (lIndex < 0) return@clickable
if (!higherSelected) {
for (i in lIndex..item.values.size - 1) selectedList[i].value = true
} else {
for (i in lIndex..item.values.size - 1) selectedList[i].value = false
selectedList[lIndex].value = true
}
higherSelected = !higherSelected
for (i in item.values.indices) {
if (selectedList[i].value) filterValues.add(item.values[i].filterId)
else filterValues.remove(item.values[i].filterId)
}
onFilterChanged(filterValues)
})
Spacer(Modifier.weight(1f))
}
if (expandRow) NonlazyGrid(columns = 3, itemCount = item.values.size) { index ->
if (selectNone) selectedList[index].value = false
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
if (filter != null) { if (filter != null) {
if (item.values[index].filterId in filter.properties) selected = true if (item.values[index].filterId in filter.properties) selectedList[index].value = true
} }
} }
OutlinedButton(modifier = Modifier.padding(0.dp).heightIn(min = 20.dp).widthIn(min = 20.dp).wrapContentWidth(), 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), border = BorderStroke(2.dp, if (selectedList[index].value) Color.Green else textColor),
onClick = { onClick = {
selectNone = false selectNone = false
selected = !selected selectedList[index].value = !selectedList[index].value
if (selected) filterValues.add(item.values[index].filterId) if (selectedList[index].value) filterValues.add(item.values[index].filterId)
else filterValues.remove(item.values[index].filterId) else filterValues.remove(item.values[index].filterId)
onFilterChanged(filterValues) onFilterChanged(filterValues)
}, },

View File

@ -47,6 +47,10 @@ object ShareUtils {
shareLink(context, text) shareLink(context, text)
} }
fun shareFeedLinkNew(context: Context, feed: Feed) {
shareLink(context, feed.downloadUrl?:"")
}
@JvmStatic @JvmStatic
fun hasLinkToShare(item: Episode?): Boolean { fun hasLinkToShare(item: Episode?): Boolean {
return item?.getLinkWithFallback() != null return item?.getLinkWithFallback() != null

View File

@ -419,6 +419,7 @@
<string name="duration">Duration</string> <string name="duration">Duration</string>
<string name="episode_title">Episode title</string> <string name="episode_title">Episode title</string>
<string name="feed_title">Podcast title</string> <string name="feed_title">Podcast title</string>
<string name="show_criteria">Show criteria</string>
<string name="title">Title</string> <string name="title">Title</string>
<string name="author">Author</string> <string name="author">Author</string>
<string name="random">Random</string> <string name="random">Random</string>

View File

@ -1,3 +1,16 @@
# 6.12.6
* in SearchFragment, made search criteria options are togglable, and added back search online option
* further enhanced filtering routines
* categories with multi-selections are expandable/collapsable
* once expanded, added three icons: <<<, X, >>>
* tapping on <<< will select all lower items, >>> all higher items, X to clear selection in the category
* manual sorting of Queues is back.
* The way to enable/disable it, uncheck/check "Lock queue" in menu
* if "Lock queue" doesn't appear, top "Sort" in the menu and then, uncheck "Keep sorted" in the popup
* in episodes lists, swipe dialog shows up when the swipe has not been configured
* changed the info shared from FeedInfo and FeedEpisodes, now it shares simply the download url of the feed
# 6.12.5 # 6.12.5
* fixed a long-standing issue in the play apk where rewind/forward buttons don't work during cast * fixed a long-standing issue in the play apk where rewind/forward buttons don't work during cast

View File

@ -0,0 +1,12 @@
Version 6.12.6
* in SearchFragment, made search criteria options are togglable, and added back search online option
* further enhanced filtering routines
* categories with multi-selections are expandable/collapsable
* once expanded, added three icons: <<<, X, >>>
* tapping on <<< will select all lower items, >>> all higher items, X to clear selection in the category
* manual sorting of Queues is back.
* The way to enable/disable it, uncheck/check "Lock queue" in menu
* if "Lock queue" doesn't appear, top "Sort" in the menu and then, uncheck "Keep sorted" in the popup
* in episodes lists, swipe dialog shows up when the swipe has not been configured
* changed the info shared from FeedInfo and FeedEpisodes, now it shares simply the download url of the feed