Skip to content

Commit 9256945

Browse files
committed
Calculate button placing in long press menu
1 parent 5699233 commit 9256945

1 file changed

Lines changed: 96 additions & 34 deletions

File tree

app/src/main/java/org/schabi/newpipe/ui/components/menu/LongPressMenu.kt

Lines changed: 96 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
1-
@file:OptIn(ExperimentalMaterial3Api::class)
1+
@file:OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class)
22

33
package org.schabi.newpipe.ui.components.menu
44

55
import androidx.compose.foundation.basicMarquee
66
import androidx.compose.foundation.layout.Arrangement
77
import androidx.compose.foundation.layout.Box
8+
import androidx.compose.foundation.layout.BoxWithConstraints
89
import androidx.compose.foundation.layout.Column
10+
import androidx.compose.foundation.layout.ExperimentalLayoutApi
11+
import androidx.compose.foundation.layout.FlowRow
912
import androidx.compose.foundation.layout.Row
1013
import androidx.compose.foundation.layout.Spacer
11-
import androidx.compose.foundation.layout.fillMaxHeight
1214
import androidx.compose.foundation.layout.fillMaxWidth
1315
import androidx.compose.foundation.layout.height
1416
import androidx.compose.foundation.layout.padding
17+
import androidx.compose.foundation.layout.size
1518
import androidx.compose.foundation.layout.width
1619
import androidx.compose.foundation.layout.widthIn
1720
import androidx.compose.material.icons.Icons
@@ -36,7 +39,9 @@ import androidx.compose.ui.tooling.preview.Preview
3639
import androidx.compose.ui.tooling.preview.PreviewParameter
3740
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
3841
import androidx.compose.ui.tooling.preview.datasource.LoremIpsum
42+
import androidx.compose.ui.unit.Dp
3943
import androidx.compose.ui.unit.dp
44+
import androidx.compose.ui.unit.times
4045
import coil3.compose.AsyncImage
4146
import org.schabi.newpipe.R
4247
import org.schabi.newpipe.player.playqueue.PlayQueue
@@ -55,20 +60,53 @@ fun LongPressMenu(
5560
onDismissRequest,
5661
sheetState = sheetState,
5762
) {
58-
Column {
59-
LongPressMenuHeader(
60-
item = longPressable,
61-
modifier = Modifier
62-
.padding(horizontal = 12.dp)
63-
.fillMaxWidth()
64-
)
65-
Spacer(Modifier.height(100.dp))
63+
BoxWithConstraints(
64+
modifier = Modifier.fillMaxWidth()
65+
.padding(bottom = 16.dp)
66+
) {
67+
val maxContainerWidth = maxWidth
68+
val minButtonWidth = 60.dp
69+
val buttonHeight = 70.dp
70+
val padding = 12.dp
71+
val boxCount = ((maxContainerWidth - padding) / (minButtonWidth + padding)).toInt()
72+
val buttonWidth = (maxContainerWidth - (boxCount + 1) * padding) / boxCount
73+
val desiredHeaderWidth = buttonWidth * 5 + padding * 4
74+
75+
FlowRow(
76+
horizontalArrangement = Arrangement.spacedBy(padding),
77+
verticalArrangement = Arrangement.spacedBy(padding),
78+
// left and right padding are implicit in the .align(Center), this way approximation
79+
// errors in the calculations above don't make the items wrap at the wrong position
80+
modifier = Modifier.align(Alignment.Center),
81+
) {
82+
LongPressMenuHeader(
83+
item = longPressable,
84+
thumbnailHeight = buttonHeight,
85+
// subtract 2.dp to account for approximation errors in the calculations above
86+
modifier = if (desiredHeaderWidth >= maxContainerWidth - 2 * padding - 2.dp) {
87+
// leave the height as small as possible, since it's the only item on the
88+
// row anyway
89+
Modifier.width(maxContainerWidth - 2 * padding)
90+
} else {
91+
// make sure it has the same height as other buttons
92+
Modifier.size(desiredHeaderWidth, buttonHeight)
93+
}
94+
)
95+
96+
for (i in 0..10) {
97+
LongPressMenuButton(modifier = Modifier.size(buttonWidth, buttonHeight))
98+
}
99+
}
66100
}
67101
}
68102
}
69103

