Clean up and protect against bad data

This commit is contained in:
Damontecres 2025-11-01 16:09:07 -04:00
parent 1ecbc37a07
commit 8f08f0d118
No known key found for this signature in database
2 changed files with 34 additions and 77 deletions

View file

@ -40,7 +40,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 = 3L const val MAX_HOURS = 48L
@HiltViewModel @HiltViewModel
class LiveTvViewModel class LiveTvViewModel
@ -64,10 +64,7 @@ class LiveTvViewModel
val fetchingItem = MutableLiveData<LoadingState>(LoadingState.Pending) val fetchingItem = MutableLiveData<LoadingState>(LoadingState.Pending)
val fetchedItem = MutableLiveData<BaseItem?>(null) val fetchedItem = MutableLiveData<BaseItem?>(null)
val offset = MutableLiveData(0)
val programOffset = MutableLiveData(0)
private val range = 8 private val range = 8
private var currentIndex = 0
val fetchedRange = MutableLiveData<IntRange>(0..<range) val fetchedRange = MutableLiveData<IntRange>(0..<range)
fun init(firstLoad: Boolean) { fun init(firstLoad: Boolean) {
@ -196,13 +193,21 @@ class LiveTvViewModel
channelProgramCount[channelId] = programs.size channelProgramCount[channelId] = programs.size
} }
val finalProgramList = val finalProgramList =
(programs + fake).sortedWith( (programs + fake)
.filterNot {
// TODO filter out items that start after end
// This can happen due to daylight savings switch or just be bad data
it.startHours >= it.endHours
}.sortedWith(
compareBy( compareBy(
{ channelsIdToIndex[it.channelId]!! }, { channelsIdToIndex[it.channelId]!! },
{ it.start }, { it.start },
), ),
) )
Timber.d("Got ${programs.size} programs & ${fake.size} fake programs") Timber.d("Got ${programs.size} programs & ${fake.size} fake programs")
finalProgramList
.filter { it.startHours == it.endHours }
.let { Timber.e("Items with same start & end!!! %s", it) }
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
this@LiveTvViewModel.programs.value = finalProgramList this@LiveTvViewModel.programs.value = finalProgramList
this@LiveTvViewModel.programsByChannel.value = finalProgramsByChannel this@LiveTvViewModel.programsByChannel.value = finalProgramsByChannel
@ -336,11 +341,6 @@ class LiveTvViewModel
Timber.v("Loading more programs for channels $newFetchRange") Timber.v("Loading more programs for channels $newFetchRange")
return viewModelScope.launchIO { return viewModelScope.launchIO {
fetchPrograms(channels, newFetchRange) fetchPrograms(channels, newFetchRange)
// withContext(Dispatchers.Main) {
// this@LiveTvViewModel.offset.value = fetchStart
// currentIndex = index
// this@LiveTvViewModel.programOffset.value = programOffset
// }
} }
} }
return null return null

View file

