mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-11-07 03:04:50 +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 android.content.Context
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.focusGroup
|
import androidx.compose.foundation.focusGroup
|
||||||
import androidx.compose.foundation.focusable
|
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.interaction.collectIsFocusedAsState
|
import androidx.compose.foundation.interaction.collectIsFocusedAsState
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
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.ExpandableFaButton
|
||||||
import com.github.damontecres.wholphin.ui.components.ExpandablePlayButton
|
import com.github.damontecres.wholphin.ui.components.ExpandablePlayButton
|
||||||
import com.github.damontecres.wholphin.ui.components.FilterByButton
|
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.LoadingPage
|
||||||
import com.github.damontecres.wholphin.ui.components.MusicContextActions
|
import com.github.damontecres.wholphin.ui.components.MusicContextActions
|
||||||
import com.github.damontecres.wholphin.ui.components.Optional
|
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.launchIO
|
||||||
import com.github.damontecres.wholphin.ui.nav.Destination
|
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||||
import com.github.damontecres.wholphin.ui.roundMinutes
|
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.toServerString
|
||||||
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
||||||
import com.github.damontecres.wholphin.ui.util.LocalClock
|
import com.github.damontecres.wholphin.ui.util.LocalClock
|
||||||
|
|
@ -367,7 +368,6 @@ fun PlaylistDetails(
|
||||||
),
|
),
|
||||||
addToPlaylistViewModel: AddPlaylistViewModel = hiltViewModel(),
|
addToPlaylistViewModel: AddPlaylistViewModel = hiltViewModel(),
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
|
||||||
val state by viewModel.state.collectAsState()
|
val state by viewModel.state.collectAsState()
|
||||||
val musicState by viewModel.musicState.collectAsState()
|
val musicState by viewModel.musicState.collectAsState()
|
||||||
|
|
||||||
|
|
@ -405,19 +405,21 @@ fun PlaylistDetails(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val musicContextActions =
|
val musicContextActions =
|
||||||
MusicContextActions(
|
remember {
|
||||||
navigateTo = { viewModel.navigationManager.navigateTo(it) },
|
MusicContextActions(
|
||||||
onClickPlay = { index, item -> play(index, item, false, MediaType.AUDIO) },
|
navigateTo = { viewModel.navigationManager.navigateTo(it) },
|
||||||
onClickPlayNext = { index, item -> viewModel.playNext(item) },
|
onClickPlay = { index, item -> play(index, item, false, MediaType.AUDIO) },
|
||||||
onClickAddToQueue = { item -> viewModel.addToQueue(item, Int.MAX_VALUE) },
|
onClickPlayNext = { index, item -> viewModel.playNext(item) },
|
||||||
onClickFavorite = { id, favorite -> viewModel.setFavorite(id, favorite) },
|
onClickAddToQueue = { item -> viewModel.addToQueue(item, Int.MAX_VALUE) },
|
||||||
onClickAddPlaylist = { itemId ->
|
onClickFavorite = { id, favorite -> viewModel.setFavorite(id, favorite) },
|
||||||
addToPlaylistViewModel.loadPlaylists()
|
onClickAddPlaylist = { itemId ->
|
||||||
showPlaylistDialog.makePresent(itemId)
|
addToPlaylistViewModel.loadPlaylists()
|
||||||
},
|
showPlaylistDialog.makePresent(itemId)
|
||||||
onClickRemoveFromQueue = { _, _ -> },
|
},
|
||||||
onDeleteItem = viewModel::deleteItem,
|
onClickRemoveFromQueue = { _, _ -> },
|
||||||
)
|
onDeleteItem = viewModel::deleteItem,
|
||||||
|
)
|
||||||
|
}
|
||||||
val contextActions =
|
val contextActions =
|
||||||
remember {
|
remember {
|
||||||
ContextMenuActions(
|
ContextMenuActions(
|
||||||
|
|
@ -534,7 +536,6 @@ fun PlaylistDetailsContent(
|
||||||
) {
|
) {
|
||||||
var savedIndex by rememberSaveable { mutableIntStateOf(0) }
|
var savedIndex by rememberSaveable { mutableIntStateOf(0) }
|
||||||
var focusedIndex by remember { mutableIntStateOf(savedIndex) }
|
var focusedIndex by remember { mutableIntStateOf(savedIndex) }
|
||||||
val focus = remember { FocusRequester() }
|
|
||||||
val focusedItem = items.getOrNull(focusedIndex)
|
val focusedItem = items.getOrNull(focusedIndex)
|
||||||
LaunchedEffect(focusedItem) {
|
LaunchedEffect(focusedItem) {
|
||||||
focusedItem?.let(onChangeBackdrop)
|
focusedItem?.let(onChangeBackdrop)
|
||||||
|
|
@ -555,9 +556,15 @@ fun PlaylistDetailsContent(
|
||||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
.padding(top = 16.dp)
|
|
||||||
.fillMaxSize(),
|
.fillMaxSize(),
|
||||||
) {
|
) {
|
||||||
|
val title =
|
||||||
|
if (loadingState is LoadingState.Success) {
|
||||||
|
playlist?.name ?: stringResource(R.string.playlist)
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
}
|
||||||
|
GridTitle(title)
|
||||||
Row(
|
Row(
|
||||||
horizontalArrangement = Arrangement.spacedBy(24.dp),
|
horizontalArrangement = Arrangement.spacedBy(24.dp),
|
||||||
modifier =
|
modifier =
|
||||||
|
|
@ -577,115 +584,28 @@ fun PlaylistDetailsContent(
|
||||||
.padding(top = 80.dp)
|
.padding(top = 80.dp)
|
||||||
.fillMaxWidth(.25f),
|
.fillMaxWidth(.25f),
|
||||||
)
|
)
|
||||||
when (loadingState) {
|
PlaylistItems(
|
||||||
is LoadingState.Error -> {
|
loadingState = loadingState,
|
||||||
ErrorMessage(loadingState, modifier)
|
items = items,
|
||||||
}
|
musicState = musicState,
|
||||||
|
playButtonFocusRequester = playButtonFocusRequester,
|
||||||
LoadingState.Pending, LoadingState.Loading -> {
|
onFocusItem = { index, item ->
|
||||||
LoadingPage(modifier)
|
focusedIndex = index
|
||||||
}
|
},
|
||||||
|
onClickItem = { index, item ->
|
||||||
LoadingState.Success -> {
|
savedIndex = index
|
||||||
Column(
|
item?.let { onClickIndex.invoke(index, item) }
|
||||||
modifier =
|
},
|
||||||
Modifier
|
onLongClickItem = { index, item ->
|
||||||
.fillMaxWidth()
|
savedIndex = index
|
||||||
.padding(horizontal = 16.dp),
|
item?.let { onLongClickIndex.invoke(index, item) }
|
||||||
) {
|
},
|
||||||
Text(
|
modifier =
|
||||||
text = playlist?.name ?: stringResource(R.string.playlist),
|
Modifier
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
.padding(horizontal = 16.dp)
|
||||||
style = MaterialTheme.typography.displayMedium,
|
.weight(1f)
|
||||||
textAlign = TextAlign.Center,
|
.focusRequester(focusRequester),
|
||||||
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(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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
|
@Composable
|
||||||
fun PlaylistItem(
|
fun PlaylistItem(
|
||||||
item: BaseItem?,
|
item: BaseItem?,
|
||||||
|
|
@ -804,7 +815,23 @@ fun PlaylistItem(
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
trailingContent = {
|
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 now by LocalClock.current.now
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val endTimeStr =
|
val endTimeStr =
|
||||||
|
|
@ -813,12 +840,13 @@ fun PlaylistItem(
|
||||||
formatTime(context, endTime)
|
formatTime(context, endTime)
|
||||||
}
|
}
|
||||||
val resources = LocalResources.current
|
val resources = LocalResources.current
|
||||||
val durationText = remember(duration) { resources.formatDuration(duration) }
|
val durationText =
|
||||||
|
remember(resources, duration) { resources.formatDuration(duration) }
|
||||||
Column {
|
Column {
|
||||||
Text(
|
Text(
|
||||||
text = durationText,
|
text = durationText,
|
||||||
)
|
)
|
||||||
if (item.type != BaseItemKind.AUDIO) {
|
if (item?.type != BaseItemKind.AUDIO) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.ends_at, endTimeStr),
|
text = stringResource(R.string.ends_at, endTimeStr),
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue