6.12.4 commit

This commit is contained in:
Xilin Jia 2024-10-25 20:18:48 +01:00
parent 4001693f10
commit 25893b79ea
5 changed files with 59 additions and 91 deletions

View File

@ -56,7 +56,6 @@ While podcast subscriptions' OPML files (from AntennaPod or any other sources) c
* single tap not during play has no effect
* Added preference "Fallback Speed" under "Playback" in settings with default value of 0.0, dialog allows setting a float number (capped between 0.0 and 1.5)
* if the user customizes "Fallback speed" to a value greater than 0.1, long-press the Play button during play enters the fallback mode and plays at the set fallback speed, single tap exits the fallback mode
* Various efficiency improvements
* streamed media somewhat equivalent to downloaded media
* enabled episode description on player detailed view
* enabled intro- and end- skipping
@ -65,7 +64,6 @@ While podcast subscriptions' OPML files (from AntennaPod or any other sources) c
* new video episode view, with video player on top and episode descriptions in portrait mode
* easy switches on video player to other video mode or audio only, in seamless way
* video player automatically switch to audio when app invisible
* default video player mode setting in preferences
* when video mode is set to audio only, click on image on audio player on a video episode brings up the normal player detailed view
* "Prefer streaming over download" is now on setting of individual feed
* added setting in individual feed to play audio only for video feeds,
@ -115,13 +113,11 @@ While podcast subscriptions' OPML files (from AntennaPod or any other sources) c
* Every feed (podcast) can be associated with a queue allowing downloaded media to be added to the queue
* FeedInfo view offers a link for direct search of feeds related to author
* FeedInfo view has button showing number of episodes to open the FeedEpisodes view
* FeedInfo view has feed setting in the header
* instead of isFavorite, there is a new rating system for every episode: Trash, Bad, Neutral, Good, Favorite
* instead of Played or Unplayed, there is a new play state system Unspecified, Building, New, Unplayed, Later, Soon, InQueue, InProgress, Skipped, Played, Ignored
* among which Unplayed, Later, Soon, Skipped, Played, Ignored are settable by the user
* when an episode is started to play, its state is set to InProgress
* when episode is added to a queue, its state is set to InQueue, when it's removed from a queue, the state (if lower than Skipped) is set to Skipped
* in EpisodeInfo view, "mark played/unplayed", "add to/remove from queue"
* in EpisodeInfo view, one can enter personal comments/notes under "My opinion" for the episode
* in FeedInfo view, one can enter personal comments/notes under "My opinion" for the feed
* New episode home view with two display modes: webpage or reader
@ -133,7 +129,7 @@ While podcast subscriptions' OPML files (from AntennaPod or any other sources) c
* Long-press on a feed in online feed list prompts to subscribe it straight out.
* More info about feeds are shown in the online search view
* Ability to open podcast from webpage address
* Ability to open podcast with webpage address
* Online feed info display is handled in similar ways as any local feed, and offers options to subscribe or view episodes
* Online feed episodes can be freely played (streamed) without a subscription
* Online feed episodes can be selectively reserved into synthetic podcasts

View File

@ -720,14 +720,6 @@ class OnlineFeedFragment : Fragment() {
updateToolbar()
return root
}
// override fun onStart() {
// super.onStart()
//// procFlowEvents()
// }
// override fun onStop() {
// super.onStop()
//// cancelFlowEvents()
// }
override fun onDestroyView() {
episodeList.clear()
super.onDestroyView()
@ -760,23 +752,6 @@ 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 -> {}
// }
// }
// }
// }
companion object {
const val PREF_NAME: String = "EpisodesListFragment"

View File

@ -233,7 +233,6 @@ class SearchFragment : Fragment() {
// results[pos].downloadState.value = event.map[url]?.state ?: DownloadStatus.State.UNKNOWN.ordinal
vms[pos].downloadState = event.map[url]?.state ?: DownloadStatus.State.UNKNOWN.ordinal
}
}
}
@ -291,6 +290,53 @@ class SearchFragment : Fragment() {
}
}
@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)
})
}
}
}
}
@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())
@ -393,53 +439,6 @@ class SearchFragment : Fragment() {
(activity as MainActivity).loadChildFragment(SearchResultsFragment.newInstance(CombinedSearcher::class.java, query))
}
@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)
})
}
}
}
}
companion object {
private val TAG: String = SearchFragment::class.simpleName ?: "Anonymous"
private const val ARG_QUERY = "query"

View File

@ -1,11 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<menu
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_search"
android:icon="@drawable/ic_search"
app:showAsAction="collapseActionView|always"
app:actionViewClass="androidx.appcompat.widget.SearchView"
android:title="@string/search_label"/>
android:id="@+id/action_search"
android:icon="@drawable/ic_search"
app:showAsAction="collapseActionView|always"
app:actionViewClass="androidx.appcompat.widget.SearchView"
android:title="@string/search_label"/>
</menu>

View File

@ -204,7 +204,6 @@ class CastPsmp(context: Context, callback: MediaPlayerCallback) : MediaPlayerBas
} while (nextPlayable != null && !CastUtils.isCastable(nextPlayable, castContext.sessionManager.currentCastSession))
if (nextPlayable != null) playMediaObject(nextPlayable, stream, startWhenPrepared, prepareImmediately, forceReset)
return
}
@ -224,11 +223,10 @@ class CastPsmp(context: Context, callback: MediaPlayerCallback) : MediaPlayerBas
callback.onPlaybackPause(curMedia, pos)
}
if (prevMedia != null && curMedia!!.getIdentifier() != prevMedia?.getIdentifier()) {
if (prevMedia != null && curMedia!!.getIdentifier() != prevMedia?.getIdentifier())
callback.onPostPlayback(prevMedia, false, skipped = false, playingNext = true)
}
prevMedia = curMedia
prevMedia = curMedia
setPlayerStatus(PlayerStatus.INDETERMINATE, null)
}
}
@ -275,7 +273,7 @@ class CastPsmp(context: Context, callback: MediaPlayerCallback) : MediaPlayerBas
override fun seekTo(t: Int) {
Exception("Seeking to $t").printStackTrace()
remoteMediaClient!!.seek(MediaSeekOptions.Builder().setPosition(t.toLong()).build())
remoteMediaClient!!.seek(MediaSeekOptions.Builder().setPosition(t.toLong()).setResumeState(MediaSeekOptions.RESUME_STATE_PLAY).build())
}
override fun getDuration(): Int {