@ -80,7 +80,7 @@ fun TvGuideGrid(
val channels by viewModel.channels.observeAsState(listOf()) val channels by viewModel.channels.observeAsState(listOf())
val programs by viewModel.programs.observeAsState(listOf()) val programs by viewModel.programs.observeAsState(listOf())
val programsByChannel by viewModel.programsByChannel.observeAsState(mapOf()) val programsByChannel by viewModel.programsByChannel.observeAsState(mapOf())
val channelOffset by viewModel.offset.observeAsState(0) val fetchedRange by viewModel.fetchedRange.observeAsState(0..0)
when (val state = loading) { when (val state = loading) {
is LoadingState.Error -> ErrorMessage(state, modifier) is LoadingState.Error -> ErrorMessage(state, modifier)
LoadingState.Pending, LoadingState.Pending,
@ -107,7 +107,7 @@ fun TvGuideGrid(
programs = programsByChannel, programs = programsByChannel,
channelProgramCount = viewModel.channelProgramCount, channelProgramCount = viewModel.channelProgramCount,
start = viewModel.start, start = viewModel.start,
channelOffset = channelOffset, channelOffset = fetchedRange.start,
onClickChannel = { index, channel -> onClickChannel = { index, channel ->
viewModel.navigationManager.navigateTo( viewModel.navigationManager.navigateTo(
Destination.Playback( Destination.Playback(
@ -209,8 +209,6 @@ fun TvGuideGrid(
val focusManager = LocalFocusManager.current val focusManager = LocalFocusManager.current
val state = rememberSaveableProgramGuideState() val state = rememberSaveableProgramGuideState()
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
// var focusedProgramIndex by rememberInt(0)
// var focusedChannelIndex by rememberInt(0)
var focusedItem by rememberPosition(RowColumn(0, 0)) var focusedItem by rememberPosition(RowColumn(0, 0))
val focusedChannelIndex = focusedItem.row val focusedChannelIndex = focusedItem.row
@ -222,9 +220,9 @@ fun TvGuideGrid(
} }
LaunchedEffect(focusedProgramIndex) { LaunchedEffect(focusedProgramIndex) {
val (channelIndex, programIndex) = focusedItem // Timber.v("Focusing on $focusedItem, programIndex=$focusedProgramIndex")
Timber.v("Focusing on $focusedItem, programIndex=$focusedProgramIndex")
scope.launch(ExceptionHandler()) { scope.launch(ExceptionHandler()) {
// Timber.v("program=${programList.getOrNull(focusedProgramIndex)}")
state.animateToProgram(focusedProgramIndex, Alignment.Center) state.animateToProgram(focusedProgramIndex, Alignment.Center)
} }
} }
@ -236,35 +234,6 @@ fun TvGuideGrid(
channelHeight = 80.dp, channelHeight = 80.dp,
currentTimeWidth = 2.dp, currentTimeWidth = 2.dp,
) )
// val programsBeforeChannel =
// remember {
// CacheBuilder
// .newBuilder()
// .maximumSize(200)
// .build<Int, Int>(
// object : CacheLoader<Int, Int>() {
// override fun load(key: Int): Int =
// (
// channels
// .subList(0, key)
// .map { it.id }
// .sumOf {
// channelProgramCount[it] ?: 0
// } - programOffset
// ).coerceAtLeast(0)
// },
// )
// }
var movementEnabled by remember { mutableStateOf(true) }
// LaunchedEffect(programOffset) {
// movementEnabled = false
// programsBeforeChannel.invalidateAll()
// Timber.v("Adjusting focusedProgramIndex, $focusedProgramIndex - $programOffset")
// focusedProgramIndex = (focusedProgramIndex - programOffset).coerceAtLeast(0)
// movementEnabled = true
// }
var gridHasFocus by rememberSaveable { mutableStateOf(false) } var gridHasFocus by rememberSaveable { mutableStateOf(false) }
var channelColumnFocused by rememberSaveable { mutableStateOf(false) } var channelColumnFocused by rememberSaveable { mutableStateOf(false) }
Box(modifier = modifier) { Box(modifier = modifier) {
@ -281,9 +250,6 @@ fun TvGuideGrid(
if (it.type == KeyEventType.KeyUp) { if (it.type == KeyEventType.KeyUp) {
return@onPreviewKeyEvent false return@onPreviewKeyEvent false
} }
if (!movementEnabled) {
return@onPreviewKeyEvent true
}
val item = focusedItem val item = focusedItem
val newFocusedItem = val newFocusedItem =
when (it.key) { when (it.key) {
@ -429,32 +395,23 @@ fun TvGuideGrid(
} }
if (newFocusedItem != null) { if (newFocusedItem != null) {
focusedItem = newFocusedItem val channel = channels[newFocusedItem.row]
onFocus(newFocusedItem) val programs = programs[channel.id].orEmpty()
// Ensure it isn't going out of range
val toFocus =
newFocusedItem
.copy(
row = newFocusedItem.row.coerceIn(0, channels.size - 1),
column =
newFocusedItem.column.coerceIn(
0,
programs.size - 1,
),
)
focusedItem = toFocus
onFocus(toFocus)
return@onPreviewKeyEvent true return@onPreviewKeyEvent true
} }
// if (newIndex != null) {
// // Timber.v("newIndex=$newIndex")
// if (newIndex >= 0) {
// val before = programsBeforeChannel.get(focusedChannelIndex)
// val max =
// before +
// channels[focusedChannelIndex].let {
// programs[it.id]?.size ?: 0
// } - 1
// val index = newIndex.coerceIn(before, max)
// Timber.v("move: programIndex=$index, channelIndex=$focusedChannelIndex")
// scope.launch(ExceptionHandler()) {
// focusedProgramIndex = index
// state.animateToProgram(index, Alignment.Center)
// onFocusChannel.invoke(focusedChannelIndex)
// }
// return@onPreviewKeyEvent true
// } else {
// Timber.w("newIndex is <0: $newIndex")
// return@onPreviewKeyEvent true
// }
// }
return@onPreviewKeyEvent false return@onPreviewKeyEvent false
}, },
) { ) {