-
-
Notifications
You must be signed in to change notification settings - Fork 3.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Migrate related items fragment to Jetpack Compose (#11383)
* Rename .java to .kt * Migrate related items fragment to Jetpack Compose * Specify mode parameter explicitly * Rm unused class * Fix list item size * Added stream progress bar, separate stream and playlist thumbnails * Display message if no related streams are available * Dispose of related items when closing the video player * Add modifiers for no items message function * Implement remaining stream menu items * Improved stream composables * Use view model lifecycle scope * Make live color solid red * Use nested scroll modifier * Simplify determineItemViewMode()
1 parent
9d04a73
commit 2836191
Showing
30 changed files
with
940 additions
and
395 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
176 changes: 0 additions & 176 deletions
176
app/src/main/java/org/schabi/newpipe/fragments/list/videos/RelatedItemsFragment.java
This file was deleted.
Oops, something went wrong.
43 changes: 43 additions & 0 deletions
43
app/src/main/java/org/schabi/newpipe/fragments/list/videos/RelatedItemsFragment.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
package org.schabi.newpipe.fragments.list.videos | ||
|
||
import android.os.Bundle | ||
import android.view.LayoutInflater | ||
import android.view.View | ||
import android.view.ViewGroup | ||
import androidx.compose.material3.MaterialTheme | ||
import androidx.compose.material3.Surface | ||
import androidx.compose.ui.platform.ComposeView | ||
import androidx.compose.ui.platform.ViewCompositionStrategy | ||
import androidx.core.os.bundleOf | ||
import androidx.fragment.app.Fragment | ||
import org.schabi.newpipe.extractor.stream.StreamInfo | ||
import org.schabi.newpipe.ktx.serializable | ||
import org.schabi.newpipe.ui.components.video.RelatedItems | ||
import org.schabi.newpipe.ui.theme.AppTheme | ||
import org.schabi.newpipe.util.KEY_INFO | ||
|
||
class RelatedItemsFragment : Fragment() { | ||
override fun onCreateView( | ||
inflater: LayoutInflater, | ||
container: ViewGroup?, | ||
savedInstanceState: Bundle? | ||
): View { | ||
return ComposeView(requireContext()).apply { | ||
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) | ||
setContent { | ||
AppTheme { | ||
Surface(color = MaterialTheme.colorScheme.background) { | ||
RelatedItems(requireArguments().serializable<StreamInfo>(KEY_INFO)!!) | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
companion object { | ||
@JvmStatic | ||
fun getInstance(info: StreamInfo) = RelatedItemsFragment().apply { | ||
arguments = bundleOf(KEY_INFO to info) | ||
} | ||
} | ||
} |
22 changes: 0 additions & 22 deletions
22
app/src/main/java/org/schabi/newpipe/fragments/list/videos/RelatedItemsInfo.java
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
42 changes: 42 additions & 0 deletions
42
app/src/main/java/org/schabi/newpipe/ui/components/common/NoItemsMessage.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
package org.schabi.newpipe.ui.components.common | ||
|
||
import android.content.res.Configuration | ||
import androidx.annotation.StringRes | ||
import androidx.compose.foundation.layout.Column | ||
import androidx.compose.foundation.layout.fillMaxWidth | ||
import androidx.compose.foundation.layout.wrapContentSize | ||
import androidx.compose.material3.MaterialTheme | ||
import androidx.compose.material3.Surface | ||
import androidx.compose.material3.Text | ||
import androidx.compose.runtime.Composable | ||
import androidx.compose.ui.Alignment | ||
import androidx.compose.ui.Modifier | ||
import androidx.compose.ui.res.stringResource | ||
import androidx.compose.ui.tooling.preview.Preview | ||
import androidx.compose.ui.unit.sp | ||
import org.schabi.newpipe.R | ||
import org.schabi.newpipe.ui.theme.AppTheme | ||
|
||
@Composable | ||
fun NoItemsMessage(@StringRes message: Int) { | ||
Column( | ||
modifier = Modifier | ||
.fillMaxWidth() | ||
.wrapContentSize(Alignment.Center), | ||
horizontalAlignment = Alignment.CenterHorizontally | ||
) { | ||
Text(text = "(╯°-°)╯", fontSize = 35.sp) | ||
Text(text = stringResource(id = message), fontSize = 24.sp) | ||
} | ||
} | ||
|
||
@Preview(name = "Light mode", uiMode = Configuration.UI_MODE_NIGHT_NO) | ||
@Preview(name = "Dark mode", uiMode = Configuration.UI_MODE_NIGHT_YES) | ||
@Composable | ||
private fun NoItemsMessagePreview() { | ||
AppTheme { | ||
Surface(color = MaterialTheme.colorScheme.background) { | ||
NoItemsMessage(message = R.string.no_videos) | ||
} | ||
} | ||
} |
114 changes: 114 additions & 0 deletions
114
app/src/main/java/org/schabi/newpipe/ui/components/items/ItemList.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
package org.schabi.newpipe.ui.components.items | ||
|
||
import androidx.compose.foundation.lazy.LazyColumn | ||
import androidx.compose.foundation.lazy.LazyListScope | ||
import androidx.compose.foundation.lazy.rememberLazyListState | ||
import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo | ||
import androidx.compose.runtime.Composable | ||
import androidx.compose.runtime.getValue | ||
import androidx.compose.runtime.mutableStateOf | ||
import androidx.compose.runtime.remember | ||
import androidx.compose.runtime.setValue | ||
import androidx.compose.ui.Modifier | ||
import androidx.compose.ui.input.nestedscroll.nestedScroll | ||
import androidx.compose.ui.platform.LocalContext | ||
import androidx.compose.ui.platform.rememberNestedScrollInteropConnection | ||
import androidx.compose.ui.res.stringResource | ||
import androidx.fragment.app.FragmentActivity | ||
import androidx.preference.PreferenceManager | ||
import androidx.window.core.layout.WindowWidthSizeClass | ||
import my.nanihadesuka.compose.LazyColumnScrollbar | ||
import org.schabi.newpipe.R | ||
import org.schabi.newpipe.extractor.InfoItem | ||
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem | ||
import org.schabi.newpipe.extractor.stream.StreamInfoItem | ||
import org.schabi.newpipe.info_list.ItemViewMode | ||
import org.schabi.newpipe.ui.components.items.playlist.PlaylistListItem | ||
import org.schabi.newpipe.ui.components.items.stream.StreamListItem | ||
import org.schabi.newpipe.util.DependentPreferenceHelper | ||
import org.schabi.newpipe.util.NavigationHelper | ||
|
||
@Composable | ||
fun ItemList( | ||
items: List<InfoItem>, | ||
mode: ItemViewMode = determineItemViewMode(), | ||
listHeader: LazyListScope.() -> Unit = {} | ||
) { | ||
val context = LocalContext.current | ||
val onClick = remember { | ||
{ item: InfoItem -> | ||
val fragmentManager = (context as FragmentActivity).supportFragmentManager | ||
if (item is StreamInfoItem) { | ||
NavigationHelper.openVideoDetailFragment( | ||
context, fragmentManager, item.serviceId, item.url, item.name, null, false | ||
) | ||
} else if (item is PlaylistInfoItem) { | ||
NavigationHelper.openPlaylistFragment( | ||
fragmentManager, item.serviceId, item.url, item.name | ||
) | ||
} | ||
} | ||
} | ||
|
||
// Handle long clicks for stream items | ||
// TODO: Adjust the menu display depending on where it was triggered | ||
var selectedStream by remember { mutableStateOf<StreamInfoItem?>(null) } | ||
val onLongClick = remember { | ||
{ stream: StreamInfoItem -> | ||
selectedStream = stream | ||
} | ||
} | ||
val onDismissPopup = remember { | ||
{ | ||
selectedStream = null | ||
} | ||
} | ||
|
||
val showProgress = DependentPreferenceHelper.getPositionsInListsEnabled(context) | ||
val nestedScrollModifier = Modifier.nestedScroll(rememberNestedScrollInteropConnection()) | ||
|
||
if (mode == ItemViewMode.GRID) { | ||
// TODO: Implement grid layout using LazyVerticalGrid and LazyVerticalGridScrollbar. | ||
} else { | ||
val state = rememberLazyListState() | ||
|
||
LazyColumnScrollbar(state = state) { | ||
LazyColumn(modifier = nestedScrollModifier, state = state) { | ||
listHeader() | ||
|
||
items(items.size) { | ||
val item = items[it] | ||
|
||
if (item is StreamInfoItem) { | ||
val isSelected = selectedStream == item | ||
StreamListItem( | ||
item, showProgress, isSelected, onClick, onLongClick, onDismissPopup | ||
) | ||
} else if (item is PlaylistInfoItem) { | ||
PlaylistListItem(item, onClick) | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
@Composable | ||
private fun determineItemViewMode(): ItemViewMode { | ||
val prefValue = PreferenceManager.getDefaultSharedPreferences(LocalContext.current) | ||
.getString(stringResource(R.string.list_view_mode_key), null) | ||
val viewMode = prefValue?.let { ItemViewMode.valueOf(it.uppercase()) } ?: ItemViewMode.AUTO | ||
|
||
return when (viewMode) { | ||
ItemViewMode.AUTO -> { | ||
// Evaluate whether to use Grid based on screen real estate. | ||
val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass | ||
if (windowSizeClass.windowWidthSizeClass == WindowWidthSizeClass.EXPANDED) { | ||
ItemViewMode.GRID | ||
} else { | ||
ItemViewMode.LIST | ||
} | ||
} | ||
else -> viewMode | ||
} | ||
} |
71 changes: 71 additions & 0 deletions
71
app/src/main/java/org/schabi/newpipe/ui/components/items/playlist/PlaylistListItem.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
package org.schabi.newpipe.ui.components.items.playlist | ||
|
||
import android.content.res.Configuration | ||
import androidx.compose.foundation.clickable | ||
import androidx.compose.foundation.layout.Arrangement | ||
import androidx.compose.foundation.layout.Column | ||
import androidx.compose.foundation.layout.Row | ||
import androidx.compose.foundation.layout.fillMaxWidth | ||
import androidx.compose.foundation.layout.padding | ||
import androidx.compose.foundation.layout.size | ||
import androidx.compose.material3.MaterialTheme | ||
import androidx.compose.material3.Surface | ||
import androidx.compose.material3.Text | ||
import androidx.compose.runtime.Composable | ||
import androidx.compose.ui.Alignment | ||
import androidx.compose.ui.Modifier | ||
import androidx.compose.ui.text.style.TextOverflow | ||
import androidx.compose.ui.tooling.preview.Preview | ||
import androidx.compose.ui.unit.dp | ||
import org.schabi.newpipe.extractor.InfoItem | ||
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem | ||
import org.schabi.newpipe.ui.theme.AppTheme | ||
import org.schabi.newpipe.util.NO_SERVICE_ID | ||
|
||
@Composable | ||
fun PlaylistListItem( | ||
playlist: PlaylistInfoItem, | ||
onClick: (InfoItem) -> Unit = {}, | ||
) { | ||
Row( | ||
modifier = Modifier | ||
.clickable { onClick(playlist) } | ||
.fillMaxWidth() | ||
.padding(12.dp), | ||
horizontalArrangement = Arrangement.spacedBy(4.dp), | ||
verticalAlignment = Alignment.CenterVertically | ||
) { | ||
PlaylistThumbnail( | ||
playlist = playlist, | ||
modifier = Modifier.size(width = 140.dp, height = 78.dp) | ||
) | ||
|
||
Column { | ||
Text( | ||
text = playlist.name, | ||
overflow = TextOverflow.Ellipsis, | ||
style = MaterialTheme.typography.titleSmall, | ||
maxLines = 2 | ||
) | ||
|
||
Text( | ||
text = playlist.uploaderName.orEmpty(), | ||
style = MaterialTheme.typography.bodySmall | ||
) | ||
} | ||
} | ||
} | ||
|
||
@Preview(name = "Light mode", uiMode = Configuration.UI_MODE_NIGHT_NO) | ||
@Preview(name = "Dark mode", uiMode = Configuration.UI_MODE_NIGHT_YES) | ||
@Composable | ||
private fun PlaylistListItemPreview() { | ||
val playlist = PlaylistInfoItem(NO_SERVICE_ID, "", "Playlist") | ||
playlist.uploaderName = "Uploader" | ||
|
||
AppTheme { | ||
Surface(color = MaterialTheme.colorScheme.background) { | ||
PlaylistListItem(playlist) | ||
} | ||
} | ||
} |
65 changes: 65 additions & 0 deletions
65
app/src/main/java/org/schabi/newpipe/ui/components/items/playlist/PlaylistThumbnail.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
package org.schabi.newpipe.ui.components.items.playlist | ||
|
||
import androidx.compose.foundation.Image | ||
import androidx.compose.foundation.background | ||
import androidx.compose.foundation.layout.Box | ||
import androidx.compose.foundation.layout.Row | ||
import androidx.compose.foundation.layout.padding | ||
import androidx.compose.foundation.layout.size | ||
import androidx.compose.material3.MaterialTheme | ||
import androidx.compose.material3.Text | ||
import androidx.compose.runtime.Composable | ||
import androidx.compose.ui.Alignment | ||
import androidx.compose.ui.Modifier | ||
import androidx.compose.ui.graphics.Color | ||
import androidx.compose.ui.graphics.ColorFilter | ||
import androidx.compose.ui.layout.ContentScale | ||
import androidx.compose.ui.platform.LocalContext | ||
import androidx.compose.ui.res.painterResource | ||
import androidx.compose.ui.unit.dp | ||
import coil.compose.AsyncImage | ||
import org.schabi.newpipe.R | ||
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem | ||
import org.schabi.newpipe.util.Localization | ||
import org.schabi.newpipe.util.image.ImageStrategy | ||
|
||
@Composable | ||
fun PlaylistThumbnail( | ||
playlist: PlaylistInfoItem, | ||
modifier: Modifier = Modifier, | ||
contentScale: ContentScale = ContentScale.Fit | ||
) { | ||
Box(contentAlignment = Alignment.BottomEnd) { | ||
AsyncImage( | ||
model = ImageStrategy.choosePreferredImage(playlist.thumbnails), | ||
contentDescription = null, | ||
placeholder = painterResource(R.drawable.placeholder_thumbnail_playlist), | ||
error = painterResource(R.drawable.placeholder_thumbnail_playlist), | ||
contentScale = contentScale, | ||
modifier = modifier | ||
) | ||
|
||
Row( | ||
modifier = Modifier | ||
.padding(2.dp) | ||
.background(Color.Black.copy(alpha = 0.5f)) | ||
.padding(2.dp), | ||
verticalAlignment = Alignment.CenterVertically | ||
) { | ||
Image( | ||
painter = painterResource(R.drawable.ic_playlist_play), | ||
contentDescription = null, | ||
colorFilter = ColorFilter.tint(Color.White), | ||
modifier = Modifier.size(18.dp) | ||
) | ||
|
||
val context = LocalContext.current | ||
Text( | ||
text = Localization.localizeStreamCountMini(context, playlist.streamCount), | ||
color = Color.White, | ||
style = MaterialTheme.typography.bodySmall, | ||
modifier = Modifier.padding(start = 4.dp) | ||
) | ||
} | ||
} | ||
} |
85 changes: 85 additions & 0 deletions
85
app/src/main/java/org/schabi/newpipe/ui/components/items/stream/StreamListItem.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
package org.schabi.newpipe.ui.components.items.stream | ||
|
||
import android.content.res.Configuration | ||
import androidx.compose.foundation.ExperimentalFoundationApi | ||
import androidx.compose.foundation.combinedClickable | ||
import androidx.compose.foundation.layout.Arrangement | ||
import androidx.compose.foundation.layout.Box | ||
import androidx.compose.foundation.layout.Column | ||
import androidx.compose.foundation.layout.Row | ||
import androidx.compose.foundation.layout.fillMaxWidth | ||
import androidx.compose.foundation.layout.padding | ||
import androidx.compose.foundation.layout.size | ||
import androidx.compose.material3.MaterialTheme | ||
import androidx.compose.material3.Surface | ||
import androidx.compose.material3.Text | ||
import androidx.compose.runtime.Composable | ||
import androidx.compose.ui.Alignment | ||
import androidx.compose.ui.Modifier | ||
import androidx.compose.ui.text.style.TextOverflow | ||
import androidx.compose.ui.tooling.preview.Preview | ||
import androidx.compose.ui.tooling.preview.PreviewParameter | ||
import androidx.compose.ui.unit.dp | ||
import org.schabi.newpipe.extractor.stream.StreamInfoItem | ||
import org.schabi.newpipe.ui.theme.AppTheme | ||
|
||
@OptIn(ExperimentalFoundationApi::class) | ||
@Composable | ||
fun StreamListItem( | ||
stream: StreamInfoItem, | ||
showProgress: Boolean, | ||
isSelected: Boolean, | ||
onClick: (StreamInfoItem) -> Unit = {}, | ||
onLongClick: (StreamInfoItem) -> Unit = {}, | ||
onDismissPopup: () -> Unit = {} | ||
) { | ||
// Box serves as an anchor for the dropdown menu | ||
Box( | ||
modifier = Modifier | ||
.combinedClickable(onLongClick = { onLongClick(stream) }, onClick = { onClick(stream) }) | ||
.fillMaxWidth() | ||
.padding(12.dp) | ||
) { | ||
Row( | ||
horizontalArrangement = Arrangement.spacedBy(4.dp), | ||
verticalAlignment = Alignment.CenterVertically | ||
) { | ||
StreamThumbnail( | ||
stream = stream, | ||
showProgress = showProgress, | ||
modifier = Modifier.size(width = 140.dp, height = 78.dp) | ||
) | ||
|
||
Column { | ||
Text( | ||
text = stream.name, | ||
overflow = TextOverflow.Ellipsis, | ||
style = MaterialTheme.typography.titleSmall, | ||
maxLines = 2 | ||
) | ||
|
||
Text(text = stream.uploaderName.orEmpty(), style = MaterialTheme.typography.bodySmall) | ||
|
||
Text( | ||
text = getStreamInfoDetail(stream), | ||
style = MaterialTheme.typography.bodySmall | ||
) | ||
} | ||
} | ||
|
||
StreamMenu(stream, isSelected, onDismissPopup) | ||
} | ||
} | ||
|
||
@Preview(name = "Light mode", uiMode = Configuration.UI_MODE_NIGHT_NO) | ||
@Preview(name = "Dark mode", uiMode = Configuration.UI_MODE_NIGHT_YES) | ||
@Composable | ||
private fun StreamListItemPreview( | ||
@PreviewParameter(StreamItemPreviewProvider::class) stream: StreamInfoItem | ||
) { | ||
AppTheme { | ||
Surface(color = MaterialTheme.colorScheme.background) { | ||
StreamListItem(stream, showProgress = false, isSelected = false) | ||
} | ||
} | ||
} |
152 changes: 152 additions & 0 deletions
152
app/src/main/java/org/schabi/newpipe/ui/components/items/stream/StreamMenu.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
package org.schabi.newpipe.ui.components.items.stream | ||
|
||
import androidx.annotation.StringRes | ||
import androidx.compose.material3.DropdownMenu | ||
import androidx.compose.material3.DropdownMenuItem | ||
import androidx.compose.material3.MaterialTheme | ||
import androidx.compose.material3.Text | ||
import androidx.compose.runtime.Composable | ||
import androidx.compose.ui.platform.LocalContext | ||
import androidx.compose.ui.res.stringResource | ||
import androidx.fragment.app.FragmentActivity | ||
import androidx.lifecycle.viewmodel.compose.viewModel | ||
import org.schabi.newpipe.R | ||
import org.schabi.newpipe.database.stream.model.StreamEntity | ||
import org.schabi.newpipe.download.DownloadDialog | ||
import org.schabi.newpipe.extractor.stream.StreamInfoItem | ||
import org.schabi.newpipe.local.dialog.PlaylistAppendDialog | ||
import org.schabi.newpipe.local.dialog.PlaylistDialog | ||
import org.schabi.newpipe.player.helper.PlayerHolder | ||
import org.schabi.newpipe.util.NavigationHelper | ||
import org.schabi.newpipe.util.SparseItemUtil | ||
import org.schabi.newpipe.util.external_communication.ShareUtils | ||
import org.schabi.newpipe.viewmodels.StreamViewModel | ||
|
||
@Composable | ||
fun StreamMenu( | ||
stream: StreamInfoItem, | ||
expanded: Boolean, | ||
onDismissRequest: () -> Unit | ||
) { | ||
val context = LocalContext.current | ||
val streamViewModel = viewModel<StreamViewModel>() | ||
val playerHolder = PlayerHolder.getInstance() | ||
|
||
DropdownMenu(expanded = expanded, onDismissRequest = onDismissRequest) { | ||
if (playerHolder.isPlayQueueReady) { | ||
StreamMenuItem( | ||
text = R.string.enqueue_stream, | ||
onClick = { | ||
onDismissRequest() | ||
SparseItemUtil.fetchItemInfoIfSparse(context, stream) { | ||
NavigationHelper.enqueueOnPlayer(context, it) | ||
} | ||
} | ||
) | ||
|
||
if (playerHolder.queuePosition < playerHolder.queueSize - 1) { | ||
StreamMenuItem( | ||
text = R.string.enqueue_next_stream, | ||
onClick = { | ||
onDismissRequest() | ||
SparseItemUtil.fetchItemInfoIfSparse(context, stream) { | ||
NavigationHelper.enqueueNextOnPlayer(context, it) | ||
} | ||
} | ||
) | ||
} | ||
} | ||
|
||
StreamMenuItem( | ||
text = R.string.start_here_on_background, | ||
onClick = { | ||
onDismissRequest() | ||
SparseItemUtil.fetchItemInfoIfSparse(context, stream) { | ||
NavigationHelper.playOnBackgroundPlayer(context, it, true) | ||
} | ||
} | ||
) | ||
StreamMenuItem( | ||
text = R.string.start_here_on_popup, | ||
onClick = { | ||
onDismissRequest() | ||
SparseItemUtil.fetchItemInfoIfSparse(context, stream) { | ||
NavigationHelper.playOnPopupPlayer(context, it, true) | ||
} | ||
} | ||
) | ||
StreamMenuItem( | ||
text = R.string.download, | ||
onClick = { | ||
onDismissRequest() | ||
SparseItemUtil.fetchStreamInfoAndSaveToDatabase( | ||
context, stream.serviceId, stream.url | ||
) { info -> | ||
// TODO: Use an AlertDialog composable instead. | ||
val downloadDialog = DownloadDialog(context, info) | ||
val fragmentManager = (context as FragmentActivity).supportFragmentManager | ||
downloadDialog.show(fragmentManager, "downloadDialog") | ||
} | ||
} | ||
) | ||
StreamMenuItem( | ||
text = R.string.add_to_playlist, | ||
onClick = { | ||
onDismissRequest() | ||
val list = listOf(StreamEntity(stream)) | ||
PlaylistDialog.createCorrespondingDialog(context, list) { dialog -> | ||
val tag = if (dialog is PlaylistAppendDialog) "append" else "create" | ||
dialog.show( | ||
(context as FragmentActivity).supportFragmentManager, | ||
"StreamDialogEntry@${tag}_playlist" | ||
) | ||
} | ||
} | ||
) | ||
StreamMenuItem( | ||
text = R.string.share, | ||
onClick = { | ||
onDismissRequest() | ||
ShareUtils.shareText(context, stream.name, stream.url, stream.thumbnails) | ||
} | ||
) | ||
StreamMenuItem( | ||
text = R.string.open_in_browser, | ||
onClick = { | ||
onDismissRequest() | ||
ShareUtils.openUrlInBrowser(context, stream.url) | ||
} | ||
) | ||
StreamMenuItem( | ||
text = R.string.mark_as_watched, | ||
onClick = { | ||
onDismissRequest() | ||
streamViewModel.markAsWatched(stream) | ||
} | ||
) | ||
StreamMenuItem( | ||
text = R.string.show_channel_details, | ||
onClick = { | ||
onDismissRequest() | ||
SparseItemUtil.fetchUploaderUrlIfSparse( | ||
context, stream.serviceId, stream.url, stream.uploaderUrl | ||
) { url -> | ||
NavigationHelper.openChannelFragment(context as FragmentActivity, stream, url) | ||
} | ||
} | ||
) | ||
} | ||
} | ||
|
||
@Composable | ||
private fun StreamMenuItem( | ||
@StringRes text: Int, | ||
onClick: () -> Unit | ||
) { | ||
DropdownMenuItem( | ||
text = { | ||
Text(text = stringResource(text), color = MaterialTheme.colorScheme.onBackground) | ||
}, | ||
onClick = onClick | ||
) | ||
} |
89 changes: 89 additions & 0 deletions
89
app/src/main/java/org/schabi/newpipe/ui/components/items/stream/StreamThumbnail.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
package org.schabi.newpipe.ui.components.items.stream | ||
|
||
import androidx.compose.foundation.background | ||
import androidx.compose.foundation.layout.Box | ||
import androidx.compose.foundation.layout.Column | ||
import androidx.compose.foundation.layout.padding | ||
import androidx.compose.foundation.layout.requiredHeight | ||
import androidx.compose.material3.LinearProgressIndicator | ||
import androidx.compose.material3.MaterialTheme | ||
import androidx.compose.material3.Text | ||
import androidx.compose.runtime.Composable | ||
import androidx.compose.runtime.LaunchedEffect | ||
import androidx.compose.runtime.getValue | ||
import androidx.compose.runtime.mutableLongStateOf | ||
import androidx.compose.runtime.saveable.rememberSaveable | ||
import androidx.compose.runtime.setValue | ||
import androidx.compose.ui.Alignment | ||
import androidx.compose.ui.Modifier | ||
import androidx.compose.ui.graphics.Color | ||
import androidx.compose.ui.layout.ContentScale | ||
import androidx.compose.ui.res.painterResource | ||
import androidx.compose.ui.res.stringResource | ||
import androidx.compose.ui.unit.dp | ||
import androidx.lifecycle.viewmodel.compose.viewModel | ||
import coil.compose.AsyncImage | ||
import org.schabi.newpipe.R | ||
import org.schabi.newpipe.extractor.stream.StreamInfoItem | ||
import org.schabi.newpipe.util.Localization | ||
import org.schabi.newpipe.util.StreamTypeUtil | ||
import org.schabi.newpipe.util.image.ImageStrategy | ||
import org.schabi.newpipe.viewmodels.StreamViewModel | ||
import kotlin.time.Duration.Companion.milliseconds | ||
import kotlin.time.Duration.Companion.seconds | ||
|
||
@Composable | ||
fun StreamThumbnail( | ||
stream: StreamInfoItem, | ||
showProgress: Boolean, | ||
modifier: Modifier = Modifier, | ||
contentScale: ContentScale = ContentScale.Fit | ||
) { | ||
Column(modifier = modifier) { | ||
Box(contentAlignment = Alignment.BottomEnd) { | ||
AsyncImage( | ||
model = ImageStrategy.choosePreferredImage(stream.thumbnails), | ||
contentDescription = null, | ||
placeholder = painterResource(R.drawable.placeholder_thumbnail_video), | ||
error = painterResource(R.drawable.placeholder_thumbnail_video), | ||
contentScale = contentScale, | ||
modifier = modifier | ||
) | ||
|
||
val isLive = StreamTypeUtil.isLiveStream(stream.streamType) | ||
Text( | ||
modifier = Modifier | ||
.padding(2.dp) | ||
.background(if (isLive) Color.Red else Color.Black.copy(alpha = 0.5f)) | ||
.padding(2.dp), | ||
text = if (isLive) { | ||
stringResource(R.string.duration_live) | ||
} else { | ||
Localization.getDurationString(stream.duration) | ||
}, | ||
color = Color.White, | ||
style = MaterialTheme.typography.bodySmall | ||
) | ||
} | ||
|
||
if (showProgress) { | ||
val streamViewModel = viewModel<StreamViewModel>() | ||
var progress by rememberSaveable { mutableLongStateOf(0L) } | ||
|
||
LaunchedEffect(stream) { | ||
progress = streamViewModel.getStreamState(stream)?.progressMillis ?: 0L | ||
} | ||
|
||
if (progress != 0L) { | ||
LinearProgressIndicator( | ||
modifier = Modifier.requiredHeight(2.dp), | ||
progress = { | ||
(progress.milliseconds / stream.duration.seconds).toFloat() | ||
}, | ||
gapSize = 0.dp, | ||
drawStopIndicator = {} // Hide stop indicator | ||
) | ||
} | ||
} | ||
} | ||
} |
68 changes: 68 additions & 0 deletions
68
app/src/main/java/org/schabi/newpipe/ui/components/items/stream/StreamUtils.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
package org.schabi.newpipe.ui.components.items.stream | ||
|
||
import androidx.compose.runtime.Composable | ||
import androidx.compose.runtime.saveable.rememberSaveable | ||
import androidx.compose.ui.platform.LocalContext | ||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider | ||
import org.schabi.newpipe.extractor.Image | ||
import org.schabi.newpipe.extractor.stream.StreamInfoItem | ||
import org.schabi.newpipe.extractor.stream.StreamType | ||
import org.schabi.newpipe.util.Localization | ||
import org.schabi.newpipe.util.NO_SERVICE_ID | ||
import java.util.concurrent.TimeUnit | ||
|
||
fun StreamInfoItem( | ||
serviceId: Int = NO_SERVICE_ID, | ||
url: String = "", | ||
name: String = "Stream", | ||
streamType: StreamType, | ||
uploaderName: String? = "Uploader", | ||
uploaderUrl: String? = null, | ||
uploaderAvatars: List<Image> = emptyList(), | ||
duration: Long = TimeUnit.HOURS.toSeconds(1), | ||
viewCount: Long = 10, | ||
textualUploadDate: String = "1 month ago" | ||
) = StreamInfoItem(serviceId, url, name, streamType).apply { | ||
this.uploaderName = uploaderName | ||
this.uploaderUrl = uploaderUrl | ||
this.uploaderAvatars = uploaderAvatars | ||
this.duration = duration | ||
this.viewCount = viewCount | ||
this.textualUploadDate = textualUploadDate | ||
} | ||
|
||
@Composable | ||
internal fun getStreamInfoDetail(stream: StreamInfoItem): String { | ||
val context = LocalContext.current | ||
|
||
return rememberSaveable(stream) { | ||
val count = stream.viewCount | ||
val views = if (count >= 0) { | ||
when (stream.streamType) { | ||
StreamType.AUDIO_LIVE_STREAM -> Localization.listeningCount(context, count) | ||
StreamType.LIVE_STREAM -> Localization.shortWatchingCount(context, count) | ||
else -> Localization.shortViewCount(context, count) | ||
} | ||
} else { | ||
"" | ||
} | ||
val date = | ||
Localization.relativeTimeOrTextual(context, stream.uploadDate, stream.textualUploadDate) | ||
|
||
if (views.isEmpty()) { | ||
date.orEmpty() | ||
} else if (date.isNullOrEmpty()) { | ||
views | ||
} else { | ||
"$views • $date" | ||
} | ||
} | ||
} | ||
|
||
internal class StreamItemPreviewProvider : PreviewParameterProvider<StreamInfoItem> { | ||
override val values = sequenceOf( | ||
StreamInfoItem(streamType = StreamType.NONE), | ||
StreamInfoItem(streamType = StreamType.LIVE_STREAM), | ||
StreamInfoItem(streamType = StreamType.AUDIO_LIVE_STREAM), | ||
) | ||
} |
99 changes: 99 additions & 0 deletions
99
app/src/main/java/org/schabi/newpipe/ui/components/video/RelatedItems.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
package org.schabi.newpipe.ui.components.video | ||
|
||
import android.content.res.Configuration | ||
import androidx.compose.foundation.layout.Arrangement | ||
import androidx.compose.foundation.layout.Row | ||
import androidx.compose.foundation.layout.fillMaxWidth | ||
import androidx.compose.foundation.layout.padding | ||
import androidx.compose.material3.MaterialTheme | ||
import androidx.compose.material3.Surface | ||
import androidx.compose.material3.Switch | ||
import androidx.compose.material3.Text | ||
import androidx.compose.runtime.Composable | ||
import androidx.compose.runtime.getValue | ||
import androidx.compose.runtime.mutableStateOf | ||
import androidx.compose.runtime.saveable.rememberSaveable | ||
import androidx.compose.runtime.setValue | ||
import androidx.compose.ui.Alignment | ||
import androidx.compose.ui.Modifier | ||
import androidx.compose.ui.platform.LocalContext | ||
import androidx.compose.ui.res.stringResource | ||
import androidx.compose.ui.tooling.preview.Preview | ||
import androidx.compose.ui.unit.dp | ||
import androidx.core.content.edit | ||
import androidx.preference.PreferenceManager | ||
import org.schabi.newpipe.R | ||
import org.schabi.newpipe.extractor.stream.StreamInfo | ||
import org.schabi.newpipe.extractor.stream.StreamType | ||
import org.schabi.newpipe.info_list.ItemViewMode | ||
import org.schabi.newpipe.ui.components.common.NoItemsMessage | ||
import org.schabi.newpipe.ui.components.items.ItemList | ||
import org.schabi.newpipe.ui.components.items.stream.StreamInfoItem | ||
import org.schabi.newpipe.ui.theme.AppTheme | ||
import org.schabi.newpipe.util.NO_SERVICE_ID | ||
|
||
@Composable | ||
fun RelatedItems(info: StreamInfo) { | ||
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(LocalContext.current) | ||
val key = stringResource(R.string.auto_queue_key) | ||
// TODO: AndroidX DataStore might be a better option. | ||
var isAutoQueueEnabled by rememberSaveable { | ||
mutableStateOf(sharedPreferences.getBoolean(key, false)) | ||
} | ||
|
||
if (info.relatedItems.isEmpty()) { | ||
NoItemsMessage(message = R.string.no_videos) | ||
} else { | ||
ItemList( | ||
items = info.relatedItems, | ||
mode = ItemViewMode.LIST, | ||
listHeader = { | ||
item { | ||
Row( | ||
modifier = Modifier | ||
.fillMaxWidth() | ||
.padding(start = 12.dp, end = 12.dp), | ||
horizontalArrangement = Arrangement.SpaceBetween, | ||
verticalAlignment = Alignment.CenterVertically, | ||
) { | ||
Text(text = stringResource(R.string.auto_queue_description)) | ||
|
||
Row( | ||
horizontalArrangement = Arrangement.spacedBy(4.dp), | ||
verticalAlignment = Alignment.CenterVertically | ||
) { | ||
Text(text = stringResource(R.string.auto_queue_toggle)) | ||
Switch( | ||
checked = isAutoQueueEnabled, | ||
onCheckedChange = { | ||
isAutoQueueEnabled = it | ||
sharedPreferences.edit { | ||
putBoolean(key, it) | ||
} | ||
} | ||
) | ||
} | ||
} | ||
} | ||
} | ||
) | ||
} | ||
} | ||
|
||
@Preview(name = "Light mode", uiMode = Configuration.UI_MODE_NIGHT_NO) | ||
@Preview(name = "Dark mode", uiMode = Configuration.UI_MODE_NIGHT_YES) | ||
@Composable | ||
private fun RelatedItemsPreview() { | ||
val info = StreamInfo(NO_SERVICE_ID, "", "", StreamType.VIDEO_STREAM, "", "", 0) | ||
info.relatedItems = listOf( | ||
StreamInfoItem(streamType = StreamType.NONE), | ||
StreamInfoItem(streamType = StreamType.LIVE_STREAM), | ||
StreamInfoItem(streamType = StreamType.AUDIO_LIVE_STREAM), | ||
) | ||
|
||
AppTheme { | ||
Surface(color = MaterialTheme.colorScheme.background) { | ||
RelatedItems(info) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
26 changes: 26 additions & 0 deletions
26
app/src/main/java/org/schabi/newpipe/viewmodels/StreamViewModel.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package org.schabi.newpipe.viewmodels | ||
|
||
import android.app.Application | ||
import androidx.lifecycle.AndroidViewModel | ||
import androidx.lifecycle.viewModelScope | ||
import kotlinx.coroutines.launch | ||
import kotlinx.coroutines.rx3.await | ||
import kotlinx.coroutines.rx3.awaitSingleOrNull | ||
import org.schabi.newpipe.database.stream.model.StreamStateEntity | ||
import org.schabi.newpipe.extractor.InfoItem | ||
import org.schabi.newpipe.extractor.stream.StreamInfoItem | ||
import org.schabi.newpipe.local.history.HistoryRecordManager | ||
|
||
class StreamViewModel(application: Application) : AndroidViewModel(application) { | ||
private val historyRecordManager = HistoryRecordManager(application) | ||
|
||
suspend fun getStreamState(infoItem: InfoItem): StreamStateEntity? { | ||
return historyRecordManager.loadStreamState(infoItem).awaitSingleOrNull() | ||
} | ||
|
||
fun markAsWatched(stream: StreamInfoItem) { | ||
viewModelScope.launch { | ||
historyRecordManager.markAsWatched(stream).await() | ||
} | ||
} | ||
} |
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters