added about cast in readme

This commit is contained in:
Xilin Jia 2024-09-25 20:23:21 +01:00
parent 8b4747bc42
commit 0b68607bd9
4 changed files with 94 additions and 30 deletions

View File

@ -15,6 +15,7 @@ An open source podcast instrument, attuned to Puccini ![Puccini](./images/Puccin
#### Podcini.R version 6.5 as a major step forward brings YouTube contents in the app. Channels can be searched, received from share, subscribed. Since 6.6, podcasts, playlists as well as single media from Youtube and YT Music can be shared to Podcini. For more see the Youtube section below or the changelogs
That means finally: [Nessun dorma](https://www.youtube.com/watch?v=cWc7vYjgnTs)
#### For Podcini to show up on car's HUD with Android Auto, please read AnroidAuto.md for instructions.
#### If you need to cast to an external speaker, you should install the "play" apk, not the "free" apk, that's about the difference between the two.
#### If you are migrating from Podcini version 5, please read the migrationTo5.md file for migration instructions.
This project was developed from a fork of [AntennaPod](<https://github.com/AntennaPod/AntennaPod>) as of Feb 5 2024.

View File

@ -58,7 +58,8 @@ import kotlin.math.sin
open class SwipeActions(dragDirs: Int, private val fragment: Fragment, private val tag: String)
: ItemTouchHelper.SimpleCallback(dragDirs, ItemTouchHelper.RIGHT or ItemTouchHelper.LEFT), DefaultLifecycleObserver {
private var filter: EpisodeFilter? = null
@set:JvmName("setFilterProperty")
var filter: EpisodeFilter? = null
var actions: Actions? = null
var swipeOutEnabled: Boolean = true
@ -79,6 +80,7 @@ open class SwipeActions(dragDirs: Int, private val fragment: Fragment, private v
actions = null
}
@JvmName("setFilterFunction")
fun setFilter(filter: EpisodeFilter?) {
this.filter = filter
}
@ -186,7 +188,8 @@ open class SwipeActions(dragDirs: Int, private val fragment: Fragment, private v
return if (swipeOutEnabled) 0.6f else 1.0f
}
@UnstableApi override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {
@UnstableApi
override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {
super.clearView(recyclerView, viewHolder)
if (swipedOutTo != 0) {

View File

@ -8,15 +8,19 @@ import ac.mdiq.podcini.storage.database.Episodes
import ac.mdiq.podcini.storage.database.Episodes.setPlayState
import ac.mdiq.podcini.storage.database.Queues
import ac.mdiq.podcini.storage.database.Queues.removeFromQueue
import ac.mdiq.podcini.storage.database.RealmDB.realm
import ac.mdiq.podcini.storage.model.Episode
import ac.mdiq.podcini.storage.model.MediaType
import ac.mdiq.podcini.storage.utils.DurationConverter
import ac.mdiq.podcini.storage.utils.ImageResourceUtils
import ac.mdiq.podcini.ui.actions.handler.EpisodeMultiSelectHandler.PutToQueueDialog
import ac.mdiq.podcini.ui.actions.swipeactions.SwipeAction
import ac.mdiq.podcini.ui.activity.MainActivity
import ac.mdiq.podcini.ui.adapter.EpisodesAdapter.EpisodeInfoFragment
import ac.mdiq.podcini.ui.fragment.FeedInfoFragment
import ac.mdiq.podcini.ui.utils.LocalDeleteModal
import ac.mdiq.podcini.ui.view.EpisodeViewHolder
import ac.mdiq.podcini.ui.view.EpisodeViewHolder.Companion
import ac.mdiq.podcini.util.Logd
import ac.mdiq.podcini.util.MiscFormatter.formatAbbrev
import android.text.format.Formatter
@ -49,24 +53,25 @@ import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import androidx.constraintlayout.compose.ConstraintLayout
import coil.compose.AsyncImage
import kotlinx.coroutines.launch
import io.realm.kotlin.notifications.SingleQueryChange
import io.realm.kotlin.notifications.UpdatedObject
import kotlinx.coroutines.*
import kotlin.math.roundToInt
@Composable
fun InforBar(text: MutableState<String>, leftActionConfig: () -> Unit, rightActionConfig: () -> Unit) {
// val textState by remember { mutableStateOf(text) }
fun InforBar(text: MutableState<String>, leftAction: MutableState<SwipeAction?>, rightAction: MutableState<SwipeAction?>, actionConfig: () -> Unit) {
val textColor = MaterialTheme.colors.onSurface
Logd("InforBar", "textState: ${text.value}")
Row {
Image(painter = painterResource(R.drawable.ic_questionmark), contentDescription = "left_action_icon",
Modifier.width(24.dp).height(24.dp).clickable(onClick = leftActionConfig))
Image(painter = painterResource(leftAction.value?.getActionIcon() ?:R.drawable.ic_questionmark), contentDescription = "left_action_icon",
Modifier.width(24.dp).height(24.dp).clickable(onClick = actionConfig))
Image(painter = painterResource(R.drawable.baseline_arrow_left_alt_24), contentDescription = "left_arrow", Modifier.width(24.dp).height(24.dp))
Spacer(modifier = Modifier.weight(1f))
Text(text.value, color = textColor, style = MaterialTheme.typography.body2)
Spacer(modifier = Modifier.weight(1f))
Image(painter = painterResource(R.drawable.baseline_arrow_right_alt_24), contentDescription = "right_arrow", Modifier.width(24.dp).height(24.dp))
Image(painter = painterResource(R.drawable.ic_questionmark), contentDescription = "right_action_icon",
Modifier.width(24.dp).height(24.dp).clickable(onClick = rightActionConfig))
Image(painter = painterResource(rightAction.value?.getActionIcon() ?:R.drawable.ic_questionmark), contentDescription = "right_action_icon",
Modifier.width(24.dp).height(24.dp).clickable(onClick = actionConfig))
}
}
@ -144,10 +149,8 @@ fun EpisodeSpeedDialOptions(activity: MainActivity, selected: List<Episode>): Li
}
@Composable
fun EpisodeLazyColumn(activity: MainActivity, episodes: SnapshotStateList<Episode>, leftAction: (Episode) -> Unit, rightAction: (Episode) -> Unit) {
fun EpisodeLazyColumn(activity: MainActivity, episodes: SnapshotStateList<Episode>, leftActionCB: (Episode) -> Unit, rightActionCB: (Episode) -> Unit) {
var selectMode by remember { mutableStateOf(false) }
var longPressedItem by remember { mutableStateOf<Episode?>(null) }
var longPressedPosition by remember { mutableStateOf(0) }
val selectedIds by remember { mutableStateOf(mutableSetOf<Long>()) }
val selected = remember { mutableListOf<Episode>()}
val coroutineScope = rememberCoroutineScope()
@ -173,10 +176,10 @@ fun EpisodeLazyColumn(activity: MainActivity, episodes: SnapshotStateList<Episod
if (velocity > 1000f || velocity < -1000f) {
if (velocity > 0) {
Logd("EpisodeLazyColumn","Fling to the right with velocity: $velocity")
rightAction(episode)
rightActionCB(episode)
} else {
Logd("EpisodeLazyColumn","Fling to the left with velocity: $velocity")
leftAction(episode)
leftActionCB(episode)
}
}
offsetX.animateTo(
@ -216,8 +219,6 @@ fun EpisodeLazyColumn(activity: MainActivity, episodes: SnapshotStateList<Episod
selectedIds.clear()
}
Logd("EpisodeLazyColumn", "long clicked: ${episode.title}")
longPressedItem = episode
longPressedPosition = index
},
iconOnClick = {
Logd("EpisodeLazyColumn", "icon clicked!")
@ -249,8 +250,65 @@ fun EpisodeLazyColumn(activity: MainActivity, episodes: SnapshotStateList<Episod
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun EpisodeRow(episode: Episode, isSelected: MutableState<Boolean>, onClick: () -> Unit, onLongClick: () -> Unit, iconOnClick: () -> Unit = {}) {
fun EpisodeRow(episode_: Episode, isSelected: MutableState<Boolean>, onClick: () -> Unit, onLongClick: () -> Unit, iconOnClick: () -> Unit = {}) {
var episode = episode_
val textColor = MaterialTheme.colors.onSurface
var positionState by remember(episode) { mutableStateOf(episode.media?.position?:0) }
var playedState by remember { mutableStateOf(episode.isPlayed()) }
var farvoriteState by remember { mutableStateOf(episode.isFavorite) }
var inProgressState by remember { mutableStateOf(episode.isInProgress) }
var episodeMonitor: Job? by remember { mutableStateOf(null) }
var mediaMonitor: Job? by remember { mutableStateOf(null) }
if (episodeMonitor == null) {
val item_ = realm.query(Episode::class).query("id == ${episode.id}").first()
episodeMonitor = CoroutineScope(Dispatchers.Default).launch {
val episodeFlow = item_.asFlow()
episodeFlow.collect { changes: SingleQueryChange<Episode> ->
when (changes) {
is UpdatedObject -> {
Logd("EpisodeRow", "episodeMonitor UpdatedObject ${changes.obj.title} ${changes.changedFields.joinToString()}")
episode = changes.obj
playedState = episode.isPlayed()
farvoriteState = episode.isFavorite
// withContext(Dispatchers.Main) {
//// bind(changes.obj)
//// if (posIndex >= 0) refreshAdapterPosCallback?.invoke(posIndex, changes.obj)
// }
}
else -> {}
}
}
}
}
if (mediaMonitor == null) {
val item_ = realm.query(Episode::class).query("id == ${episode.id}").first()
mediaMonitor = CoroutineScope(Dispatchers.Default).launch {
val episodeFlow = item_.asFlow(listOf("media.*"))
episodeFlow.collect { changes: SingleQueryChange<Episode> ->
when (changes) {
is UpdatedObject -> {
Logd("EpisodeRow", "mediaMonitor UpdatedObject ${changes.obj.title} ${changes.changedFields.joinToString()}")
episode = changes.obj
positionState = episode.media?.position?:0
inProgressState = episode.isInProgress
// withContext(Dispatchers.Main) {
//// updatePlaybackPositionNew(changes.obj)
////// bind(changes.obj)
//// if (posIndex >= 0) refreshAdapterPosCallback?.invoke(posIndex, changes.obj)
// }
}
else -> {}
}
}
}
}
DisposableEffect(Unit) {
onDispose {
episodeMonitor?.cancel()
mediaMonitor?.cancel()
}
}
Row (Modifier.background(if (isSelected.value) MaterialTheme.colors.secondary else MaterialTheme.colors.surface)) {
if (false) {
val typedValue = TypedValue()
@ -260,8 +318,6 @@ fun EpisodeRow(episode: Episode, isSelected: MutableState<Boolean>, onClick: ()
modifier = Modifier.width(16.dp).align(Alignment.CenterVertically))
}
ConstraintLayout(modifier = Modifier.width(56.dp).height(56.dp)) {
var playedState by remember { mutableStateOf(false) }
playedState = episode.isPlayed()
Logd("EpisodeRow", "playedState: $playedState")
val (image1, image2) = createRefs()
val imgLoc = ImageResourceUtils.getEpisodeListImageLocation(episode)
@ -286,7 +342,7 @@ fun EpisodeRow(episode: Episode, isSelected: MutableState<Boolean>, onClick: ()
Row {
if (episode.media?.getMediaType() == MediaType.VIDEO)
Image(painter = painterResource(R.drawable.ic_videocam), contentDescription = "isVideo", Modifier.width(14.dp).height(14.dp))
if (episode.isFavorite)
if (farvoriteState)
Image(painter = painterResource(R.drawable.ic_star), contentDescription = "isFavorite", Modifier.width(14.dp).height(14.dp))
if (curQueue.contains(episode))
Image(painter = painterResource(R.drawable.ic_playlist_play), contentDescription = "ivInPlaylist", Modifier.width(14.dp).height(14.dp))
@ -296,9 +352,9 @@ fun EpisodeRow(episode: Episode, isSelected: MutableState<Boolean>, onClick: ()
Text(if((episode.media?.size?:0) > 0) Formatter.formatShortFileSize(LocalContext.current, episode.media!!.size) else "", color = textColor, style = MaterialTheme.typography.body2)
}
Text(episode.title?:"", color = textColor, maxLines = 2, overflow = TextOverflow.Ellipsis)
if (InTheatre.isCurMedia(episode.media) || episode.isInProgress) {
val pos = episode.media!!.getPosition()
val dur = episode.media!!.getDuration()
if (InTheatre.isCurMedia(episode.media) || inProgressState) {
val pos = positionState
val dur = remember(episode, episode.media) { episode.media!!.getDuration()}
val prog = if (dur > 0 && pos >= 0 && dur >= pos) 1.0f * pos / dur else 0f
Row {
Text(DurationConverter.getDurationStringLong(pos), color = textColor)

View File

@ -16,6 +16,7 @@ import ac.mdiq.podcini.storage.model.EpisodeFilter
import ac.mdiq.podcini.storage.model.EpisodeMedia
import ac.mdiq.podcini.storage.model.EpisodeSortOrder
import ac.mdiq.podcini.storage.utils.EpisodeUtil
import ac.mdiq.podcini.ui.actions.swipeactions.SwipeAction
import ac.mdiq.podcini.ui.actions.swipeactions.SwipeActions
import ac.mdiq.podcini.ui.activity.MainActivity
import ac.mdiq.podcini.ui.compose.CustomTheme
@ -36,8 +37,7 @@ import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.appcompat.widget.Toolbar
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.*
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.media3.common.util.UnstableApi
@ -65,6 +65,8 @@ import java.util.*
private var episodes = mutableStateListOf<Episode>()
private var infoBarText = mutableStateOf("")
var leftActionState = mutableStateOf<SwipeAction?>(null)
var rightActionState = mutableStateOf<SwipeAction?>(null)
private lateinit var toolbar: MaterialToolbar
// private lateinit var recyclerView: EpisodesRecyclerView
@ -94,17 +96,18 @@ import java.util.*
(activity as MainActivity).setupToolbarToggle(toolbar, displayUpArrow)
swipeActions = SwipeActions(this, TAG)
swipeActions.setFilter(EpisodeFilter(EpisodeFilter.States.downloaded.name))
binding.infobar.setContent {
CustomTheme(requireContext()) {
InforBar(infoBarText, leftActionConfig = {swipeActions.showDialog()}, rightActionConfig = { swipeActions.showDialog() })
InforBar(infoBarText, leftAction = leftActionState, rightAction = rightActionState, actionConfig = {swipeActions.showDialog()})
}
}
binding.lazyColumn.setContent {
CustomTheme(requireContext()) {
EpisodeLazyColumn(activity as MainActivity, episodes = episodes,
leftAction = { swipeActions.actions?.left?.performAction(it, this, EpisodeFilter())},
rightAction = { swipeActions.actions?.right?.performAction(it, this, EpisodeFilter())})
leftActionCB = { leftActionState.value?.performAction(it, this, swipeActions.filter ?: EpisodeFilter())},
rightActionCB = { rightActionState.value?.performAction(it, this, swipeActions.filter ?: EpisodeFilter())})
}
}
// recyclerView.setRecycledViewPool((activity as MainActivity).recycledViewPool)
@ -112,8 +115,7 @@ import java.util.*
// recyclerView.addOnScrollListener(LiftOnScrollListener(binding.appbar))
// swipeActions = SwipeActions(this, TAG).attachTo(recyclerView)
lifecycle.addObserver(swipeActions)
swipeActions.setFilter(EpisodeFilter(EpisodeFilter.States.downloaded.name))
// lifecycle.addObserver(swipeActions)
refreshSwipeTelltale()
// binding.leftActionIcon.setOnClickListener { swipeActions.showDialog() }
// binding.rightActionIcon.setOnClickListener { swipeActions.showDialog() }
@ -363,6 +365,8 @@ import java.util.*
}
private fun refreshSwipeTelltale() {
leftActionState.value = swipeActions.actions?.left
rightActionState.value = swipeActions.actions?.right
// if (swipeActions.actions?.left != null) binding.leftActionIcon.setImageResource(swipeActions.actions!!.left!!.getActionIcon())
// if (swipeActions.actions?.right != null) binding.rightActionIcon.setImageResource(swipeActions.actions!!.right!!.getActionIcon())
}