Skip to content

Commit

Permalink
feat(artist items): multi-select
Browse files Browse the repository at this point in the history
  • Loading branch information
z-huang committed Sep 1, 2024
1 parent d9aecfb commit d312039
Showing 1 changed file with 139 additions and 59 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.zionhuang.music.ui.screens.artist

import androidx.activity.compose.BackHandler
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.clickable
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.windowInsetsPadding
Expand All @@ -11,6 +11,7 @@ import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.Checkbox
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
Expand All @@ -20,13 +21,22 @@ import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.listSaver
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.runtime.toMutableStateList
import androidx.compose.ui.Modifier
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.NavController
Expand All @@ -52,6 +62,7 @@ import com.zionhuang.music.ui.menu.YouTubeAlbumMenu
import com.zionhuang.music.ui.menu.YouTubeArtistMenu
import com.zionhuang.music.ui.menu.YouTubePlaylistMenu
import com.zionhuang.music.ui.menu.YouTubeSongMenu
import com.zionhuang.music.ui.menu.YouTubeSongSelectionMenu
import com.zionhuang.music.ui.utils.backToMain
import com.zionhuang.music.viewmodels.ArtistItemsViewModel

Expand All @@ -73,6 +84,29 @@ fun ArtistItemsScreen(

val title by viewModel.title.collectAsState()
val itemsPage by viewModel.itemsPage.collectAsState()
val songIndex: Map<String, SongItem> by remember(itemsPage) {
derivedStateOf {
itemsPage?.items
?.filterIsInstance<SongItem>()
?.associateBy { it.id }
.orEmpty()
}
}

var inSelectMode by rememberSaveable { mutableStateOf(false) }
val selection = rememberSaveable(
saver = listSaver<MutableList<String>, String>(
save = { it.toList() },
restore = { it.toMutableStateList() }
)
) { mutableStateListOf() }
val onExitSelectionMode = {
inSelectMode = false
selection.clear()
}
if (inSelectMode) {
BackHandler(onBack = onExitSelectionMode)
}

LaunchedEffect(lazyListState) {
snapshotFlow {
Expand All @@ -99,76 +133,72 @@ fun ArtistItemsScreen(
contentPadding = LocalPlayerAwareWindowInsets.current.asPaddingValues()
) {
items(
items = itemsPage?.items.orEmpty(),
items = itemsPage?.items?.filterIsInstance<SongItem>().orEmpty(),
key = { it.id }
) { item ->
) { song ->
val onCheckedChange: (Boolean) -> Unit = {
if (it) {
selection.add(song.id)
} else {
selection.remove(song.id)
}
}

YouTubeListItem(
item = item,
isActive = when (item) {
is SongItem -> mediaMetadata?.id == item.id
is AlbumItem -> mediaMetadata?.album?.id == item.id
else -> false
},
item = song,
isActive = mediaMetadata?.id == song.id,
isPlaying = isPlaying,
trailingContent = {
IconButton(
onClick = {
menuState.show {
when (item) {
is SongItem -> YouTubeSongMenu(
song = item,
navController = navController,
onDismiss = menuState::dismiss
)

is AlbumItem -> YouTubeAlbumMenu(
albumItem = item,
if (inSelectMode) {
Checkbox(
checked = song.id in selection,
onCheckedChange = onCheckedChange
)
} else {
IconButton(
onClick = {
menuState.show {
YouTubeSongMenu(
song = song,
navController = navController,
onDismiss = menuState::dismiss
)

is ArtistItem -> YouTubeArtistMenu(
artist = item,
onDismiss = menuState::dismiss
)

is PlaylistItem -> YouTubePlaylistMenu(
playlist = item,
coroutineScope = coroutineScope,
onDismiss = menuState::dismiss
)
}
}
) {
Icon(
painter = painterResource(R.drawable.more_vert),
contentDescription = null
)
}
) {
Icon(
painter = painterResource(R.drawable.more_vert),
contentDescription = null
)
}
},
modifier = Modifier
.clickable {
when (item) {
is SongItem -> {
if (item.id == mediaMetadata?.id) {
playerConnection.player.togglePlayPause()
} else {
playerConnection.playQueue(YouTubeQueue(item.endpoint ?: WatchEndpoint(videoId = item.id), item.toMediaMetadata()))
}
.combinedClickable(
onClick = {
if (inSelectMode) {
onCheckedChange(song.id !in selection)
} else if (song.id == mediaMetadata?.id) {
playerConnection.player.togglePlayPause()
} else {
playerConnection.playQueue(YouTubeQueue(song.endpoint ?: WatchEndpoint(videoId = song.id), song.toMediaMetadata()))
}
},
onLongClick = {
if (!inSelectMode) {
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
inSelectMode = true
onCheckedChange(true)
}

is AlbumItem -> navController.navigate("album/${item.id}")
is ArtistItem -> navController.navigate("artist/${item.id}")
is PlaylistItem -> navController.navigate("online_playlist/${item.id}")
}
}
)
.animateItem()
)
}

if (itemsPage?.continuation != null) {
item(key = "loading") {
ShimmerHost {
ShimmerHost(Modifier.animateItem()) {
repeat(3) {
ListItemPlaceHolder()
}
Expand Down Expand Up @@ -235,22 +265,72 @@ fun ArtistItemsScreen(
}
}
)
.animateItem()
)
}
}
}

TopAppBar(
title = { Text(title) },
title = {
if (inSelectMode) {
Text(pluralStringResource(R.plurals.n_selected, selection.size, selection.size))
} else {
Text(title)
}
},
navigationIcon = {
IconButton(
onClick = navController::navigateUp,
onLongClick = navController::backToMain
) {
Icon(
painterResource(R.drawable.arrow_back),
contentDescription = null
if (inSelectMode) {
IconButton(onClick = onExitSelectionMode) {
Icon(
painter = painterResource(R.drawable.close),
contentDescription = null,
)
}
} else {
IconButton(
onClick = navController::navigateUp,
onLongClick = navController::backToMain
) {
Icon(
painterResource(R.drawable.arrow_back),
contentDescription = null
)
}
}
},
actions = {
if (inSelectMode) {
Checkbox(
checked = selection.size == itemsPage?.items?.size && selection.isNotEmpty(),
onCheckedChange = {
if (selection.size == itemsPage?.items?.size) {
selection.clear()
} else {
selection.clear()
selection.addAll(itemsPage?.items?.map { it.id }.orEmpty())
}
}
)
IconButton(
enabled = selection.isNotEmpty(),
onClick = {
menuState.show {
YouTubeSongSelectionMenu(
selection = selection.mapNotNull { songId ->
songIndex[songId]
},
onDismiss = menuState::dismiss,
onExitSelectionMode = onExitSelectionMode
)
}
}
) {
Icon(
painterResource(R.drawable.more_vert),
contentDescription = null
)
}
}
},
scrollBehavior = scrollBehavior
Expand Down

0 comments on commit d312039

Please sign in to comment.