70104
@Composable
71-
fun LongPressMenuHeader(item: LongPressable, modifier: Modifier = Modifier) {
105+
fun LongPressMenuHeader(
106+
item: LongPressable,
107+
thumbnailHeight: Dp,
108+
modifier: Modifier = Modifier,
109+
) {
72110
val ctx = LocalContext.current
73111

74112
Surface(
@@ -77,18 +115,16 @@ fun LongPressMenuHeader(item: LongPressable, modifier: Modifier = Modifier) {
77115
shape = MaterialTheme.shapes.large,
78116
modifier = modifier,
79117
) {
80-
Row {
81-
Box(
82-
modifier = Modifier.height(70.dp)
83-
) {
118+
Row(verticalAlignment = Alignment.CenterVertically) {
119+
Box {
84120
if (item.thumbnailUrl != null) {
85121
AsyncImage(
86122
model = item.thumbnailUrl,
87123
contentDescription = null,
88124
placeholder = painterResource(R.drawable.placeholder_thumbnail_video),
89125
error = painterResource(R.drawable.placeholder_thumbnail_video),
90126
modifier = Modifier
91-
.fillMaxHeight()
127+
.height(thumbnailHeight)
92128
.widthIn(max = 125.dp) // 16:9 thumbnail at most
93129
.clip(MaterialTheme.shapes.large)
94130
)
@@ -100,7 +136,7 @@ fun LongPressMenuHeader(item: LongPressable, modifier: Modifier = Modifier) {
100136
contentColor = Color.White,
101137
modifier = Modifier
102138
.align(Alignment.TopEnd)
103-
.fillMaxHeight()
139+
.height(thumbnailHeight)
104140
.width(40.dp)
105141
.clip(MaterialTheme.shapes.large),
106142
) {
@@ -143,10 +179,7 @@ fun LongPressMenuHeader(item: LongPressable, modifier: Modifier = Modifier) {
143179
}
144180

145181
Column(
146-
verticalArrangement = Arrangement.SpaceBetween,
147-
modifier = Modifier
148-
.height(70.dp)
149-
.padding(vertical = 12.dp, horizontal = 12.dp),
182+
modifier = Modifier.padding(vertical = 12.dp, horizontal = 12.dp),
150183
) {
151184
Text(
152185
text = item.title,
@@ -155,23 +188,36 @@ fun LongPressMenuHeader(item: LongPressable, modifier: Modifier = Modifier) {
155188
modifier = Modifier.basicMarquee(iterations = Int.MAX_VALUE),
156189
)
157190

158-
Text(
159-
text = Localization.concatenateStrings(
160-
item.uploader,
161-
item.uploadDate?.match<String>(
162-
{ it },
163-
{ Localization.localizeUploadDate(ctx, it) }
164-
),
165-
item.viewCount?.let { Localization.localizeViewCount(ctx, it) }
191+
val subtitle = Localization.concatenateStrings(
192+
item.uploader,
193+
item.uploadDate?.match<String>(
194+
{ it },
195+
{ Localization.relativeTime(it) }
166196
),
167-
style = MaterialTheme.typography.bodyMedium,
168-
modifier = Modifier.basicMarquee(iterations = Int.MAX_VALUE),
197+
item.viewCount?.let { Localization.localizeViewCount(ctx, it) }
169198
)
199+
if (subtitle.isNotBlank()) {
200+
Spacer(Modifier.height(1.dp))
201+
Text(
202+
text = subtitle,
203+
style = MaterialTheme.typography.bodyMedium,
204+
modifier = Modifier.basicMarquee(iterations = Int.MAX_VALUE),
205+
)
206+
}
170207
}
171208
}
172209
}
173210
}
174211

212+
@Composable
213+
fun LongPressMenuButton(modifier: Modifier = Modifier) {
214+
Surface(
215+
color = Color.Black,
216+
modifier = modifier,
217+
shape = MaterialTheme.shapes.large,
218+
) { }
219+
}
220+
175221
private class LongPressablePreviews : CollectionPreviewParameterProvider<LongPressable>(
176222
listOf(
177223
object : LongPressable {
@@ -205,6 +251,21 @@ private class LongPressablePreviews : CollectionPreviewParameterProvider<LongPre
205251
return SinglePlayQueue(listOf(), 0)
206252
}
207253
},
254+
object : LongPressable {
255+
override val title: String = LoremIpsum().values.first()
256+
override val url: String = "https://www.youtube.com/watch?v=YE7VzlLtp-4"
257+
override val thumbnailUrl: String? = null
258+
override val uploader: String? = null
259+
override val uploaderUrl: String = "https://www.youtube.com/@BlenderOfficial"
260+
override val viewCount: Long? = null
261+
override val uploadDate: Either<String, OffsetDateTime>? = null
262+
override val playlistSize: Long? = null
263+
override val duration: Long? = null
264+
265+
override fun getPlayQueue(): PlayQueue {
266+
return SinglePlayQueue(listOf(), 0)
267+
}
268+
},
208269
object : LongPressable {
209270
override val title: String = LoremIpsum().values.first()
210271
override val url: String = "https://www.youtube.com/watch?v=YE7VzlLtp-4"
@@ -228,7 +289,7 @@ private class LongPressablePreviews : CollectionPreviewParameterProvider<LongPre
228289
override val uploader: String? = null
229290
override val uploaderUrl: String? = null
230291
override val viewCount: Long? = null
231-
override val uploadDate: Either<String, OffsetDateTime>? = null
292+
override val uploadDate: Either<String, OffsetDateTime> = Either.right(OffsetDateTime.now().minusSeconds(12))
232293
override val playlistSize: Long = 1500
233294
override val duration: Long = 500
234295

@@ -240,13 +301,14 @@ private class LongPressablePreviews : CollectionPreviewParameterProvider<LongPre
240301
)
241302

242303
@Preview
304+
@Preview(device = "spec:width=1280dp,height=800dp,dpi=240")
243305
@Composable
244306
private fun LongPressMenuPreview(
245307
@PreviewParameter(LongPressablePreviews::class) longPressable: LongPressable
246308
) {
247309
LongPressMenu(
248-
longPressable = longPressable,
310+
longPressable = LongPressablePreviews().values.toList()[4],
249311
onDismissRequest = {},
250-
sheetState = rememberStandardBottomSheetState(), // makes it start out as open
312+
sheetState = rememberStandardBottomSheetState(), // makes it start out as open
251313
)
252314
}

0 commit comments

Comments
 (0)