From 7e53f9505479ebfed021782cc41bb199bfc4f743 Mon Sep 17 00:00:00 2001 From: Damontecres Date: Thu, 29 Jan 2026 18:13:32 -0500 Subject: [PATCH] Focus improvements --- .../ui/main/settings/HomeSettingsAddRow.kt | 4 +- .../ui/main/settings/HomeSettingsGlobal.kt | 3 +- .../ui/main/settings/HomeSettingsRowList.kt | 54 ++++++++++++++--- .../ui/main/settings/HomeSettingsViewModel.kt | 59 +++++++++++-------- 4 files changed, 84 insertions(+), 36 deletions(-) diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/main/settings/HomeSettingsAddRow.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/main/settings/HomeSettingsAddRow.kt index 11f0ba0a..d805c4be 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/main/settings/HomeSettingsAddRow.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/main/settings/HomeSettingsAddRow.kt @@ -11,7 +11,6 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.material3.HorizontalDivider import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester @@ -24,7 +23,6 @@ import androidx.tv.material3.MaterialTheme import androidx.tv.material3.Text import com.github.damontecres.wholphin.R import com.github.damontecres.wholphin.ui.ifElse -import com.github.damontecres.wholphin.ui.tryRequestFocus @Composable fun HomeSettingsAddRow( @@ -35,7 +33,7 @@ fun HomeSettingsAddRow( modifier: Modifier, firstFocus: FocusRequester = remember { FocusRequester() }, ) { - LaunchedEffect(Unit) { firstFocus.tryRequestFocus() } +// LaunchedEffect(Unit) { firstFocus.tryRequestFocus() } Column(modifier = modifier) { Text( text = "Add row for...", diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/main/settings/HomeSettingsGlobal.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/main/settings/HomeSettingsGlobal.kt index 309d7d88..3c619e4e 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/main/settings/HomeSettingsGlobal.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/main/settings/HomeSettingsGlobal.kt @@ -14,6 +14,7 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.focusRestorer import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign @@ -65,7 +66,7 @@ fun HomeSettingsGlobal( ) }, onClick = { onClickResize.invoke(1) }, - modifier = Modifier, + modifier = Modifier.focusRequester(firstFocus), ) } item { diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/main/settings/HomeSettingsRowList.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/main/settings/HomeSettingsRowList.kt index 59199eca..3a1da2fd 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/main/settings/HomeSettingsRowList.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/main/settings/HomeSettingsRowList.kt @@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.wrapContentWidth import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Delete @@ -17,7 +18,10 @@ import androidx.compose.material.icons.filled.Settings import androidx.compose.material3.HorizontalDivider import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusDirection @@ -36,7 +40,9 @@ import com.github.damontecres.wholphin.R import com.github.damontecres.wholphin.services.HomeRowConfigDisplay import com.github.damontecres.wholphin.ui.FontAwesome import com.github.damontecres.wholphin.ui.components.Button +import com.github.damontecres.wholphin.ui.rememberInt import com.github.damontecres.wholphin.ui.tryRequestFocus +import kotlinx.coroutines.launch enum class MoveDirection { UP, @@ -55,7 +61,16 @@ fun HomeSettingsRowList( firstFocus: FocusRequester = remember { FocusRequester() }, ) { val focusManager = LocalFocusManager.current - LaunchedEffect(Unit) { firstFocus.tryRequestFocus() } + val scope = rememberCoroutineScope() + val listState = rememberLazyListState() + + val itemsBeforeRows = 3 + val focusRequesters = + remember(state.rows.size) { List(itemsBeforeRows + state.rows.size) { FocusRequester() } } + + var position by rememberInt(0) + + LaunchedEffect(Unit) { focusRequesters.getOrNull(position)?.tryRequestFocus() } Column(modifier = modifier) { Text( text = stringResource(R.string.customize_home), @@ -64,6 +79,7 @@ fun HomeSettingsRowList( modifier = Modifier.fillMaxWidth(), ) LazyColumn( + state = listState, verticalArrangement = Arrangement.spacedBy(8.dp), modifier = modifier @@ -84,8 +100,11 @@ fun HomeSettingsRowList( contentDescription = null, ) }, - onClick = onClickAdd, - modifier = Modifier.focusRequester(firstFocus), + onClick = { + position = 0 + onClickAdd.invoke() + }, + modifier = Modifier.focusRequester(focusRequesters[0]), ) } item { @@ -102,8 +121,11 @@ fun HomeSettingsRowList( contentDescription = null, ) }, - onClick = onClickSettings, - modifier = Modifier, + onClick = { + position = 1 + onClickSettings.invoke() + }, + modifier = Modifier.focusRequester(focusRequesters[1]), ) } item { @@ -121,7 +143,18 @@ fun HomeSettingsRowList( moveUpAllowed = index > 0, moveDownAllowed = index != state.rows.lastIndex, deleteAllowed = state.rows.size > 1, - onClickMove = { onClickMove.invoke(it, index) }, + onClickMove = { + onClickMove.invoke(it, index) + scope.launch { + val scrollIndex = + itemsBeforeRows + if (it == MoveDirection.UP) index - 1 else index + 1 + if (scrollIndex < listState.firstVisibleItemIndex || + scrollIndex > listState.layoutInfo.visibleItemsInfo.lastIndex + ) { + listState.animateScrollToItem(scrollIndex) + } + } + }, onClickDelete = { if (index != state.rows.lastIndex) { focusManager.moveFocus(FocusDirection.Down) @@ -130,10 +163,15 @@ fun HomeSettingsRowList( } onClickDelete.invoke(index) }, - onClick = { onClick.invoke(index, row) }, + onClick = { + position = itemsBeforeRows + index + onClick.invoke(index, row) + }, modifier = Modifier - .fillMaxWidth(), + .fillMaxWidth() + .animateItem() + .focusRequester(focusRequesters[itemsBeforeRows + index]), ) } } diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/main/settings/HomeSettingsViewModel.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/main/settings/HomeSettingsViewModel.kt index a4f4a338..12db7332 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/main/settings/HomeSettingsViewModel.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/main/settings/HomeSettingsViewModel.kt @@ -30,11 +30,16 @@ import com.github.damontecres.wholphin.util.LoadingState import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.update +import kotlinx.coroutines.sync.Semaphore +import kotlinx.coroutines.sync.withPermit import kotlinx.serialization.Serializable import org.jellyfin.sdk.model.api.BaseItemKind import org.jellyfin.sdk.model.api.CollectionType @@ -95,24 +100,29 @@ class HomeSettingsViewModel private suspend fun fetchRowData() { val limit = 6 + val semaphore = Semaphore(4) val rows = serverRepository.currentUserDto.value?.let { userDto -> val prefs = userPreferencesService.getCurrent().appPreferences.homePagePreferences - state.value.let { state -> - state.rows - .map { it.config } - .map { row -> - // TODO parallelize - homeSettingsService.fetchDataForRow( - row = row, - scope = viewModelScope, - prefs = prefs, - userDto = userDto, - libraries = state.libraries, - limit = limit, - ) - } - } + state.value + .let { state -> + state.rows + .map { it.config } + .map { row -> + viewModelScope.async(Dispatchers.IO) { + semaphore.withPermit { + homeSettingsService.fetchDataForRow( + row = row, + scope = viewModelScope, + prefs = prefs, + userDto = userDto, + libraries = state.libraries, + limit = limit, + ) + } + } + } + }.awaitAll() } rows?.let { rows -> rows @@ -152,16 +162,17 @@ class HomeSettingsViewModel direction: MoveDirection, index: Int, ) { - updateState { - val rows = it.rows.move(direction, index) - // TODO would be more efficient to move rowData, but uncombined continue watching is two rows -// val rowData = it.rowData.move(direction, index) - it.copy( - loading = LoadingState.Loading, - rows = rows, - ) + viewModelScope.launchIO { + updateState { + val rows = it.rows.move(direction, index) + val rowData = it.rowData.move(direction, index) + it.copy( + rows = rows, + rowData = rowData, + ) + } } - viewModelScope.launchIO { fetchRowData() } +// viewModelScope.launchIO { fetchRowData() } } fun deleteRow(index: Int) {