mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-11-06 02:40:58 +01:00
Minor UI changes to playlist details page (#1749)
## Description Makes some minor changes to the playlist details page - Center playlist name - Show duration with seconds for songs - Adjust some padding Also fixes an issue where focus could be lost when leaving the page while focused on a playlist item Dev note: bulk of the change in thie PR is refactoring the `LazyColumn` of playlist items into its own composable ### Related issues N/A ### Testing Emulator ## Screenshots N/A, the changes are very minor ## AI or LLM usage None
This commit is contained in:
parent
26a23d673b
commit
4171d0b861
1 changed files with 157 additions and 129 deletions
|
|
@ -3,7 +3,6 @@ package com.github.damontecres.wholphin.ui.detail
|
|||
import android.content.Context
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.focusGroup
|
||||
import androidx.compose.foundation.focusable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.interaction.collectIsFocusedAsState
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
|
|
@ -81,6 +80,7 @@ import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
|||
import com.github.damontecres.wholphin.ui.components.ExpandableFaButton
|
||||
import com.github.damontecres.wholphin.ui.components.ExpandablePlayButton
|
||||
import com.github.damontecres.wholphin.ui.components.FilterByButton
|
||||
import com.github.damontecres.wholphin.ui.components.GridTitle
|
||||
import com.github.damontecres.wholphin.ui.components.LoadingPage
|
||||
import com.github.damontecres.wholphin.ui.components.MusicContextActions
|
||||
import com.github.damontecres.wholphin.ui.components.Optional
|
||||
|
|
@ -102,6 +102,7 @@ import com.github.damontecres.wholphin.ui.launchDefault
|
|||
import com.github.damontecres.wholphin.ui.launchIO
|
||||
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||
import com.github.damontecres.wholphin.ui.roundMinutes
|
||||
import com.github.damontecres.wholphin.ui.roundSeconds
|
||||
import com.github.damontecres.wholphin.ui.toServerString
|
||||
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
||||
import com.github.damontecres.wholphin.ui.util.LocalClock
|
||||
|
|
@ -367,7 +368,6 @@ fun PlaylistDetails(
|
|||
),
|
||||
addToPlaylistViewModel: AddPlaylistViewModel = hiltViewModel(),
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val state by viewModel.state.collectAsState()
|
||||
val musicState by viewModel.musicState.collectAsState()
|
||||
|
||||
|
|
@ -405,19 +405,21 @@ fun PlaylistDetails(
|
|||
}
|
||||
}
|
||||
val musicContextActions =
|
||||
MusicContextActions(
|
||||
navigateTo = { viewModel.navigationManager.navigateTo(it) },
|
||||
onClickPlay = { index, item -> play(index, item, false, MediaType.AUDIO) },
|
||||
onClickPlayNext = { index, item -> viewModel.playNext(item) },
|
||||
onClickAddToQueue = { item -> viewModel.addToQueue(item, Int.MAX_VALUE) },
|
||||
onClickFavorite = { id, favorite -> viewModel.setFavorite(id, favorite) },
|
||||
onClickAddPlaylist = { itemId ->
|
||||
addToPlaylistViewModel.loadPlaylists()
|
||||
showPlaylistDialog.makePresent(itemId)
|
||||
},
|
||||
onClickRemoveFromQueue = { _, _ -> },
|
||||
onDeleteItem = viewModel::deleteItem,
|
||||
)
|
||||
remember {
|
||||
MusicContextActions(
|
||||
navigateTo = { viewModel.navigationManager.navigateTo(it) },
|
||||
onClickPlay = { index, item -> play(index, item, false, MediaType.AUDIO) },
|
||||
onClickPlayNext = { index, item -> viewModel.playNext(item) },
|
||||
onClickAddToQueue = { item -> viewModel.addToQueue(item, Int.MAX_VALUE) },
|
||||
onClickFavorite = { id, favorite -> viewModel.setFavorite(id, favorite) },
|
||||
onClickAddPlaylist = { itemId ->
|
||||
addToPlaylistViewModel.loadPlaylists()
|
||||
showPlaylistDialog.makePresent(itemId)
|
||||
},
|
||||
onClickRemoveFromQueue = { _, _ -> },
|
||||
onDeleteItem = viewModel::deleteItem,
|
||||
)
|
||||
}
|
||||
val contextActions =
|
||||
remember {
|
||||
ContextMenuActions(
|
||||
|
|
@ -534,7 +536,6 @@ fun PlaylistDetailsContent(
|
|||
) {
|
||||
var savedIndex by rememberSaveable { mutableIntStateOf(0) }
|
||||
var focusedIndex by remember { mutableIntStateOf(savedIndex) }
|
||||
val focus = remember { FocusRequester() }
|
||||
val focusedItem = items.getOrNull(focusedIndex)
|
||||
LaunchedEffect(focusedItem) {
|
||||
focusedItem?.let(onChangeBackdrop)
|
||||
|
|
@ -555,9 +556,15 @@ fun PlaylistDetailsContent(
|
|||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
modifier =
|
||||
Modifier
|
||||
.padding(top = 16.dp)
|
||||
.fillMaxSize(),
|
||||
) {
|
||||
val title =
|
||||
if (loadingState is LoadingState.Success) {
|
||||
playlist?.name ?: stringResource(R.string.playlist)
|
||||
} else {
|
||||
""
|
||||
}
|
||||
GridTitle(title)
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(24.dp),
|
||||
modifier =
|
||||
|
|
@ -577,115 +584,28 @@ fun PlaylistDetailsContent(
|
|||
.padding(top = 80.dp)
|
||||
.fillMaxWidth(.25f),
|
||||
)
|
||||
when (loadingState) {
|
||||
is LoadingState.Error -> {
|
||||
ErrorMessage(loadingState, modifier)
|
||||
}
|
||||
|
||||
LoadingState.Pending, LoadingState.Loading -> {
|
||||
LoadingPage(modifier)
|
||||
}
|
||||
|
||||
LoadingState.Success -> {
|
||||
Column(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
) {
|
||||
Text(
|
||||
text = playlist?.name ?: stringResource(R.string.playlist),
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
style = MaterialTheme.typography.displayMedium,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
if (items.isNotEmpty()) {
|
||||
LazyColumn(
|
||||
contentPadding = PaddingValues(8.dp),
|
||||
modifier =
|
||||
Modifier
|
||||
.padding(bottom = 32.dp)
|
||||
.fillMaxHeight()
|
||||
// .fillMaxWidth(.8f)
|
||||
.weight(1f)
|
||||
.background(
|
||||
MaterialTheme.colorScheme
|
||||
.surfaceColorAtElevation(1.dp)
|
||||
.copy(alpha = .75f),
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
).focusProperties {
|
||||
onExit = {
|
||||
playButtonFocusRequester.tryRequestFocus()
|
||||
}
|
||||
}.focusRequester(focusRequester)
|
||||
.focusGroup()
|
||||
.focusRestorer(focus),
|
||||
) {
|
||||
itemsIndexed(items) { index, item ->
|
||||
PlaylistItem(
|
||||
item = item,
|
||||
index = index,
|
||||
onClick = {
|
||||
savedIndex = index
|
||||
item?.let {
|
||||
onClickIndex.invoke(index, item)
|
||||
}
|
||||
},
|
||||
onLongClick = {
|
||||
savedIndex = index
|
||||
item?.let {
|
||||
onLongClickIndex.invoke(index, item)
|
||||
}
|
||||
},
|
||||
isPlaying =
|
||||
equalsNotNull(
|
||||
musicState.currentItemId,
|
||||
item?.id,
|
||||
),
|
||||
isQueued = item?.id in musicState.queuedIds,
|
||||
modifier =
|
||||
Modifier
|
||||
.ifElse(
|
||||
item?.type != BaseItemKind.AUDIO,
|
||||
Modifier.height(80.dp),
|
||||
).ifElse(
|
||||
index == savedIndex,
|
||||
Modifier.focusRequester(focus),
|
||||
).onFocusChanged {
|
||||
if (it.isFocused) {
|
||||
focusedIndex = index
|
||||
}
|
||||
}.focusProperties {
|
||||
left = playButtonFocusRequester
|
||||
previous = playButtonFocusRequester
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Box(
|
||||
contentAlignment = Alignment.Center,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.no_results),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier =
|
||||
Modifier
|
||||
.focusProperties {
|
||||
onExit = {
|
||||
playButtonFocusRequester.tryRequestFocus()
|
||||
}
|
||||
}.focusRequester(focusRequester)
|
||||
.focusable(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
PlaylistItems(
|
||||
loadingState = loadingState,
|
||||
items = items,
|
||||
musicState = musicState,
|
||||
playButtonFocusRequester = playButtonFocusRequester,
|
||||
onFocusItem = { index, item ->
|
||||
focusedIndex = index
|
||||
},
|
||||
onClickItem = { index, item ->
|
||||
savedIndex = index
|
||||
item?.let { onClickIndex.invoke(index, item) }
|
||||
},
|
||||
onLongClickItem = { index, item ->
|
||||
savedIndex = index
|
||||
item?.let { onLongClickIndex.invoke(index, item) }
|
||||
},
|
||||
modifier =
|
||||
Modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.weight(1f)
|
||||
.focusRequester(focusRequester),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -771,6 +691,97 @@ fun PlaylistDetailsHeader(
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun PlaylistItems(
|
||||
loadingState: LoadingState,
|
||||
items: List<BaseItem?>,
|
||||
musicState: MusicServiceState,
|
||||
playButtonFocusRequester: FocusRequester,
|
||||
onFocusItem: (Int, BaseItem?) -> Unit,
|
||||
onClickItem: (Int, BaseItem?) -> Unit,
|
||||
onLongClickItem: (Int, BaseItem?) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
when (loadingState) {
|
||||
is LoadingState.Error -> {
|
||||
ErrorMessage(loadingState, modifier)
|
||||
}
|
||||
|
||||
LoadingState.Pending, LoadingState.Loading -> {
|
||||
LoadingPage(modifier)
|
||||
}
|
||||
|
||||
LoadingState.Success -> {
|
||||
if (items.isNotEmpty()) {
|
||||
LazyColumn(
|
||||
contentPadding = PaddingValues(8.dp),
|
||||
modifier =
|
||||
modifier
|
||||
.padding(bottom = 32.dp)
|
||||
.fillMaxHeight()
|
||||
.background(
|
||||
MaterialTheme.colorScheme
|
||||
.surfaceColorAtElevation(1.dp)
|
||||
.copy(alpha = .75f),
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
).focusProperties {
|
||||
left = playButtonFocusRequester
|
||||
previous = playButtonFocusRequester
|
||||
}.focusGroup()
|
||||
.focusRestorer(),
|
||||
) {
|
||||
itemsIndexed(items) { index, item ->
|
||||
PlaylistItem(
|
||||
item = item,
|
||||
index = index,
|
||||
onClick = {
|
||||
onClickItem.invoke(index, item)
|
||||
},
|
||||
onLongClick = {
|
||||
onLongClickItem.invoke(index, item)
|
||||
},
|
||||
isPlaying =
|
||||
equalsNotNull(
|
||||
musicState.currentItemId,
|
||||
item?.id,
|
||||
),
|
||||
isQueued = item?.id in musicState.queuedIds,
|
||||
modifier =
|
||||
Modifier
|
||||
.ifElse(
|
||||
item?.type != BaseItemKind.AUDIO,
|
||||
Modifier.height(80.dp),
|
||||
).onFocusChanged {
|
||||
if (it.isFocused) {
|
||||
onFocusItem(index, item)
|
||||
}
|
||||
}.focusProperties {
|
||||
left = playButtonFocusRequester
|
||||
previous = playButtonFocusRequester
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LaunchedEffect(Unit) {
|
||||
playButtonFocusRequester.tryRequestFocus()
|
||||
}
|
||||
Box(
|
||||
contentAlignment = Alignment.Center,
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.no_results),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun PlaylistItem(
|
||||
item: BaseItem?,
|
||||
|
|
@ -804,7 +815,23 @@ fun PlaylistItem(
|
|||
)
|
||||
},
|
||||
trailingContent = {
|
||||
item?.data?.runTimeTicks?.ticks?.roundMinutes?.let { duration ->
|
||||
val duration =
|
||||
when (item?.type) {
|
||||
BaseItemKind.AUDIO -> {
|
||||
item.data.runTimeTicks
|
||||
?.ticks
|
||||
?.roundSeconds
|
||||
}
|
||||
|
||||
else -> {
|
||||
item
|
||||
?.data
|
||||
?.runTimeTicks
|
||||
?.ticks
|
||||
?.roundMinutes
|
||||
}
|
||||
}
|
||||
duration?.let { duration ->
|
||||
val now by LocalClock.current.now
|
||||
val context = LocalContext.current
|
||||
val endTimeStr =
|
||||
|
|
@ -813,12 +840,13 @@ fun PlaylistItem(
|
|||
formatTime(context, endTime)
|
||||
}
|
||||
val resources = LocalResources.current
|
||||
val durationText = remember(duration) { resources.formatDuration(duration) }
|
||||
val durationText =
|
||||
remember(resources, duration) { resources.formatDuration(duration) }
|
||||
Column {
|
||||
Text(
|
||||
text = durationText,
|
||||
)
|
||||
if (item.type != BaseItemKind.AUDIO) {
|
||||
if (item?.type != BaseItemKind.AUDIO) {
|
||||
Text(
|
||||
text = stringResource(R.string.ends_at, endTimeStr),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue