mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 23:51:21 +02:00
Save/restore focus
This commit is contained in:
parent
2879cd477c
commit
5d6b5527dc
2 changed files with 102 additions and 28 deletions
|
|
@ -67,11 +67,14 @@ import com.github.damontecres.wholphin.ui.components.Optional
|
||||||
import com.github.damontecres.wholphin.ui.components.OverviewText
|
import com.github.damontecres.wholphin.ui.components.OverviewText
|
||||||
import com.github.damontecres.wholphin.ui.components.QuickDetails
|
import com.github.damontecres.wholphin.ui.components.QuickDetails
|
||||||
import com.github.damontecres.wholphin.ui.data.AddPlaylistViewModel
|
import com.github.damontecres.wholphin.ui.data.AddPlaylistViewModel
|
||||||
|
import com.github.damontecres.wholphin.ui.data.RowColumn
|
||||||
import com.github.damontecres.wholphin.ui.detail.PlaylistDialog
|
import com.github.damontecres.wholphin.ui.detail.PlaylistDialog
|
||||||
import com.github.damontecres.wholphin.ui.detail.PlaylistLoadingState
|
import com.github.damontecres.wholphin.ui.detail.PlaylistLoadingState
|
||||||
import com.github.damontecres.wholphin.ui.ifElse
|
import com.github.damontecres.wholphin.ui.ifElse
|
||||||
|
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.letNotEmpty
|
import com.github.damontecres.wholphin.ui.letNotEmpty
|
||||||
|
import com.github.damontecres.wholphin.ui.rememberPosition
|
||||||
import com.github.damontecres.wholphin.ui.toBaseItems
|
import com.github.damontecres.wholphin.ui.toBaseItems
|
||||||
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
||||||
import com.github.damontecres.wholphin.util.ApiRequestPager
|
import com.github.damontecres.wholphin.util.ApiRequestPager
|
||||||
|
|
@ -159,22 +162,7 @@ class AlbumViewModel
|
||||||
loading = LoadingState.Success,
|
loading = LoadingState.Success,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (album.data.imageTags?.contains(ImageType.BACKDROP) == true) {
|
updateBackDrop()
|
||||||
backdropService.submit(album)
|
|
||||||
} else {
|
|
||||||
val artistIds =
|
|
||||||
album.data.albumArtists
|
|
||||||
?.shuffled()
|
|
||||||
?.take(50)
|
|
||||||
?.map { it.id }
|
|
||||||
api.itemsApi
|
|
||||||
.getItems(
|
|
||||||
ids = artistIds,
|
|
||||||
imageTypes = listOf(ImageType.BACKDROP),
|
|
||||||
).content.items
|
|
||||||
.firstOrNull()
|
|
||||||
?.let { backdropService.submit(BaseItem(it, false)) }
|
|
||||||
}
|
|
||||||
if (state.value.similar.isEmpty()) {
|
if (state.value.similar.isEmpty()) {
|
||||||
viewModelScope.launchIO {
|
viewModelScope.launchIO {
|
||||||
val similar =
|
val similar =
|
||||||
|
|
@ -216,6 +204,29 @@ class AlbumViewModel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun updateBackDrop() {
|
||||||
|
state.value.album?.let { album ->
|
||||||
|
viewModelScope.launchDefault {
|
||||||
|
if (album.data.imageTags?.contains(ImageType.BACKDROP) == true) {
|
||||||
|
backdropService.submit(album)
|
||||||
|
} else {
|
||||||
|
val artistIds =
|
||||||
|
album.data.albumArtists
|
||||||
|
?.shuffled()
|
||||||
|
?.take(50)
|
||||||
|
?.map { it.id }
|
||||||
|
api.itemsApi
|
||||||
|
.getItems(
|
||||||
|
ids = artistIds,
|
||||||
|
imageTypes = listOf(ImageType.BACKDROP),
|
||||||
|
).content.items
|
||||||
|
.firstOrNull()
|
||||||
|
?.let { backdropService.submit(BaseItem(it, false)) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun setFavorite(
|
fun setFavorite(
|
||||||
itemId: UUID,
|
itemId: UUID,
|
||||||
favorite: Boolean,
|
favorite: Boolean,
|
||||||
|
|
@ -252,6 +263,11 @@ data class AlbumState(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private const val HEADER_ROW = 0
|
||||||
|
private const val SONG_ROW = HEADER_ROW + 1
|
||||||
|
private const val MUSIC_VIDEO_ROW = SONG_ROW + 1
|
||||||
|
private const val SIMILAR_ROW = MUSIC_VIDEO_ROW + 1
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AlbumDetailsPage(
|
fun AlbumDetailsPage(
|
||||||
itemId: UUID,
|
itemId: UUID,
|
||||||
|
|
@ -285,7 +301,6 @@ fun AlbumDetailsPage(
|
||||||
onClickRemoveFromQueue = {},
|
onClickRemoveFromQueue = {},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
when (val loading = state.loading) {
|
when (val loading = state.loading) {
|
||||||
is LoadingState.Error -> {
|
is LoadingState.Error -> {
|
||||||
ErrorMessage(loading, modifier)
|
ErrorMessage(loading, modifier)
|
||||||
|
|
@ -299,13 +314,24 @@ fun AlbumDetailsPage(
|
||||||
|
|
||||||
LoadingState.Success -> {
|
LoadingState.Success -> {
|
||||||
val album = state.album!!
|
val album = state.album!!
|
||||||
val focusRequester = remember { FocusRequester() }
|
var position by rememberPosition(0, 0)
|
||||||
|
|
||||||
val firstFocusRequester = remember { FocusRequester() }
|
val firstFocusRequester = remember { FocusRequester() }
|
||||||
val firstBringIntoViewRequester = remember { BringIntoViewRequester() }
|
val firstBringIntoViewRequester = remember { BringIntoViewRequester() }
|
||||||
LaunchedEffect(Unit) { focusRequester.requestFocus() }
|
|
||||||
val bringIntoViewRequester = remember { BringIntoViewRequester() }
|
val bringIntoViewRequester = remember { BringIntoViewRequester() }
|
||||||
val listState = rememberLazyListState()
|
val listState = rememberLazyListState()
|
||||||
val itemsBefore = 2
|
val itemsBefore = 2
|
||||||
|
val focusRequesters =
|
||||||
|
remember { List(SIMILAR_ROW + 1) { FocusRequester() } }
|
||||||
|
val songFocusRequester = remember { FocusRequester() }
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
if (position.row == SONG_ROW) {
|
||||||
|
songFocusRequester.tryRequestFocus()
|
||||||
|
} else {
|
||||||
|
focusRequesters.getOrNull(position.row)?.tryRequestFocus()
|
||||||
|
}
|
||||||
|
viewModel.updateBackDrop()
|
||||||
|
}
|
||||||
val backHandlerActive by remember {
|
val backHandlerActive by remember {
|
||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
listState.firstVisibleItemIndex > itemsBefore
|
listState.firstVisibleItemIndex > itemsBefore
|
||||||
|
|
@ -371,13 +397,16 @@ fun AlbumDetailsPage(
|
||||||
},
|
},
|
||||||
favorite = album.favorite,
|
favorite = album.favorite,
|
||||||
buttonOnFocusChanged = {
|
buttonOnFocusChanged = {
|
||||||
if (it.isFocused) scope.launch { bringIntoViewRequester.bringIntoView() }
|
if (it.isFocused) {
|
||||||
|
position = RowColumn(HEADER_ROW, 0)
|
||||||
|
scope.launch { bringIntoViewRequester.bringIntoView() }
|
||||||
|
}
|
||||||
},
|
},
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
.onFocusChanged {
|
.onFocusChanged {
|
||||||
if (it.hasFocus) scope.launch { bringIntoViewRequester.bringIntoView() }
|
if (it.hasFocus) scope.launch { bringIntoViewRequester.bringIntoView() }
|
||||||
}.focusRequester(focusRequester),
|
}.focusRequester(focusRequesters[HEADER_ROW]),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -396,7 +425,10 @@ fun AlbumDetailsPage(
|
||||||
) {
|
) {
|
||||||
SongListItem(
|
SongListItem(
|
||||||
song = song,
|
song = song,
|
||||||
onClick = { viewModel.play(false, index) },
|
onClick = {
|
||||||
|
position = RowColumn(SONG_ROW, index)
|
||||||
|
viewModel.play(false, index)
|
||||||
|
},
|
||||||
onLongClick = {
|
onLongClick = {
|
||||||
if (song != null) {
|
if (song != null) {
|
||||||
moreDialog =
|
moreDialog =
|
||||||
|
|
@ -424,6 +456,9 @@ fun AlbumDetailsPage(
|
||||||
Modifier
|
Modifier
|
||||||
.focusRequester(firstFocusRequester)
|
.focusRequester(firstFocusRequester)
|
||||||
.bringIntoViewRequester(firstBringIntoViewRequester),
|
.bringIntoViewRequester(firstBringIntoViewRequester),
|
||||||
|
).ifElse(
|
||||||
|
position.row == SONG_ROW && position.column == index,
|
||||||
|
Modifier.focusRequester(songFocusRequester),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -434,6 +469,7 @@ fun AlbumDetailsPage(
|
||||||
title = stringResource(R.string.music_videos),
|
title = stringResource(R.string.music_videos),
|
||||||
items = state.musicVideos,
|
items = state.musicVideos,
|
||||||
onClickItem = { index, item ->
|
onClickItem = { index, item ->
|
||||||
|
position = RowColumn(MUSIC_VIDEO_ROW, index)
|
||||||
viewModel.navigationManager.navigateTo(item.destination())
|
viewModel.navigationManager.navigateTo(item.destination())
|
||||||
},
|
},
|
||||||
onLongClickItem = { index, item ->
|
onLongClickItem = { index, item ->
|
||||||
|
|
@ -450,6 +486,7 @@ fun AlbumDetailsPage(
|
||||||
modifier = mod,
|
modifier = mod,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
modifier = Modifier.focusRequester(focusRequesters[MUSIC_VIDEO_ROW]),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -459,6 +496,7 @@ fun AlbumDetailsPage(
|
||||||
title = stringResource(R.string.more_like_this),
|
title = stringResource(R.string.more_like_this),
|
||||||
items = state.similar,
|
items = state.similar,
|
||||||
onClickItem = { index, item ->
|
onClickItem = { index, item ->
|
||||||
|
position = RowColumn(SIMILAR_ROW, index)
|
||||||
viewModel.navigationManager.navigateTo(item.destination())
|
viewModel.navigationManager.navigateTo(item.destination())
|
||||||
},
|
},
|
||||||
onLongClickItem = { index, item ->
|
onLongClickItem = { index, item ->
|
||||||
|
|
@ -475,6 +513,7 @@ fun AlbumDetailsPage(
|
||||||
modifier = mod,
|
modifier = mod,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
modifier = Modifier.focusRequester(focusRequesters[SIMILAR_ROW]),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -64,12 +64,16 @@ import com.github.damontecres.wholphin.ui.components.Optional
|
||||||
import com.github.damontecres.wholphin.ui.components.OverviewText
|
import com.github.damontecres.wholphin.ui.components.OverviewText
|
||||||
import com.github.damontecres.wholphin.ui.components.QuickDetails
|
import com.github.damontecres.wholphin.ui.components.QuickDetails
|
||||||
import com.github.damontecres.wholphin.ui.data.AddPlaylistViewModel
|
import com.github.damontecres.wholphin.ui.data.AddPlaylistViewModel
|
||||||
|
import com.github.damontecres.wholphin.ui.data.RowColumn
|
||||||
import com.github.damontecres.wholphin.ui.detail.PlaylistDialog
|
import com.github.damontecres.wholphin.ui.detail.PlaylistDialog
|
||||||
import com.github.damontecres.wholphin.ui.detail.PlaylistLoadingState
|
import com.github.damontecres.wholphin.ui.detail.PlaylistLoadingState
|
||||||
|
import com.github.damontecres.wholphin.ui.ifElse
|
||||||
import com.github.damontecres.wholphin.ui.launchDefault
|
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.letNotEmpty
|
import com.github.damontecres.wholphin.ui.letNotEmpty
|
||||||
|
import com.github.damontecres.wholphin.ui.rememberPosition
|
||||||
import com.github.damontecres.wholphin.ui.toBaseItems
|
import com.github.damontecres.wholphin.ui.toBaseItems
|
||||||
|
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
||||||
import com.github.damontecres.wholphin.util.ApiRequestPager
|
import com.github.damontecres.wholphin.util.ApiRequestPager
|
||||||
import com.github.damontecres.wholphin.util.ExceptionHandler
|
import com.github.damontecres.wholphin.util.ExceptionHandler
|
||||||
import com.github.damontecres.wholphin.util.GetItemsRequestHandler
|
import com.github.damontecres.wholphin.util.GetItemsRequestHandler
|
||||||
|
|
@ -270,6 +274,12 @@ data class ArtistState(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private const val HEADER_ROW = 0
|
||||||
|
private const val SONG_ROW = HEADER_ROW + 1
|
||||||
|
private const val ALBUM_ROW = SONG_ROW + 1
|
||||||
|
private const val MUSIC_VIDEO_ROW = ALBUM_ROW + 1
|
||||||
|
private const val SIMILAR_ROW = MUSIC_VIDEO_ROW + 1
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ArtistDetailsPage(
|
fun ArtistDetailsPage(
|
||||||
itemId: UUID,
|
itemId: UUID,
|
||||||
|
|
@ -316,10 +326,17 @@ fun ArtistDetailsPage(
|
||||||
}
|
}
|
||||||
|
|
||||||
LoadingState.Success -> {
|
LoadingState.Success -> {
|
||||||
|
var position by rememberPosition(0, 0)
|
||||||
val artist = state.artist!!
|
val artist = state.artist!!
|
||||||
val focusRequester = remember { FocusRequester() }
|
val focusRequesters =
|
||||||
|
remember { List(SIMILAR_ROW + 1) { FocusRequester() } }
|
||||||
|
val songFocusRequester = remember { FocusRequester() }
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
focusRequester.requestFocus()
|
if (position.row == SONG_ROW) {
|
||||||
|
songFocusRequester.tryRequestFocus()
|
||||||
|
} else {
|
||||||
|
focusRequesters.getOrNull(position.row)?.tryRequestFocus()
|
||||||
|
}
|
||||||
viewModel.init()
|
viewModel.init()
|
||||||
}
|
}
|
||||||
val bringIntoViewRequester = remember { BringIntoViewRequester() }
|
val bringIntoViewRequester = remember { BringIntoViewRequester() }
|
||||||
|
|
@ -378,13 +395,16 @@ fun ArtistDetailsPage(
|
||||||
},
|
},
|
||||||
favorite = artist.favorite,
|
favorite = artist.favorite,
|
||||||
buttonOnFocusChanged = {
|
buttonOnFocusChanged = {
|
||||||
if (it.isFocused) scope.launch { bringIntoViewRequester.bringIntoView() }
|
if (it.isFocused) {
|
||||||
|
position = RowColumn(HEADER_ROW, 0)
|
||||||
|
scope.launch { bringIntoViewRequester.bringIntoView() }
|
||||||
|
}
|
||||||
},
|
},
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
.onFocusChanged {
|
.onFocusChanged {
|
||||||
if (it.hasFocus) scope.launch { bringIntoViewRequester.bringIntoView() }
|
if (it.hasFocus) scope.launch { bringIntoViewRequester.bringIntoView() }
|
||||||
}.focusRequester(focusRequester),
|
}.focusRequester(focusRequesters[HEADER_ROW]),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -400,7 +420,10 @@ fun ArtistDetailsPage(
|
||||||
itemsIndexed(state.topSongs) { index, song ->
|
itemsIndexed(state.topSongs) { index, song ->
|
||||||
SongListItem(
|
SongListItem(
|
||||||
song = song,
|
song = song,
|
||||||
onClick = { song?.let { viewModel.play(it) } },
|
onClick = {
|
||||||
|
position = RowColumn(SONG_ROW, index)
|
||||||
|
song?.let { viewModel.play(it) }
|
||||||
|
},
|
||||||
onLongClick = {
|
onLongClick = {
|
||||||
if (song != null) {
|
if (song != null) {
|
||||||
moreDialog =
|
moreDialog =
|
||||||
|
|
@ -418,9 +441,15 @@ fun ArtistDetailsPage(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
modifier = Modifier.fillMaxWidth(.75f),
|
|
||||||
showArtist = false,
|
showArtist = false,
|
||||||
isPlaying = song != null && currentMusic.currentItemId == song.id,
|
isPlaying = song != null && currentMusic.currentItemId == song.id,
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.fillMaxWidth(.75f)
|
||||||
|
.ifElse(
|
||||||
|
position.row == SONG_ROW && position.column == index,
|
||||||
|
Modifier.focusRequester(songFocusRequester),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -429,6 +458,7 @@ fun ArtistDetailsPage(
|
||||||
title = stringResource(R.string.albums),
|
title = stringResource(R.string.albums),
|
||||||
items = state.albums,
|
items = state.albums,
|
||||||
onClickItem = { index, album ->
|
onClickItem = { index, album ->
|
||||||
|
position = RowColumn(ALBUM_ROW, index)
|
||||||
viewModel.navigationManager.navigateTo(album.destination())
|
viewModel.navigationManager.navigateTo(album.destination())
|
||||||
},
|
},
|
||||||
onLongClickItem = { index, album ->
|
onLongClickItem = { index, album ->
|
||||||
|
|
@ -444,6 +474,7 @@ fun ArtistDetailsPage(
|
||||||
aspectRatio = AspectRatios.SQUARE,
|
aspectRatio = AspectRatios.SQUARE,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
modifier = Modifier.focusRequester(focusRequesters[ALBUM_ROW]),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (state.musicVideos.isNotEmpty()) {
|
if (state.musicVideos.isNotEmpty()) {
|
||||||
|
|
@ -452,6 +483,7 @@ fun ArtistDetailsPage(
|
||||||
title = stringResource(R.string.music_videos),
|
title = stringResource(R.string.music_videos),
|
||||||
items = state.musicVideos,
|
items = state.musicVideos,
|
||||||
onClickItem = { index, item ->
|
onClickItem = { index, item ->
|
||||||
|
position = RowColumn(MUSIC_VIDEO_ROW, index)
|
||||||
viewModel.navigationManager.navigateTo(item.destination())
|
viewModel.navigationManager.navigateTo(item.destination())
|
||||||
},
|
},
|
||||||
onLongClickItem = { index, item ->
|
onLongClickItem = { index, item ->
|
||||||
|
|
@ -468,6 +500,7 @@ fun ArtistDetailsPage(
|
||||||
modifier = mod,
|
modifier = mod,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
modifier = Modifier.focusRequester(focusRequesters[MUSIC_VIDEO_ROW]),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -477,6 +510,7 @@ fun ArtistDetailsPage(
|
||||||
title = stringResource(R.string.more_like_this),
|
title = stringResource(R.string.more_like_this),
|
||||||
items = state.similar,
|
items = state.similar,
|
||||||
onClickItem = { index, item ->
|
onClickItem = { index, item ->
|
||||||
|
position = RowColumn(SIMILAR_ROW, index)
|
||||||
viewModel.navigationManager.navigateTo(item.destination())
|
viewModel.navigationManager.navigateTo(item.destination())
|
||||||
},
|
},
|
||||||
onLongClickItem = { index, item ->
|
onLongClickItem = { index, item ->
|
||||||
|
|
@ -493,6 +527,7 @@ fun ArtistDetailsPage(
|
||||||
modifier = mod,
|
modifier = mod,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
modifier = Modifier.focusRequester(focusRequesters[SIMILAR_ROW]),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue