Skip to content

Commit b471bc8

Browse files
committed
feat(queue): unify multiselect look with playlist screen
1 parent ec6c3eb commit b471bc8

File tree

2 files changed

+143
-91
lines changed

2 files changed

+143
-91
lines changed

app/src/main/java/com/malopieds/innertune/ui/component/Items.kt

+43-19
Original file line numberDiff line numberDiff line change
@@ -1116,6 +1116,7 @@ fun PlaylistGridItem(
11161116
fun MediaMetadataListItem(
11171117
mediaMetadata: MediaMetadata,
11181118
modifier: Modifier,
1119+
isSelected: Boolean = false,
11191120
isActive: Boolean = false,
11201121
isPlaying: Boolean = false,
11211122
trailingContent: @Composable RowScope.() -> Unit = {},
@@ -1127,26 +1128,49 @@ fun MediaMetadataListItem(
11271128
makeTimeString(mediaMetadata.duration * 1000L),
11281129
),
11291130
thumbnailContent = {
1130-
AsyncImage(
1131-
model = mediaMetadata.thumbnailUrl,
1132-
contentDescription = null,
1133-
modifier =
1134-
Modifier
1135-
.size(ListThumbnailSize)
1136-
.clip(RoundedCornerShape(ThumbnailCornerRadius)),
1137-
)
1131+
Box(
1132+
contentAlignment = Alignment.Center,
1133+
modifier = Modifier.size(ListThumbnailSize),
1134+
) {
1135+
if (isSelected) {
1136+
Box(
1137+
modifier =
1138+
Modifier
1139+
.fillMaxSize()
1140+
.zIndex(1000f)
1141+
.clip(RoundedCornerShape(ThumbnailCornerRadius))
1142+
.background(Color.Black.copy(alpha = 0.5f)),
1143+
) {
1144+
Icon(
1145+
painter = painterResource(R.drawable.done),
1146+
modifier = Modifier.align(Alignment.Center),
1147+
contentDescription = null,
1148+
)
1149+
}
1150+
}
11381151

1139-
PlayingIndicatorBox(
1140-
isActive = isActive,
1141-
playWhenReady = isPlaying,
1142-
modifier =
1143-
Modifier
1144-
.size(ListThumbnailSize)
1145-
.background(
1146-
color = Color.Black.copy(alpha = 0.4f),
1147-
shape = RoundedCornerShape(ThumbnailCornerRadius),
1148-
),
1149-
)
1152+
AsyncImage(
1153+
model = mediaMetadata.thumbnailUrl,
1154+
contentDescription = null,
1155+
modifier =
1156+
Modifier
1157+
.fillMaxSize()
1158+
.clip(RoundedCornerShape(ThumbnailCornerRadius)),
1159+
)
1160+
1161+
PlayingIndicatorBox(
1162+
isActive = isActive,
1163+
playWhenReady = isPlaying,
1164+
color = Color.White,
1165+
modifier =
1166+
Modifier
1167+
.fillMaxSize()
1168+
.background(
1169+
color = Color.Black.copy(alpha = 0.4f),
1170+
shape = RoundedCornerShape(ThumbnailCornerRadius),
1171+
),
1172+
)
1173+
}
11501174
},
11511175
trailingContent = trailingContent,
11521176
modifier = modifier,

app/src/main/java/com/malopieds/innertune/ui/player/Queue.kt

+100-72
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,14 @@ package com.malopieds.innertune.ui.player
33
import android.annotation.SuppressLint
44
import android.text.format.Formatter
55
import android.widget.Toast
6+
import androidx.compose.animation.AnimatedVisibility
7+
import androidx.compose.animation.animateContentSize
8+
import androidx.compose.animation.expandVertically
9+
import androidx.compose.animation.fadeIn
10+
import androidx.compose.animation.fadeOut
11+
import androidx.compose.animation.shrinkVertically
12+
import androidx.compose.animation.slideInVertically
13+
import androidx.compose.animation.slideOutVertically
614
import androidx.compose.foundation.ExperimentalFoundationApi
715
import androidx.compose.foundation.background
816
import androidx.compose.foundation.clickable
@@ -120,6 +128,9 @@ fun Queue(
120128

121129
val selectedSongs: MutableList<MediaMetadata> = mutableStateListOf()
122130
val selectedItems: MutableList<Timeline.Window> = mutableStateListOf()
131+
var selection by remember {
132+
mutableStateOf(false)
133+
}
123134

124135
var showDetailsDialog by rememberSaveable {
125136
mutableStateOf(false)
@@ -278,6 +289,15 @@ fun Queue(
278289
.reorderable(reorderableState)
279290
.nestedScroll(state.preUpPostDownNestedScrollConnection),
280291
) {
292+
item {
293+
Spacer(
294+
modifier =
295+
Modifier
296+
.animateContentSize()
297+
.height(if (selection) 64.dp else 0.dp),
298+
)
299+
}
300+
281301
itemsIndexed(
282302
items = mutableQueueWindows,
283303
key = { _, item -> item.uid.hashCode() },
@@ -308,40 +328,9 @@ fun Queue(
308328
// state = dismissState,
309329
// = {
310330
) {
311-
Row(
312-
horizontalArrangement = Arrangement.Center,
313-
) {
314-
IconButton(
315-
modifier =
316-
Modifier
317-
.align(Alignment.CenterVertically),
318-
onClick = {
319-
if (window.mediaItem.metadata!! in selectedSongs) {
320-
selectedSongs.remove(window.mediaItem.metadata!!)
321-
selectedItems.remove(currentItem)
322-
} else {
323-
selectedSongs.add(window.mediaItem.metadata!!)
324-
selectedItems.add(currentItem)
325-
}
326-
},
327-
) {
328-
Icon(
329-
painter =
330-
painterResource(
331-
if (window.mediaItem.metadata!! in
332-
selectedSongs
333-
) {
334-
R.drawable.check_box
335-
} else {
336-
R.drawable.uncheck_box
337-
},
338-
),
339-
contentDescription = null,
340-
tint = LocalContentColor.current,
341-
)
342-
}
343331
MediaMetadataListItem(
344332
mediaMetadata = window.mediaItem.metadata!!,
333+
isSelected = selection && window.mediaItem.metadata!! in selectedSongs,
345334
isActive = index == currentWindowIndex,
346335
isPlaying = isPlaying,
347336
trailingContent = {
@@ -362,7 +351,15 @@ fun Queue(
362351
.fillMaxWidth()
363352
.combinedClickable(
364353
onClick = {
365-
if (selectedSongs.isEmpty()) {
354+
if (selection) {
355+
if (window.mediaItem.metadata!! in selectedSongs) {
356+
selectedSongs.remove(window.mediaItem.metadata!!)
357+
selectedItems.remove(currentItem)
358+
} else {
359+
selectedSongs.add(window.mediaItem.metadata!!)
360+
selectedItems.add(currentItem)
361+
}
362+
} else {
366363
if (index == currentWindowIndex) {
367364
playerConnection.player.togglePlayPause()
368365
} else {
@@ -371,14 +368,6 @@ fun Queue(
371368
)
372369
playerConnection.player.playWhenReady = true
373370
}
374-
} else {
375-
if (window.mediaItem.metadata!! in selectedSongs) {
376-
selectedSongs.remove(window.mediaItem.metadata!!)
377-
selectedItems.remove(currentItem)
378-
} else {
379-
selectedSongs.add(window.mediaItem.metadata!!)
380-
selectedItems.add(currentItem)
381-
}
382371
}
383372
},
384373
onLongClick = {
@@ -398,13 +387,12 @@ fun Queue(
398387
),
399388
// .detectReorderAfterLongPress(reorderableState)
400389
)
401-
}
402390
}
403391
}
404392
}
405393
}
406394

407-
Box(
395+
Column(
408396
modifier =
409397
Modifier
410398
.background(
@@ -431,19 +419,80 @@ fun Queue(
431419
modifier = Modifier.weight(1f),
432420
)
433421

434-
if (selectedSongs.isNotEmpty()) {
422+
AnimatedVisibility(
423+
visible = !selection,
424+
enter = fadeIn() + slideInVertically { it },
425+
exit = fadeOut() + slideOutVertically { it },
426+
) {
435427
IconButton(
436428
onClick = {
437-
selectedSongs.clear()
438-
selectedItems.clear()
429+
selection = true
439430
},
440431
) {
441432
Icon(
442-
painter = painterResource(R.drawable.deselect),
433+
painter = painterResource(R.drawable.select_all),
443434
contentDescription = null,
444-
tint = LocalContentColor.current,
445435
)
446436
}
437+
}
438+
439+
Column(
440+
verticalArrangement = Arrangement.spacedBy(4.dp),
441+
horizontalAlignment = Alignment.End,
442+
) {
443+
Text(
444+
text = pluralStringResource(R.plurals.n_song, queueWindows.size, queueWindows.size),
445+
style = MaterialTheme.typography.bodyMedium,
446+
)
447+
448+
Text(
449+
text = makeTimeString(queueLength * 1000L),
450+
style = MaterialTheme.typography.bodyMedium,
451+
)
452+
}
453+
}
454+
455+
AnimatedVisibility(
456+
visible = selection,
457+
enter = fadeIn() + expandVertically(),
458+
exit = fadeOut() + shrinkVertically(),
459+
) {
460+
Row(
461+
modifier = Modifier
462+
.height(64.dp)
463+
.padding(start = 16.dp),
464+
verticalAlignment = Alignment.CenterVertically,
465+
) {
466+
val count = selectedSongs.size
467+
Text(text = "$count elements selected", modifier = Modifier.weight(1f))
468+
IconButton(
469+
onClick = {
470+
if (count == mutableQueueWindows.size) {
471+
selectedSongs.clear()
472+
selectedItems.clear()
473+
} else {
474+
queueWindows
475+
.filter { it.mediaItem.metadata!! !in selectedSongs }
476+
.forEach {
477+
selectedSongs.add(it.mediaItem.metadata!!)
478+
selectedItems.add(it)
479+
}
480+
}
481+
},
482+
) {
483+
Icon(
484+
painter =
485+
painterResource(
486+
if (count == mutableQueueWindows.size) {
487+
R.drawable.deselect
488+
} else {
489+
R.drawable.select_all
490+
},
491+
),
492+
contentDescription = null,
493+
)
494+
}
495+
447496
IconButton(
448497
onClick = {
449498
menuState.show {
@@ -465,37 +514,16 @@ fun Queue(
465514
tint = LocalContentColor.current,
466515
)
467516
}
468-
} else {
517+
469518
IconButton(
470-
onClick = {
471-
queueWindows.forEach {
472-
selectedSongs.add(it.mediaItem.metadata!!)
473-
selectedItems.add(it)
474-
}
475-
},
519+
onClick = { selection = false },
476520
) {
477521
Icon(
478-
painter = painterResource(R.drawable.select_all),
522+
painter = painterResource(R.drawable.close),
479523
contentDescription = null,
480-
tint = LocalContentColor.current,
481524
)
482525
}
483526
}
484-
485-
Column(
486-
verticalArrangement = Arrangement.spacedBy(4.dp),
487-
horizontalAlignment = Alignment.End,
488-
) {
489-
Text(
490-
text = pluralStringResource(R.plurals.n_song, queueWindows.size, queueWindows.size),
491-
style = MaterialTheme.typography.bodyMedium,
492-
)
493-
494-
Text(
495-
text = makeTimeString(queueLength * 1000L),
496-
style = MaterialTheme.typography.bodyMedium,
497-
)
498-
}
499527
}
500528
}
501529

0 commit comments

Comments
 (0)