mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-09 08:01:20 +02:00
Focus improvements
This commit is contained in:
parent
43fde88695
commit
7e53f95054
4 changed files with 84 additions and 36 deletions
|
|
@ -11,7 +11,6 @@ import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.material3.HorizontalDivider
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
|
|
@ -24,7 +23,6 @@ import androidx.tv.material3.MaterialTheme
|
||||||
import androidx.tv.material3.Text
|
import androidx.tv.material3.Text
|
||||||
import com.github.damontecres.wholphin.R
|
import com.github.damontecres.wholphin.R
|
||||||
import com.github.damontecres.wholphin.ui.ifElse
|
import com.github.damontecres.wholphin.ui.ifElse
|
||||||
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun HomeSettingsAddRow(
|
fun HomeSettingsAddRow(
|
||||||
|
|
@ -35,7 +33,7 @@ fun HomeSettingsAddRow(
|
||||||
modifier: Modifier,
|
modifier: Modifier,
|
||||||
firstFocus: FocusRequester = remember { FocusRequester() },
|
firstFocus: FocusRequester = remember { FocusRequester() },
|
||||||
) {
|
) {
|
||||||
LaunchedEffect(Unit) { firstFocus.tryRequestFocus() }
|
// LaunchedEffect(Unit) { firstFocus.tryRequestFocus() }
|
||||||
Column(modifier = modifier) {
|
Column(modifier = modifier) {
|
||||||
Text(
|
Text(
|
||||||
text = "Add row for...",
|
text = "Add row for...",
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
|
import androidx.compose.ui.focus.focusRequester
|
||||||
import androidx.compose.ui.focus.focusRestorer
|
import androidx.compose.ui.focus.focusRestorer
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
|
@ -65,7 +66,7 @@ fun HomeSettingsGlobal(
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
onClick = { onClickResize.invoke(1) },
|
onClick = { onClickResize.invoke(1) },
|
||||||
modifier = Modifier,
|
modifier = Modifier.focusRequester(firstFocus),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.heightIn
|
||||||
import androidx.compose.foundation.layout.wrapContentWidth
|
import androidx.compose.foundation.layout.wrapContentWidth
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Add
|
import androidx.compose.material.icons.filled.Add
|
||||||
import androidx.compose.material.icons.filled.Delete
|
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.material3.HorizontalDivider
|
||||||
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.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
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
|
||||||
import androidx.compose.ui.focus.FocusDirection
|
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.services.HomeRowConfigDisplay
|
||||||
import com.github.damontecres.wholphin.ui.FontAwesome
|
import com.github.damontecres.wholphin.ui.FontAwesome
|
||||||
import com.github.damontecres.wholphin.ui.components.Button
|
import com.github.damontecres.wholphin.ui.components.Button
|
||||||
|
import com.github.damontecres.wholphin.ui.rememberInt
|
||||||
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
enum class MoveDirection {
|
enum class MoveDirection {
|
||||||
UP,
|
UP,
|
||||||
|
|
@ -55,7 +61,16 @@ fun HomeSettingsRowList(
|
||||||
firstFocus: FocusRequester = remember { FocusRequester() },
|
firstFocus: FocusRequester = remember { FocusRequester() },
|
||||||
) {
|
) {
|
||||||
val focusManager = LocalFocusManager.current
|
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) {
|
Column(modifier = modifier) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.customize_home),
|
text = stringResource(R.string.customize_home),
|
||||||
|
|
@ -64,6 +79,7 @@ fun HomeSettingsRowList(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
)
|
)
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
|
state = listState,
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
modifier =
|
modifier =
|
||||||
modifier
|
modifier
|
||||||
|
|
@ -84,8 +100,11 @@ fun HomeSettingsRowList(
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
onClick = onClickAdd,
|
onClick = {
|
||||||
modifier = Modifier.focusRequester(firstFocus),
|
position = 0
|
||||||
|
onClickAdd.invoke()
|
||||||
|
},
|
||||||
|
modifier = Modifier.focusRequester(focusRequesters[0]),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
|
|
@ -102,8 +121,11 @@ fun HomeSettingsRowList(
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
onClick = onClickSettings,
|
onClick = {
|
||||||
modifier = Modifier,
|
position = 1
|
||||||
|
onClickSettings.invoke()
|
||||||
|
},
|
||||||
|
modifier = Modifier.focusRequester(focusRequesters[1]),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
|
|
@ -121,7 +143,18 @@ fun HomeSettingsRowList(
|
||||||
moveUpAllowed = index > 0,
|
moveUpAllowed = index > 0,
|
||||||
moveDownAllowed = index != state.rows.lastIndex,
|
moveDownAllowed = index != state.rows.lastIndex,
|
||||||
deleteAllowed = state.rows.size > 1,
|
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 = {
|
onClickDelete = {
|
||||||
if (index != state.rows.lastIndex) {
|
if (index != state.rows.lastIndex) {
|
||||||
focusManager.moveFocus(FocusDirection.Down)
|
focusManager.moveFocus(FocusDirection.Down)
|
||||||
|
|
@ -130,10 +163,15 @@ fun HomeSettingsRowList(
|
||||||
}
|
}
|
||||||
onClickDelete.invoke(index)
|
onClickDelete.invoke(index)
|
||||||
},
|
},
|
||||||
onClick = { onClick.invoke(index, row) },
|
onClick = {
|
||||||
|
position = itemsBeforeRows + index
|
||||||
|
onClick.invoke(index, row)
|
||||||
|
},
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxWidth(),
|
.fillMaxWidth()
|
||||||
|
.animateItem()
|
||||||
|
.focusRequester(focusRequesters[itemsBeforeRows + index]),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,11 +30,16 @@ import com.github.damontecres.wholphin.util.LoadingState
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.async
|
||||||
|
import kotlinx.coroutines.awaitAll
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
|
import kotlinx.coroutines.sync.Semaphore
|
||||||
|
import kotlinx.coroutines.sync.withPermit
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||||
import org.jellyfin.sdk.model.api.CollectionType
|
import org.jellyfin.sdk.model.api.CollectionType
|
||||||
|
|
@ -95,14 +100,17 @@ class HomeSettingsViewModel
|
||||||
|
|
||||||
private suspend fun fetchRowData() {
|
private suspend fun fetchRowData() {
|
||||||
val limit = 6
|
val limit = 6
|
||||||
|
val semaphore = Semaphore(4)
|
||||||
val rows =
|
val rows =
|
||||||
serverRepository.currentUserDto.value?.let { userDto ->
|
serverRepository.currentUserDto.value?.let { userDto ->
|
||||||
val prefs = userPreferencesService.getCurrent().appPreferences.homePagePreferences
|
val prefs = userPreferencesService.getCurrent().appPreferences.homePagePreferences
|
||||||
state.value.let { state ->
|
state.value
|
||||||
|
.let { state ->
|
||||||
state.rows
|
state.rows
|
||||||
.map { it.config }
|
.map { it.config }
|
||||||
.map { row ->
|
.map { row ->
|
||||||
// TODO parallelize
|
viewModelScope.async(Dispatchers.IO) {
|
||||||
|
semaphore.withPermit {
|
||||||
homeSettingsService.fetchDataForRow(
|
homeSettingsService.fetchDataForRow(
|
||||||
row = row,
|
row = row,
|
||||||
scope = viewModelScope,
|
scope = viewModelScope,
|
||||||
|
|
@ -114,6 +122,8 @@ class HomeSettingsViewModel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}.awaitAll()
|
||||||
|
}
|
||||||
rows?.let { rows ->
|
rows?.let { rows ->
|
||||||
rows
|
rows
|
||||||
.firstOrNull { it is HomeRowLoadingState.Success && it.items.isNotEmpty() }
|
.firstOrNull { it is HomeRowLoadingState.Success && it.items.isNotEmpty() }
|
||||||
|
|
@ -152,16 +162,17 @@ class HomeSettingsViewModel
|
||||||
direction: MoveDirection,
|
direction: MoveDirection,
|
||||||
index: Int,
|
index: Int,
|
||||||
) {
|
) {
|
||||||
|
viewModelScope.launchIO {
|
||||||
updateState {
|
updateState {
|
||||||
val rows = it.rows.move(direction, index)
|
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)
|
||||||
// val rowData = it.rowData.move(direction, index)
|
|
||||||
it.copy(
|
it.copy(
|
||||||
loading = LoadingState.Loading,
|
|
||||||
rows = rows,
|
rows = rows,
|
||||||
|
rowData = rowData,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
viewModelScope.launchIO { fetchRowData() }
|
}
|
||||||
|
// viewModelScope.launchIO { fetchRowData() }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun deleteRow(index: Int) {
|
fun deleteRow(index: Int) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue