From 6ad16fc42ef9115b176ce94c7319810667fa518f Mon Sep 17 00:00:00 2001 From: Stypox Date: Tue, 28 Jan 2025 12:36:32 +0100 Subject: [PATCH] Calculate button placing in long press menu --- .../ui/components/menu/LongPressMenu.kt | 130 +++++++++++++----- 1 file changed, 96 insertions(+), 34 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/ui/components/menu/LongPressMenu.kt b/app/src/main/java/org/schabi/newpipe/ui/components/menu/LongPressMenu.kt index 44731b120..30e499a73 100644 --- a/app/src/main/java/org/schabi/newpipe/ui/components/menu/LongPressMenu.kt +++ b/app/src/main/java/org/schabi/newpipe/ui/components/menu/LongPressMenu.kt @@ -1,17 +1,20 @@ -@file:OptIn(ExperimentalMaterial3Api::class) +@file:OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class) package org.schabi.newpipe.ui.components.menu import androidx.compose.foundation.basicMarquee import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ExperimentalLayoutApi +import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.widthIn import androidx.compose.material.icons.Icons @@ -36,7 +39,9 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider import androidx.compose.ui.tooling.preview.datasource.LoremIpsum +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.times import coil3.compose.AsyncImage import org.schabi.newpipe.R import org.schabi.newpipe.player.playqueue.PlayQueue @@ -55,20 +60,53 @@ fun LongPressMenu( onDismissRequest, sheetState = sheetState, ) { - Column { - LongPressMenuHeader( - item = longPressable, - modifier = Modifier - .padding(horizontal = 12.dp) - .fillMaxWidth() - ) - Spacer(Modifier.height(100.dp)) + BoxWithConstraints( + modifier = Modifier.fillMaxWidth() + .padding(bottom = 16.dp) + ) { + val maxContainerWidth = maxWidth + val minButtonWidth = 60.dp + val buttonHeight = 70.dp + val padding = 12.dp + val boxCount = ((maxContainerWidth - padding) / (minButtonWidth + padding)).toInt() + val buttonWidth = (maxContainerWidth - (boxCount + 1) * padding) / boxCount + val desiredHeaderWidth = buttonWidth * 5 + padding * 4 + + FlowRow( + horizontalArrangement = Arrangement.spacedBy(padding), + verticalArrangement = Arrangement.spacedBy(padding), + // left and right padding are implicit in the .align(Center), this way approximation + // errors in the calculations above don't make the items wrap at the wrong position + modifier = Modifier.align(Alignment.Center), + ) { + LongPressMenuHeader( + item = longPressable, + thumbnailHeight = buttonHeight, + // subtract 2.dp to account for approximation errors in the calculations above + modifier = if (desiredHeaderWidth >= maxContainerWidth - 2 * padding - 2.dp) { + // leave the height as small as possible, since it's the only item on the + // row anyway + Modifier.width(maxContainerWidth - 2 * padding) + } else { + // make sure it has the same height as other buttons + Modifier.size(desiredHeaderWidth, buttonHeight) + } + ) + + for (i in 0..10) { + LongPressMenuButton(modifier = Modifier.size(buttonWidth, buttonHeight)) + } + } } } } @Composable -fun LongPressMenuHeader(item: LongPressable, modifier: Modifier = Modifier) { +fun LongPressMenuHeader( + item: LongPressable, + thumbnailHeight: Dp, + modifier: Modifier = Modifier, +) { val ctx = LocalContext.current Surface( @@ -77,10 +115,8 @@ fun LongPressMenuHeader(item: LongPressable, modifier: Modifier = Modifier) { shape = MaterialTheme.shapes.large, modifier = modifier, ) { - Row { - Box( - modifier = Modifier.height(70.dp) - ) { + Row(verticalAlignment = Alignment.CenterVertically) { + Box { if (item.thumbnailUrl != null) { AsyncImage( model = item.thumbnailUrl, @@ -88,7 +124,7 @@ fun LongPressMenuHeader(item: LongPressable, modifier: Modifier = Modifier) { placeholder = painterResource(R.drawable.placeholder_thumbnail_video), error = painterResource(R.drawable.placeholder_thumbnail_video), modifier = Modifier - .fillMaxHeight() + .height(thumbnailHeight) .widthIn(max = 125.dp) // 16:9 thumbnail at most .clip(MaterialTheme.shapes.large) ) @@ -100,7 +136,7 @@ fun LongPressMenuHeader(item: LongPressable, modifier: Modifier = Modifier) { contentColor = Color.White, modifier = Modifier .align(Alignment.TopEnd) - .fillMaxHeight() + .height(thumbnailHeight) .width(40.dp) .clip(MaterialTheme.shapes.large), ) { @@ -143,10 +179,7 @@ fun LongPressMenuHeader(item: LongPressable, modifier: Modifier = Modifier) { } Column( - verticalArrangement = Arrangement.SpaceBetween, - modifier = Modifier - .height(70.dp) - .padding(vertical = 12.dp, horizontal = 12.dp), + modifier = Modifier.padding(vertical = 12.dp, horizontal = 12.dp), ) { Text( text = item.title, @@ -155,23 +188,36 @@ fun LongPressMenuHeader(item: LongPressable, modifier: Modifier = Modifier) { modifier = Modifier.basicMarquee(iterations = Int.MAX_VALUE), ) - Text( - text = Localization.concatenateStrings( - item.uploader, - item.uploadDate?.match( - { it }, - { Localization.localizeUploadDate(ctx, it) } - ), - item.viewCount?.let { Localization.localizeViewCount(ctx, it) } + val subtitle = Localization.concatenateStrings( + item.uploader, + item.uploadDate?.match( + { it }, + { Localization.relativeTime(it) } ), - style = MaterialTheme.typography.bodyMedium, - modifier = Modifier.basicMarquee(iterations = Int.MAX_VALUE), + item.viewCount?.let { Localization.localizeViewCount(ctx, it) } ) + if (subtitle.isNotBlank()) { + Spacer(Modifier.height(1.dp)) + Text( + text = subtitle, + style = MaterialTheme.typography.bodyMedium, + modifier = Modifier.basicMarquee(iterations = Int.MAX_VALUE), + ) + } } } } } +@Composable +fun LongPressMenuButton(modifier: Modifier = Modifier) { + Surface( + color = Color.Black, + modifier = modifier, + shape = MaterialTheme.shapes.large, + ) { } +} + private class LongPressablePreviews : CollectionPreviewParameterProvider( listOf( object : LongPressable { @@ -205,6 +251,21 @@ private class LongPressablePreviews : CollectionPreviewParameterProvider? = null + override val playlistSize: Long? = null + override val duration: Long? = null + + override fun getPlayQueue(): PlayQueue { + return SinglePlayQueue(listOf(), 0) + } + }, object : LongPressable { override val title: String = LoremIpsum().values.first() override val url: String = "https://www.youtube.com/watch?v=YE7VzlLtp-4" @@ -228,7 +289,7 @@ private class LongPressablePreviews : CollectionPreviewParameterProvider? = null + override val uploadDate: Either = Either.right(OffsetDateTime.now().minusSeconds(12)) override val playlistSize: Long = 1500 override val duration: Long = 500 @@ -240,13 +301,14 @@ private class LongPressablePreviews : CollectionPreviewParameterProvider