mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 15:50:16 +02:00
Optimize recompositions on focus change (#1138)
<!-- By submitting this pull request, you acknowledge that you have read the [contributing guide](https://github.com/damontecres/Wholphin/blob/main/CONTRIBUTING.md, including the AI/LLM policy, and [developer's guide](https://github.com/damontecres/Wholphin/blob/main/DEVELOPMENT.md) --> ## Description <!-- Describe the changes in detail --> I've look at the app using Layout inspector to see if there are any recompositions that shouldn't be happening while navigating normally in the app. I've noticed two issues 1. Every BannerCard was being recomposed on every cursor move, which added a lot of overhead to home screen navigation 2. Tab row in the Series View was being recomposed while navigating across episodes in given season. ### Related issues <!-- If this is a new feature or a change, there must be a discussion in an issue first, reference the issue here --> <!-- If fixing a bug, reference the bug issue here, or describe the bug, including steps to reproduce --> Performance on the home screen is lacking, especially on low end devices, while this isn't a full fix, it will improve the navigation, as the app will be making significantly less work. Probably will fix https://github.com/damontecres/Wholphin/issues/1043 ### Testing <!-- Describe how this change was tested and on what device(s) --> Using Layout Inspector built-in recomposition counter and highlights it's easy to see components which recompose while they shouldn't. I've tested it on a 4K TV emulator on my M1 Macbook Pro ## Screenshots <!-- Please include screenshots if the PR alters any UI elements --> main: <img width="402" height="508" alt="image" src="https://github.com/user-attachments/assets/6aaac6af-50d6-4f21-b465-e78bccf7e985" /> my branch: <img width="401" height="563" alt="image" src="https://github.com/user-attachments/assets/1034c0e2-9a40-4a05-8a07-f30b2e10301d" /> same navigation, on the same content. (down, right x4 ## AI or LLM usage <!-- If you used any AI or LLM assistance, please list where in the code and how you tested it --> Gemini built in to Android Studio helped me identify non-stable parts of the code. It also helped me understand and implement fixes in the HomePage code. I've took the time to check manually that it didn't alter any behavior, and verified the fixes using Layout Inspector and recomposition counter
This commit is contained in:
parent
4ea21be430
commit
762a5e6dcc
5 changed files with 185 additions and 113 deletions
|
|
@ -19,6 +19,7 @@ import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberUpdatedState
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
|
@ -98,10 +99,15 @@ fun BannerCard(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var imageError by remember(imageUrl) { mutableStateOf(false) }
|
var imageError by remember(imageUrl) { mutableStateOf(false) }
|
||||||
|
|
||||||
|
// Stabilize callbacks to prevent AsyncImage from recomposing
|
||||||
|
val currentOnClick by rememberUpdatedState(onClick)
|
||||||
|
val currentOnLongClick by rememberUpdatedState(onLongClick)
|
||||||
|
|
||||||
Card(
|
Card(
|
||||||
modifier = modifier.size(cardHeight * aspectRatio, cardHeight),
|
modifier = modifier.size(cardHeight * aspectRatio, cardHeight),
|
||||||
onClick = onClick,
|
onClick = { currentOnClick() },
|
||||||
onLongClick = onLongClick,
|
onLongClick = { currentOnLongClick() },
|
||||||
interactionSource = interactionSource,
|
interactionSource = interactionSource,
|
||||||
colors =
|
colors =
|
||||||
CardDefaults.colors(
|
CardDefaults.colors(
|
||||||
|
|
@ -119,7 +125,7 @@ fun BannerCard(
|
||||||
model = imageUrl,
|
model = imageUrl,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
contentScale = imageContentScale,
|
contentScale = imageContentScale,
|
||||||
onError = { imageError = true },
|
onError = remember { { imageError = true } },
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberUpdatedState
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
|
|
@ -45,6 +46,10 @@ fun <T> ItemRow(
|
||||||
val firstFocus = remember { FocusRequester() }
|
val firstFocus = remember { FocusRequester() }
|
||||||
val focusRequester = remember { FocusRequester() }
|
val focusRequester = remember { FocusRequester() }
|
||||||
var position by rememberInt()
|
var position by rememberInt()
|
||||||
|
|
||||||
|
val currentOnClickItem by rememberUpdatedState(onClickItem)
|
||||||
|
val currentOnLongClickItem by rememberUpdatedState(onLongClickItem)
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
modifier =
|
modifier =
|
||||||
|
|
@ -73,23 +78,36 @@ fun <T> ItemRow(
|
||||||
) {
|
) {
|
||||||
itemsIndexed(items) { index, item ->
|
itemsIndexed(items) { index, item ->
|
||||||
val cardModifier =
|
val cardModifier =
|
||||||
|
remember(index, position) {
|
||||||
if (index == position) {
|
if (index == position) {
|
||||||
Modifier.focusRequester(firstFocus)
|
Modifier.focusRequester(firstFocus)
|
||||||
} else {
|
} else {
|
||||||
Modifier
|
Modifier
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val onClick =
|
||||||
|
remember(index, item) {
|
||||||
|
{
|
||||||
|
position = index
|
||||||
|
if (item != null) currentOnClickItem(index, item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val onLongClick =
|
||||||
|
remember(index, item) {
|
||||||
|
{
|
||||||
|
position = index
|
||||||
|
if (item != null) currentOnLongClickItem(index, item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
cardContent.invoke(
|
cardContent.invoke(
|
||||||
index,
|
index,
|
||||||
item,
|
item,
|
||||||
cardModifier,
|
cardModifier,
|
||||||
{
|
onClick,
|
||||||
position = index
|
onLongClick,
|
||||||
if (item != null) onClickItem.invoke(index, item)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
position = index
|
|
||||||
if (item != null) onLongClickItem.invoke(index, item)
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,9 +19,9 @@ import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableIntStateOf
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberUpdatedState
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
|
@ -40,7 +40,6 @@ import androidx.compose.ui.unit.sp
|
||||||
import androidx.tv.material3.MaterialTheme
|
import androidx.tv.material3.MaterialTheme
|
||||||
import androidx.tv.material3.Text
|
import androidx.tv.material3.Text
|
||||||
import com.github.damontecres.wholphin.ui.PreviewTvSpec
|
import com.github.damontecres.wholphin.ui.PreviewTvSpec
|
||||||
import com.github.damontecres.wholphin.ui.ifElse
|
|
||||||
import com.github.damontecres.wholphin.ui.theme.WholphinTheme
|
import com.github.damontecres.wholphin.ui.theme.WholphinTheme
|
||||||
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
@ -60,6 +59,11 @@ fun TabRow(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var rowHasFocus by remember { mutableStateOf(false) }
|
var rowHasFocus by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
val currentSelectedTabIndex by rememberUpdatedState(selectedTabIndex)
|
||||||
|
val currentFocusRequesters by rememberUpdatedState(focusRequesters)
|
||||||
|
val currentOnClick by rememberUpdatedState(onClick)
|
||||||
|
|
||||||
LazyRow(
|
LazyRow(
|
||||||
state = state,
|
state = state,
|
||||||
modifier =
|
modifier =
|
||||||
|
|
@ -71,14 +75,16 @@ fun TabRow(
|
||||||
onEnter = {
|
onEnter = {
|
||||||
// If entering from left or right, use last or first tab
|
// If entering from left or right, use last or first tab
|
||||||
// Otherwise use the selected tab
|
// Otherwise use the selected tab
|
||||||
Timber.v("onEnter requestedFocusDirection=$requestedFocusDirection, selectedTabIndex=$selectedTabIndex")
|
val index = currentSelectedTabIndex
|
||||||
|
val requesters = currentFocusRequesters
|
||||||
|
Timber.v("onEnter requestedFocusDirection=$requestedFocusDirection, selectedTabIndex=$index")
|
||||||
val focusRequester =
|
val focusRequester =
|
||||||
if (requestedFocusDirection == FocusDirection.Left) {
|
if (requestedFocusDirection == FocusDirection.Left) {
|
||||||
focusRequesters.lastOrNull()
|
requesters.lastOrNull()
|
||||||
} else if (requestedFocusDirection == FocusDirection.Right) {
|
} else if (requestedFocusDirection == FocusDirection.Right) {
|
||||||
focusRequesters.firstOrNull()
|
requesters.firstOrNull()
|
||||||
} else {
|
} else {
|
||||||
focusRequesters.getOrNull(selectedTabIndex)
|
requesters.getOrNull(index)
|
||||||
}
|
}
|
||||||
(focusRequester ?: FocusRequester.Default).tryRequestFocus()
|
(focusRequester ?: FocusRequester.Default).tryRequestFocus()
|
||||||
}
|
}
|
||||||
|
|
@ -86,15 +92,19 @@ fun TabRow(
|
||||||
) {
|
) {
|
||||||
itemsIndexed(tabs) { index, tabTitle ->
|
itemsIndexed(tabs) { index, tabTitle ->
|
||||||
val interactionSource = remember { MutableInteractionSource() }
|
val interactionSource = remember { MutableInteractionSource() }
|
||||||
|
val onTabClick =
|
||||||
|
remember(index) {
|
||||||
|
{
|
||||||
|
currentOnClick(index)
|
||||||
|
}
|
||||||
|
}
|
||||||
Tab(
|
Tab(
|
||||||
title = tabTitle,
|
title = tabTitle,
|
||||||
selected = index == selectedTabIndex,
|
selected = index == selectedTabIndex,
|
||||||
rowActive = rowHasFocus,
|
rowActive = rowHasFocus,
|
||||||
interactionSource = interactionSource,
|
interactionSource = interactionSource,
|
||||||
onClick = {
|
onClick = onTabClick,
|
||||||
onClick.invoke(index)
|
modifier = Modifier.focusRequester(focusRequesters.getOrElse(index) { FocusRequester() }),
|
||||||
},
|
|
||||||
modifier = Modifier.focusRequester(focusRequesters[index]),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ import androidx.compose.runtime.mutableIntStateOf
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.runtime.rememberUpdatedState
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
|
@ -95,7 +96,7 @@ fun SeriesOverviewContent(
|
||||||
) {
|
) {
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
val bringIntoViewRequester = remember { BringIntoViewRequester() }
|
val bringIntoViewRequester = remember { BringIntoViewRequester() }
|
||||||
var selectedTabIndex by rememberSaveable(position) { mutableIntStateOf(position.seasonTabIndex) }
|
var selectedTabIndex by rememberSaveable(position.seasonTabIndex) { mutableIntStateOf(position.seasonTabIndex) }
|
||||||
LaunchedEffect(selectedTabIndex) {
|
LaunchedEffect(selectedTabIndex) {
|
||||||
logTab("series_overview", selectedTabIndex)
|
logTab("series_overview", selectedTabIndex)
|
||||||
}
|
}
|
||||||
|
|
@ -120,6 +121,8 @@ fun SeriesOverviewContent(
|
||||||
}
|
}
|
||||||
val focusRequesters = remember(seasons) { List(seasons.size) { FocusRequester() } }
|
val focusRequesters = remember(seasons) { List(seasons.size) { FocusRequester() } }
|
||||||
|
|
||||||
|
val currentOnChangeSeason by rememberUpdatedState(onChangeSeason)
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier =
|
modifier =
|
||||||
modifier
|
modifier
|
||||||
|
|
@ -154,10 +157,13 @@ fun SeriesOverviewContent(
|
||||||
TabRow(
|
TabRow(
|
||||||
selectedTabIndex = selectedTabIndex,
|
selectedTabIndex = selectedTabIndex,
|
||||||
tabs = tabs,
|
tabs = tabs,
|
||||||
onClick = {
|
onClick =
|
||||||
|
remember {
|
||||||
|
{
|
||||||
selectedTabIndex = it
|
selectedTabIndex = it
|
||||||
onChangeSeason.invoke(it)
|
currentOnChangeSeason(it)
|
||||||
requestFocusAfterSeason = true
|
requestFocusAfterSeason = true
|
||||||
|
}
|
||||||
},
|
},
|
||||||
focusRequesters = focusRequesters,
|
focusRequesters = focusRequesters,
|
||||||
modifier =
|
modifier =
|
||||||
|
|
|
||||||
|
|
@ -22,10 +22,12 @@ import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberUpdatedState
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
|
@ -122,15 +124,18 @@ fun HomePage(
|
||||||
var showDeleteDialog by remember { mutableStateOf<RowColumnItem?>(null) }
|
var showDeleteDialog by remember { mutableStateOf<RowColumnItem?>(null) }
|
||||||
val playlistState by playlistViewModel.playlistState.observeAsState(PlaylistLoadingState.Pending)
|
val playlistState by playlistViewModel.playlistState.observeAsState(PlaylistLoadingState.Pending)
|
||||||
var position by rememberPosition()
|
var position by rememberPosition()
|
||||||
HomePageContent(
|
|
||||||
homeRows = homeRows,
|
val onFocusPosition = remember { { it: RowColumn -> position = it } }
|
||||||
position = position,
|
val onClickItem =
|
||||||
onFocusPosition = { position = it },
|
remember {
|
||||||
onClickItem = { clickedPosition, item ->
|
{ clickedPosition: RowColumn, item: BaseItem ->
|
||||||
position = clickedPosition
|
position = clickedPosition
|
||||||
viewModel.navigationManager.navigateTo(item.destination())
|
viewModel.navigationManager.navigateTo(item.destination())
|
||||||
},
|
}
|
||||||
onLongClickItem = { clickedPosition, item ->
|
}
|
||||||
|
val onLongClickItem =
|
||||||
|
remember {
|
||||||
|
{ clickedPosition: RowColumn, item: BaseItem ->
|
||||||
position = clickedPosition
|
position = clickedPosition
|
||||||
val dialogItems =
|
val dialogItems =
|
||||||
buildMoreDialogItemsForHome(
|
buildMoreDialogItemsForHome(
|
||||||
|
|
@ -166,10 +171,22 @@ fun HomePage(
|
||||||
fromLongClick = true,
|
fromLongClick = true,
|
||||||
items = dialogItems,
|
items = dialogItems,
|
||||||
)
|
)
|
||||||
},
|
}
|
||||||
onClickPlay = { _, item ->
|
}
|
||||||
|
val onClickPlay =
|
||||||
|
remember {
|
||||||
|
{ _: RowColumn, item: BaseItem ->
|
||||||
viewModel.navigationManager.navigateTo(Destination.Playback(item))
|
viewModel.navigationManager.navigateTo(Destination.Playback(item))
|
||||||
},
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HomePageContent(
|
||||||
|
homeRows = homeRows,
|
||||||
|
position = position,
|
||||||
|
onFocusPosition = onFocusPosition,
|
||||||
|
onClickItem = onClickItem,
|
||||||
|
onLongClickItem = onLongClickItem,
|
||||||
|
onClickPlay = onClickPlay,
|
||||||
loadingState = refreshing,
|
loadingState = refreshing,
|
||||||
showClock = preferences.appPreferences.interfacePreferences.showClock,
|
showClock = preferences.appPreferences.interfacePreferences.showClock,
|
||||||
onUpdateBackdrop = viewModel::updateBackdrop,
|
onUpdateBackdrop = viewModel::updateBackdrop,
|
||||||
|
|
@ -210,6 +227,8 @@ fun HomePage(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
else -> {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -239,12 +258,17 @@ fun HomePageContent(
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
val focusedItem =
|
val focusedItem =
|
||||||
position.let {
|
remember(homeRows, position) {
|
||||||
(homeRows.getOrNull(it.row) as? HomeRowLoadingState.Success)?.items?.getOrNull(it.column)
|
(homeRows.getOrNull(position.row) as? HomeRowLoadingState.Success)?.items?.getOrNull(position.column)
|
||||||
}
|
}
|
||||||
|
|
||||||
val rowFocusRequesters = remember(homeRows) { List(homeRows.size) { FocusRequester() } }
|
val rowFocusRequesters = remember(homeRows.size) { List(homeRows.size) { FocusRequester() } }
|
||||||
var firstFocused by remember { mutableStateOf(false) }
|
var firstFocused by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
val currentPosition by rememberUpdatedState(position)
|
||||||
|
val currentOnFocusPosition by rememberUpdatedState(onFocusPosition)
|
||||||
|
val currentOnClickPlay by rememberUpdatedState(onClickPlay)
|
||||||
|
|
||||||
if (takeFocus) {
|
if (takeFocus) {
|
||||||
LaunchedEffect(homeRows) {
|
LaunchedEffect(homeRows) {
|
||||||
if (!firstFocused && homeRows.isNotEmpty()) {
|
if (!firstFocused && homeRows.isNotEmpty()) {
|
||||||
|
|
@ -266,11 +290,6 @@ fun HomePageContent(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LaunchedEffect(position) {
|
|
||||||
if (position.row >= 0) {
|
|
||||||
listState.animateScrollToItem(position.row)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
LaunchedEffect(onUpdateBackdrop, focusedItem) {
|
LaunchedEffect(onUpdateBackdrop, focusedItem) {
|
||||||
focusedItem?.let { onUpdateBackdrop.invoke(it) }
|
focusedItem?.let { onUpdateBackdrop.invoke(it) }
|
||||||
}
|
}
|
||||||
|
|
@ -280,10 +299,12 @@ fun HomePageContent(
|
||||||
|
|
||||||
val density = LocalDensity.current
|
val density = LocalDensity.current
|
||||||
val spaceAbovePx =
|
val spaceAbovePx =
|
||||||
|
remember(density) {
|
||||||
with(density) {
|
with(density) {
|
||||||
// The size of the row titles & spacing
|
// The size of the row titles & spacing
|
||||||
50.dp.toPx()
|
50.dp.toPx()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
val defaultBringIntoViewSpec = LocalBringIntoViewSpec.current
|
val defaultBringIntoViewSpec = LocalBringIntoViewSpec.current
|
||||||
CompositionLocalProvider(
|
CompositionLocalProvider(
|
||||||
LocalBringIntoViewSpec provides ScrollToTopBringIntoViewSpec(spaceAbovePx),
|
LocalBringIntoViewSpec provides ScrollToTopBringIntoViewSpec(spaceAbovePx),
|
||||||
|
|
@ -329,14 +350,20 @@ fun HomePageContent(
|
||||||
ItemRow(
|
ItemRow(
|
||||||
title = row.title,
|
title = row.title,
|
||||||
items = row.items,
|
items = row.items,
|
||||||
onClickItem = { index, item ->
|
onClickItem =
|
||||||
|
remember(rowIndex, onClickItem) {
|
||||||
|
{ index, item ->
|
||||||
onClickItem.invoke(RowColumn(rowIndex, index), item)
|
onClickItem.invoke(RowColumn(rowIndex, index), item)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
onLongClickItem = { index, item ->
|
onLongClickItem =
|
||||||
|
remember(rowIndex, onLongClickItem) {
|
||||||
|
{ index, item ->
|
||||||
onLongClickItem.invoke(
|
onLongClickItem.invoke(
|
||||||
RowColumn(rowIndex, index),
|
RowColumn(rowIndex, index),
|
||||||
item,
|
item,
|
||||||
)
|
)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
|
|
@ -346,6 +373,26 @@ fun HomePageContent(
|
||||||
.animateItem(),
|
.animateItem(),
|
||||||
horizontalPadding = viewOptions.spacing.dp,
|
horizontalPadding = viewOptions.spacing.dp,
|
||||||
cardContent = { index, item, cardModifier, onClick, onLongClick ->
|
cardContent = { index, item, cardModifier, onClick, onLongClick ->
|
||||||
|
val onFocus =
|
||||||
|
remember(rowIndex, index) {
|
||||||
|
{ isFocused: Boolean ->
|
||||||
|
if (isFocused) {
|
||||||
|
currentOnFocusPosition(RowColumn(rowIndex, index))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val onKey =
|
||||||
|
remember(item) {
|
||||||
|
{ event: androidx.compose.ui.input.key.KeyEvent ->
|
||||||
|
if (isPlayKeyUp(event) && item?.type?.playable == true) {
|
||||||
|
Timber.v("Clicked play on ${item.id}")
|
||||||
|
currentOnClickPlay(currentPosition, item)
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
HomePageCardContent(
|
HomePageCardContent(
|
||||||
index = index,
|
index = index,
|
||||||
item = item,
|
item = item,
|
||||||
|
|
@ -354,23 +401,8 @@ fun HomePageContent(
|
||||||
viewOptions = viewOptions,
|
viewOptions = viewOptions,
|
||||||
modifier =
|
modifier =
|
||||||
cardModifier
|
cardModifier
|
||||||
.onFocusChanged {
|
.onFocusChanged { onFocus(it.isFocused) }
|
||||||
if (it.isFocused) {
|
.onKeyEvent { onKey(it) },
|
||||||
onFocusPosition?.invoke(
|
|
||||||
RowColumn(rowIndex, index),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}.onKeyEvent {
|
|
||||||
if (isPlayKeyUp(it) && item?.type?.playable == true) {
|
|
||||||
Timber.v("Clicked play on ${item.id}")
|
|
||||||
onClickPlay.invoke(
|
|
||||||
position,
|
|
||||||
item,
|
|
||||||
)
|
|
||||||
return@onKeyEvent true
|
|
||||||
}
|
|
||||||
return@onKeyEvent false
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue