mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 15:50:16 +02:00
Support selecting channel column to play
This commit is contained in:
parent
0002dece8c
commit
be7bb65a7e
2 changed files with 102 additions and 38 deletions
|
|
@ -112,7 +112,7 @@ class LiveTvViewModel
|
||||||
channelId = dto.channelId!!,
|
channelId = dto.channelId!!,
|
||||||
start = dto.startDate!!,
|
start = dto.startDate!!,
|
||||||
end = dto.endDate!!,
|
end = dto.endDate!!,
|
||||||
startHours = hoursBetween(start, dto.startDate!!),
|
startHours = hoursBetween(start, dto.startDate!!).coerceAtLeast(0f),
|
||||||
endHours = hoursBetween(start, dto.endDate!!),
|
endHours = hoursBetween(start, dto.endDate!!),
|
||||||
duration = dto.runTimeTicks!!.ticks,
|
duration = dto.runTimeTicks!!.ticks,
|
||||||
name = dto.seriesName ?: dto.name,
|
name = dto.seriesName ?: dto.name,
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ 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.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
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
|
||||||
|
|
@ -99,7 +100,15 @@ fun TvGuideGrid(
|
||||||
programList = programs,
|
programList = programs,
|
||||||
programs = programsByChannel,
|
programs = programsByChannel,
|
||||||
start = viewModel.start,
|
start = viewModel.start,
|
||||||
onClick = { index, program ->
|
onClickChannel = { index, channel ->
|
||||||
|
viewModel.navigationManager.navigateTo(
|
||||||
|
Destination.Playback(
|
||||||
|
itemId = channel.id,
|
||||||
|
positionMs = 0L,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onClickProgram = { index, program ->
|
||||||
if (program.isFake) {
|
if (program.isFake) {
|
||||||
val now = LocalDateTime.now()
|
val now = LocalDateTime.now()
|
||||||
if (now.isAfter(program.start) && now.isBefore(program.end)) {
|
if (now.isAfter(program.start) && now.isBefore(program.end)) {
|
||||||
|
|
@ -170,13 +179,16 @@ fun TvGuideGrid(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const val CHANNEL_COLUMN = -1
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun TvGuideGrid(
|
fun TvGuideGrid(
|
||||||
channels: List<TvChannel>,
|
channels: List<TvChannel>,
|
||||||
programList: List<TvProgram>,
|
programList: List<TvProgram>,
|
||||||
programs: Map<UUID, List<TvProgram>>,
|
programs: Map<UUID, List<TvProgram>>,
|
||||||
start: LocalDateTime,
|
start: LocalDateTime,
|
||||||
onClick: (Int, TvProgram) -> Unit,
|
onClickChannel: (Int, TvChannel) -> Unit,
|
||||||
|
onClickProgram: (Int, TvProgram) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
@ -211,7 +223,8 @@ fun TvGuideGrid(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
var gridHasFocus by remember { mutableStateOf(false) }
|
var gridHasFocus by rememberSaveable { mutableStateOf(false) }
|
||||||
|
var channelColumnFocused by rememberSaveable { mutableStateOf(false) }
|
||||||
|
|
||||||
ProgramGuide(
|
ProgramGuide(
|
||||||
state = state,
|
state = state,
|
||||||
|
|
@ -239,47 +252,62 @@ fun TvGuideGrid(
|
||||||
}
|
}
|
||||||
|
|
||||||
Key.DirectionRight -> {
|
Key.DirectionRight -> {
|
||||||
val nextProgramIndex = focusedProgramIndex + 1
|
if (channelColumnFocused) {
|
||||||
val programsBefore = programsBeforeChannel.get(focusedChannelIndex)
|
channelColumnFocused = false
|
||||||
val relativePosition = nextProgramIndex - programsBefore
|
focusedProgramIndex
|
||||||
val channelPrograms =
|
|
||||||
programs[channels[focusedChannelIndex].id].orEmpty()
|
|
||||||
if (relativePosition >= channelPrograms.size) {
|
|
||||||
focusManager.moveFocus(FocusDirection.Right)
|
|
||||||
null
|
|
||||||
} else {
|
} else {
|
||||||
nextProgramIndex
|
val nextProgramIndex = focusedProgramIndex + 1
|
||||||
|
val programsBefore =
|
||||||
|
programsBeforeChannel.get(focusedChannelIndex)
|
||||||
|
val relativePosition = nextProgramIndex - programsBefore
|
||||||
|
val channelPrograms =
|
||||||
|
programs[channels[focusedChannelIndex].id].orEmpty()
|
||||||
|
if (relativePosition >= channelPrograms.size) {
|
||||||
|
focusManager.moveFocus(FocusDirection.Right)
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
nextProgramIndex
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Key.DirectionLeft -> {
|
Key.DirectionLeft -> {
|
||||||
val nextProgramIndex = focusedProgramIndex - 1
|
if (channelColumnFocused) {
|
||||||
if (nextProgramIndex >= 0) {
|
focusManager.moveFocus(FocusDirection.Left)
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
val nextProgramIndex = focusedProgramIndex - 1
|
||||||
val programsBefore =
|
val programsBefore =
|
||||||
programsBeforeChannel.get(focusedChannelIndex)
|
programsBeforeChannel.get(focusedChannelIndex)
|
||||||
val relativePosition = nextProgramIndex - programsBefore
|
val relativePosition = nextProgramIndex - programsBefore
|
||||||
// val channelPrograms =
|
// val channelPrograms =
|
||||||
// programs[channels[focusedChannel].id].orEmpty()
|
// programs[channels[focusedChannel].id].orEmpty()
|
||||||
if (relativePosition < 0) {
|
if (relativePosition >= 0) {
|
||||||
|
nextProgramIndex
|
||||||
|
} else if (relativePosition == CHANNEL_COLUMN) {
|
||||||
|
channelColumnFocused = true
|
||||||
|
focusedProgramIndex
|
||||||
|
} else {
|
||||||
|
Timber.w("Move left to relativePosition=$relativePosition should not occur")
|
||||||
focusManager.moveFocus(FocusDirection.Left)
|
focusManager.moveFocus(FocusDirection.Left)
|
||||||
null
|
null
|
||||||
} else {
|
|
||||||
nextProgramIndex
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
focusManager.moveFocus(FocusDirection.Left)
|
|
||||||
null
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Key.DirectionUp -> {
|
Key.DirectionUp -> {
|
||||||
val start = programList[focusedProgramIndex].startHours
|
|
||||||
val newFocusedChannelIndex = (focusedChannelIndex - 1)
|
val newFocusedChannelIndex = (focusedChannelIndex - 1)
|
||||||
|
// Timber.v("newFocusedChannelIndex=$newFocusedChannelIndex")
|
||||||
if (newFocusedChannelIndex < 0) {
|
if (newFocusedChannelIndex < 0) {
|
||||||
focusManager.moveFocus(FocusDirection.Up)
|
focusManager.moveFocus(FocusDirection.Up)
|
||||||
null
|
null
|
||||||
|
} else if (channelColumnFocused) {
|
||||||
|
focusedChannelIndex = newFocusedChannelIndex
|
||||||
|
programsBeforeChannel.get(focusedChannelIndex)
|
||||||
} else {
|
} else {
|
||||||
focusedChannelIndex = newFocusedChannelIndex.coerceAtLeast(0)
|
val start = programList[focusedProgramIndex].startHours
|
||||||
|
focusedChannelIndex =
|
||||||
|
newFocusedChannelIndex.coerceAtLeast(0)
|
||||||
val channelId = channels[focusedChannelIndex].id
|
val channelId = channels[focusedChannelIndex].id
|
||||||
val pro = programs[channelId].orEmpty()
|
val pro = programs[channelId].orEmpty()
|
||||||
val pIndex =
|
val pIndex =
|
||||||
|
|
@ -298,14 +326,17 @@ fun TvGuideGrid(
|
||||||
}
|
}
|
||||||
|
|
||||||
Key.DirectionDown -> {
|
Key.DirectionDown -> {
|
||||||
// When the currently focused program starts
|
|
||||||
val start = programList[focusedProgramIndex].startHours
|
|
||||||
// Move channel focus down
|
// Move channel focus down
|
||||||
val newFocusedChannelIndex = (focusedChannelIndex + 1)
|
val newFocusedChannelIndex = (focusedChannelIndex + 1)
|
||||||
if (newFocusedChannelIndex >= channels.size) {
|
if (newFocusedChannelIndex >= channels.size) {
|
||||||
// If trying to move below the final channel, then move focus out of the grid
|
// If trying to move below the final channel, then move focus out of the grid
|
||||||
focusManager.moveFocus(FocusDirection.Down)
|
focusManager.moveFocus(FocusDirection.Down)
|
||||||
null
|
null
|
||||||
|
} else if (channelColumnFocused) {
|
||||||
|
// If focused on the channel column, move down a channel
|
||||||
|
focusedChannelIndex =
|
||||||
|
newFocusedChannelIndex.coerceAtMost(channels.size - 1)
|
||||||
|
programsBeforeChannel.get(focusedChannelIndex)
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, moving to a new row
|
// Otherwise, moving to a new row
|
||||||
focusedChannelIndex =
|
focusedChannelIndex =
|
||||||
|
|
@ -313,6 +344,8 @@ fun TvGuideGrid(
|
||||||
// Get the new row/channel's programs
|
// Get the new row/channel's programs
|
||||||
val channelId = channels[focusedChannelIndex].id
|
val channelId = channels[focusedChannelIndex].id
|
||||||
val pro = programs[channelId].orEmpty()
|
val pro = programs[channelId].orEmpty()
|
||||||
|
// When the currently focused program starts
|
||||||
|
val start = programList[focusedProgramIndex].startHours
|
||||||
// Find the first program where the start time (of the previously focused program) is in the middle of a program
|
// Find the first program where the start time (of the previously focused program) is in the middle of a program
|
||||||
val pIndex =
|
val pIndex =
|
||||||
pro.indexOfFirst { start in (it.startHours..<it.endHours) }
|
pro.indexOfFirst { start in (it.startHours..<it.endHours) }
|
||||||
|
|
@ -336,9 +369,15 @@ fun TvGuideGrid(
|
||||||
}
|
}
|
||||||
|
|
||||||
Key.DirectionCenter, Key.Enter, Key.NumPadEnter -> {
|
Key.DirectionCenter, Key.Enter, Key.NumPadEnter -> {
|
||||||
val program = programList[focusedProgramIndex]
|
if (channelColumnFocused) {
|
||||||
Timber.v("Clicked on %s", program)
|
val channel = channels[focusedChannelIndex]
|
||||||
onClick.invoke(focusedProgramIndex, program)
|
Timber.v("Clicked on %s", channel)
|
||||||
|
onClickChannel.invoke(focusedChannelIndex, channel)
|
||||||
|
} else {
|
||||||
|
val program = programList[focusedProgramIndex]
|
||||||
|
Timber.v("Clicked on %s", program)
|
||||||
|
onClickProgram.invoke(focusedProgramIndex, program)
|
||||||
|
}
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -347,15 +386,21 @@ fun TvGuideGrid(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (newIndex != null) {
|
if (newIndex != null) {
|
||||||
val before = programsBeforeChannel.get(focusedChannelIndex)
|
// Timber.v("newIndex=$newIndex")
|
||||||
val max =
|
if (newIndex >= 0) {
|
||||||
before + channels[focusedChannelIndex].let { programs[it.id]!!.size } - 1
|
val before = programsBeforeChannel.get(focusedChannelIndex)
|
||||||
val index = newIndex.coerceIn(before, max)
|
val max =
|
||||||
scope.launch(ExceptionHandler()) {
|
before + channels[focusedChannelIndex].let { programs[it.id]!!.size } - 1
|
||||||
focusedProgramIndex = index
|
val index = newIndex.coerceIn(before, max)
|
||||||
state.animateToProgram(index, Alignment.Center)
|
scope.launch(ExceptionHandler()) {
|
||||||
|
focusedProgramIndex = index
|
||||||
|
state.animateToProgram(index, Alignment.Center)
|
||||||
|
}
|
||||||
|
return@onPreviewKeyEvent true
|
||||||
|
} else {
|
||||||
|
Timber.w("newIndex is <0: $newIndex")
|
||||||
|
return@onPreviewKeyEvent true
|
||||||
}
|
}
|
||||||
return@onPreviewKeyEvent true
|
|
||||||
}
|
}
|
||||||
return@onPreviewKeyEvent false
|
return@onPreviewKeyEvent false
|
||||||
},
|
},
|
||||||
|
|
@ -420,7 +465,8 @@ fun TvGuideGrid(
|
||||||
},
|
},
|
||||||
) { programIndex ->
|
) { programIndex ->
|
||||||
val program = programList[programIndex]
|
val program = programList[programIndex]
|
||||||
val focused = gridHasFocus && programIndex == focusedProgramIndex
|
val focused =
|
||||||
|
gridHasFocus && !channelColumnFocused && programIndex == focusedProgramIndex
|
||||||
val background =
|
val background =
|
||||||
if (focused) {
|
if (focused) {
|
||||||
MaterialTheme.colorScheme.inverseSurface
|
MaterialTheme.colorScheme.inverseSurface
|
||||||
|
|
@ -480,12 +526,21 @@ fun TvGuideGrid(
|
||||||
},
|
},
|
||||||
) { channelIndex ->
|
) { channelIndex ->
|
||||||
val channel = channels[channelIndex]
|
val channel = channels[channelIndex]
|
||||||
|
val focused =
|
||||||
|
gridHasFocus && channelColumnFocused && focusedChannelIndex == channelIndex
|
||||||
|
val background =
|
||||||
|
if (focused) {
|
||||||
|
MaterialTheme.colorScheme.inverseSurface
|
||||||
|
} else {
|
||||||
|
MaterialTheme.colorScheme.surface
|
||||||
|
}
|
||||||
|
val textColor = MaterialTheme.colorScheme.contentColorFor(background)
|
||||||
Box(
|
Box(
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.background(
|
.background(
|
||||||
MaterialTheme.colorScheme.surface,
|
background,
|
||||||
shape = RoundedCornerShape(4.dp),
|
shape = RoundedCornerShape(4.dp),
|
||||||
),
|
),
|
||||||
) {
|
) {
|
||||||
|
|
@ -499,6 +554,7 @@ fun TvGuideGrid(
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = channel.number ?: channel.name ?: channelIndex.toString(),
|
text = channel.number ?: channel.name ?: channelIndex.toString(),
|
||||||
|
color = textColor,
|
||||||
modifier = Modifier,
|
modifier = Modifier,
|
||||||
)
|
)
|
||||||
AsyncImage(
|
AsyncImage(
|
||||||
|
|
@ -509,5 +565,13 @@ fun TvGuideGrid(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
topCorner {
|
||||||
|
Box(
|
||||||
|
Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.background(MaterialTheme.colorScheme.surface),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue