Fix some focus issues & document it

This commit is contained in:
Damontecres 2025-10-20 17:23:35 -04:00
parent dc6a921693
commit 52b8cceea6
No known key found for this signature in database
2 changed files with 50 additions and 20 deletions

View file

@ -34,7 +34,7 @@ import javax.inject.Inject
import kotlin.time.Duration import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds import kotlin.time.Duration.Companion.seconds
const val MAX_HOURS = 24L const val MAX_HOURS = 48L
@HiltViewModel @HiltViewModel
class LiveTvViewModel class LiveTvViewModel
@ -55,8 +55,6 @@ class LiveTvViewModel
val fetchingItem = MutableLiveData<LoadingState>(LoadingState.Pending) val fetchingItem = MutableLiveData<LoadingState>(LoadingState.Pending)
val fetchedItem = MutableLiveData<BaseItem?>(null) val fetchedItem = MutableLiveData<BaseItem?>(null)
private val programMap = mutableMapOf<UUID, MutableLiveData<List<TvProgram?>>>()
fun init() { fun init() {
loading.value = LoadingState.Loading loading.value = LoadingState.Loading
viewModelScope.launch(Dispatchers.IO + LoadingExceptionHandler(loading, "Could not fetch channels")) { viewModelScope.launch(Dispatchers.IO + LoadingExceptionHandler(loading, "Could not fetch channels")) {

View file

@ -25,6 +25,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusDirection import androidx.compose.ui.focus.FocusDirection
import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.key.Key import androidx.compose.ui.input.key.Key
import androidx.compose.ui.input.key.KeyEventType import androidx.compose.ui.input.key.KeyEventType
@ -301,14 +302,16 @@ fun TvGuideGrid(
) )
} }
// fun programsBeforeChannel(channelIndex: Int): Int = channels.subList(0, channelIndex).map { it.id }.sumOf { programs[it]?.size ?: 0 } var gridHasFocus by remember { mutableStateOf(false) }
ProgramGuide( ProgramGuide(
state = state, state = state,
dimensions = dimensions, dimensions = dimensions,
modifier = modifier =
modifier modifier
.focusable() .onFocusChanged {
gridHasFocus = it.hasFocus
}.focusable()
.onPreviewKeyEvent { .onPreviewKeyEvent {
if (it.type == KeyEventType.KeyUp) { if (it.type == KeyEventType.KeyUp) {
return@onPreviewKeyEvent false return@onPreviewKeyEvent false
@ -351,37 +354,66 @@ fun TvGuideGrid(
Key.DirectionUp -> { Key.DirectionUp -> {
val start = programList[focusedProgramIndex].startHours val start = programList[focusedProgramIndex].startHours
focusedChannelIndex = (focusedChannelIndex - 1) val newFocusedChannelIndex = (focusedChannelIndex - 1)
if (focusedChannelIndex < 0) { if (newFocusedChannelIndex < 0) {
focusManager.moveFocus(FocusDirection.Up) focusManager.moveFocus(FocusDirection.Up)
null null
} else { } else {
val channelId = channels[focusedChannelIndex].id focusedChannelIndex = newFocusedChannelIndex.coerceAtLeast(0)
val pIndex =
programs[channelId]?.indexOfFirst { start in (it.startHours..<it.endHours) }
?: -1
if (pIndex >= 0) {
programsBeforeChannel.get(focusedChannelIndex) + pIndex
} else {
programsBeforeChannel.get(focusedChannelIndex) + programs[channelId]!!.size
}
}
}
Key.DirectionDown -> {
val start = programList[focusedProgramIndex].startHours
focusedChannelIndex =
(focusedChannelIndex + 1).coerceAtMost(channels.size - 1)
val channelId = channels[focusedChannelIndex].id val channelId = channels[focusedChannelIndex].id
val pro = programs[channelId].orEmpty() val pro = programs[channelId].orEmpty()
val pIndex = val pIndex =
pro.indexOfFirst { start in (it.startHours..<it.endHours) } pro.indexOfFirst { start in (it.startHours..<it.endHours) }
if (pIndex >= 0) {
programsBeforeChannel.get(focusedChannelIndex) + pIndex
} else {
val pIndex = pro.indexOfFirst { it.startHours >= start }
if (pIndex >= 0) { if (pIndex >= 0) {
programsBeforeChannel.get(focusedChannelIndex) + pIndex programsBeforeChannel.get(focusedChannelIndex) + pIndex
} else { } else {
programsBeforeChannel.get(focusedChannelIndex) + programs[channelId]!!.size programsBeforeChannel.get(focusedChannelIndex) + programs[channelId]!!.size
} }
} }
}
}
Key.DirectionDown -> {
// When the currently focused program starts
val start = programList[focusedProgramIndex].startHours
// Move channel focus down
val newFocusedChannelIndex = (focusedChannelIndex + 1)
if (newFocusedChannelIndex >= channels.size) {
// If trying to move below the final channel, then move focus out of the grid
focusManager.moveFocus(FocusDirection.Down)
null
} else {
// Otherwise, moving to a new row
focusedChannelIndex =
newFocusedChannelIndex.coerceAtMost(channels.size - 1)
// Get the new row/channel's programs
val channelId = channels[focusedChannelIndex].id
val pro = programs[channelId].orEmpty()
// Find the first program where the start time (of the previously focused program) is in the middle of a program
val pIndex =
pro.indexOfFirst { start in (it.startHours..<it.endHours) }
if (pIndex >= 0) {
// Found one, so focus on it
// Get the sum of all of the previous channels' program sizes, plus the index found to convert a relative program index into absolute
programsBeforeChannel.get(focusedChannelIndex) + pIndex
} else {
// Didn't find one, probably due to missing data
// So now first the first one that starts after the previously focused program
val pIndex = pro.indexOfFirst { it.startHours >= start }
if (pIndex >= 0) {
// Found one, so focus on it
programsBeforeChannel.get(focusedChannelIndex) + pIndex
} else {
// Did not find one, so focus on the final program in the list
programsBeforeChannel.get(focusedChannelIndex) + programs[channelId]!!.size
}
}
}
}
Key.DirectionCenter, Key.Enter, Key.NumPadEnter -> { Key.DirectionCenter, Key.Enter, Key.NumPadEnter -> {
val program = programList[focusedProgramIndex] val program = programList[focusedProgramIndex]
@ -450,7 +482,7 @@ fun TvGuideGrid(
}, },
) { programIndex -> ) { programIndex ->
val program = programList[programIndex] val program = programList[programIndex]
val focused = programIndex == focusedProgramIndex val focused = gridHasFocus && programIndex == focusedProgramIndex
val background = val background =
if (focused) { if (focused) {
MaterialTheme.colorScheme.inverseSurface MaterialTheme.colorScheme.inverseSurface