mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 15:50:16 +02:00
WIP
This commit is contained in:
parent
8f08f0d118
commit
bf28209255
2 changed files with 84 additions and 58 deletions
|
|
@ -58,14 +58,14 @@ class LiveTvViewModel
|
|||
|
||||
val channels = MutableLiveData<List<TvChannel>>()
|
||||
val channelProgramCount = mutableMapOf<UUID, Int>()
|
||||
val programs = MutableLiveData<List<TvProgram>>()
|
||||
val programsByChannel = MutableLiveData<Map<UUID, List<TvProgram>>>(mapOf())
|
||||
val programs = MutableLiveData<FetchedPrograms>()
|
||||
// val programsByChannel = MutableLiveData<Map<UUID, List<TvProgram>>>(mapOf())
|
||||
|
||||
val fetchingItem = MutableLiveData<LoadingState>(LoadingState.Pending)
|
||||
val fetchedItem = MutableLiveData<BaseItem?>(null)
|
||||
|
||||
private val range = 8
|
||||
val fetchedRange = MutableLiveData<IntRange>(0..<range)
|
||||
// val fetchedRange = MutableLiveData<IntRange>(0..<range)
|
||||
|
||||
fun init(firstLoad: Boolean) {
|
||||
start = LocalDateTime.now().truncatedTo(ChronoUnit.HOURS)
|
||||
|
|
@ -100,9 +100,12 @@ class LiveTvViewModel
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun fetchProgramsWithLoading(channels: List<TvChannel>) {
|
||||
private suspend fun fetchProgramsWithLoading(
|
||||
channels: List<TvChannel>,
|
||||
range: IntRange,
|
||||
) {
|
||||
loading.setValueOnMain(LoadingState.Loading)
|
||||
fetchPrograms(channels, fetchedRange.value!!)
|
||||
fetchPrograms(channels, range)
|
||||
loading.setValueOnMain(LoadingState.Success)
|
||||
}
|
||||
|
||||
|
|
@ -112,12 +115,13 @@ class LiveTvViewModel
|
|||
) = mutex.withLock {
|
||||
val maxStartDate = start.plusHours(MAX_HOURS).minusMinutes(1)
|
||||
val minEndDate = start.plusMinutes(1L)
|
||||
Timber.v("Fetching programs for ${channels.size} channels")
|
||||
val channelsToFetch = channels.subList(range.first, range.last)
|
||||
Timber.v("Fetching programs for $range channels ${channelsToFetch.size}")
|
||||
val request =
|
||||
GetProgramsDto(
|
||||
maxStartDate = maxStartDate,
|
||||
minEndDate = minEndDate,
|
||||
channelIds = channels.map { it.id },
|
||||
channelIds = channelsToFetch.map { it.id },
|
||||
sortBy = listOf(ItemSortBy.START_DATE),
|
||||
)
|
||||
val programs =
|
||||
|
|
@ -161,7 +165,8 @@ class LiveTvViewModel
|
|||
}
|
||||
|
||||
val programsByChannel = programs.groupBy { it.channelId }
|
||||
val emptyChannels = channels.filter { programsByChannel[it.id].orEmpty().isEmpty() }
|
||||
val emptyChannels =
|
||||
channelsToFetch.filter { programsByChannel[it.id].orEmpty().isEmpty() }
|
||||
val fake = mutableListOf<TvProgram>()
|
||||
val finalProgramsByChannel =
|
||||
programsByChannel.toMutableMap().apply {
|
||||
|
|
@ -209,9 +214,8 @@ class LiveTvViewModel
|
|||
.filter { it.startHours == it.endHours }
|
||||
.let { Timber.e("Items with same start & end!!! %s", it) }
|
||||
withContext(Dispatchers.Main) {
|
||||
this@LiveTvViewModel.programs.value = finalProgramList
|
||||
this@LiveTvViewModel.programsByChannel.value = finalProgramsByChannel
|
||||
this@LiveTvViewModel.fetchedRange.value = range
|
||||
this@LiveTvViewModel.programs.value =
|
||||
FetchedPrograms(range, finalProgramList, finalProgramsByChannel)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -247,7 +251,7 @@ class LiveTvViewModel
|
|||
viewModelScope.launchIO(ExceptionHandler(autoToast = true)) {
|
||||
if (series) {
|
||||
api.liveTvApi.cancelSeriesTimer(timerId)
|
||||
fetchProgramsWithLoading(channels.value.orEmpty())
|
||||
fetchProgramsWithLoading(channels.value.orEmpty(), programs.value!!.range)
|
||||
} else {
|
||||
api.liveTvApi.cancelTimer(timerId)
|
||||
refreshProgram(programIndex, programId)
|
||||
|
|
@ -265,7 +269,7 @@ class LiveTvViewModel
|
|||
val d by api.liveTvApi.getDefaultTimer(programId.toServerString())
|
||||
if (series) {
|
||||
api.liveTvApi.createSeriesTimer(d)
|
||||
fetchProgramsWithLoading(channels.value.orEmpty())
|
||||
fetchProgramsWithLoading(channels.value.orEmpty(), programs.value!!.range)
|
||||
} else {
|
||||
val payload =
|
||||
TimerInfoDto(
|
||||
|
|
@ -301,36 +305,37 @@ class LiveTvViewModel
|
|||
programId: UUID,
|
||||
) = mutex.withLock {
|
||||
loading.setValueOnMain(LoadingState.Loading)
|
||||
val program by api.liveTvApi.getProgram(programId.toServerString())
|
||||
val newProgram =
|
||||
programs.value?.getOrNull(programIndex)?.copy(
|
||||
isRecording = program.timerId.isNotNullOrBlank(),
|
||||
isSeriesRecording = program.seriesTimerId.isNotNullOrBlank(),
|
||||
)
|
||||
Timber.v("new program %s", newProgram)
|
||||
if (newProgram != null) {
|
||||
programs.value
|
||||
?.toMutableList()
|
||||
?.apply {
|
||||
this[programIndex] = newProgram
|
||||
}?.let {
|
||||
this@LiveTvViewModel.programs.setValueOnMain(it)
|
||||
}
|
||||
}
|
||||
// TODO
|
||||
// val program by api.liveTvApi.getProgram(programId.toServerString())
|
||||
// val newProgram =
|
||||
// programs.value?.getOrNull(programIndex)?.copy(
|
||||
// isRecording = program.timerId.isNotNullOrBlank(),
|
||||
// isSeriesRecording = program.seriesTimerId.isNotNullOrBlank(),
|
||||
// )
|
||||
// Timber.v("new program %s", newProgram)
|
||||
// if (newProgram != null) {
|
||||
// programs.value
|
||||
// ?.toMutableList()
|
||||
// ?.apply {
|
||||
// this[programIndex] = newProgram
|
||||
// }?.let {
|
||||
// this@LiveTvViewModel.programs.setValueOnMain(it)
|
||||
// }
|
||||
// }
|
||||
loading.setValueOnMain(LoadingState.Success)
|
||||
}
|
||||
|
||||
fun onFocusChannel(position: RowColumn): Job? {
|
||||
return channels.value?.let { channels ->
|
||||
val fetchedRange = fetchedRange.value!!
|
||||
val fetchedRange = programs.value!!.range
|
||||
val quarter = (fetchedRange.last - fetchedRange.start) / 4
|
||||
val rangeStart = fetchedRange.start + quarter
|
||||
val rangeEnd = fetchedRange.last - quarter
|
||||
val testRange = rangeStart..<rangeEnd
|
||||
|
||||
Timber.v(
|
||||
"onFocusChannel: position=$position, fetchedRange=$fetchedRange, testRange=$testRange",
|
||||
)
|
||||
// Timber.v(
|
||||
// "onFocusChannel: position=$position, fetchedRange=$fetchedRange, testRange=$testRange",
|
||||
// )
|
||||
val fetchStart = (position.row - range).coerceAtLeast(0)
|
||||
val fetchEnd = (position.row + range).coerceAtMost(channels.size)
|
||||
val newFetchRange = fetchStart..<fetchEnd
|
||||
|
|
@ -340,7 +345,7 @@ class LiveTvViewModel
|
|||
if (position.row !in testRange && !newFetchRange.within(fetchedRange)) {
|
||||
Timber.v("Loading more programs for channels $newFetchRange")
|
||||
return viewModelScope.launchIO {
|
||||
fetchPrograms(channels, newFetchRange)
|
||||
fetchProgramsWithLoading(channels, newFetchRange)
|
||||
}
|
||||
}
|
||||
return null
|
||||
|
|
@ -396,3 +401,9 @@ enum class ProgramCategory(
|
|||
SPORTS(AppColors.DarkRed),
|
||||
FAKE(null),
|
||||
}
|
||||
|
||||
data class FetchedPrograms(
|
||||
val range: IntRange,
|
||||
val programs: List<TvProgram>,
|
||||
val programsByChannel: Map<UUID, List<TvProgram>>,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ import com.github.damontecres.wholphin.ui.components.LoadingPage
|
|||
import com.github.damontecres.wholphin.ui.data.RowColumn
|
||||
import com.github.damontecres.wholphin.ui.enableMarquee
|
||||
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||
import com.github.damontecres.wholphin.ui.playback.isDpad
|
||||
import com.github.damontecres.wholphin.ui.rememberPosition
|
||||
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
||||
import com.github.damontecres.wholphin.util.ExceptionHandler
|
||||
|
|
@ -78,9 +79,9 @@ fun TvGuideGrid(
|
|||
}
|
||||
val loading by viewModel.loading.observeAsState(LoadingState.Pending)
|
||||
val channels by viewModel.channels.observeAsState(listOf())
|
||||
val programs by viewModel.programs.observeAsState(listOf())
|
||||
val programsByChannel by viewModel.programsByChannel.observeAsState(mapOf())
|
||||
val fetchedRange by viewModel.fetchedRange.observeAsState(0..0)
|
||||
val programs by viewModel.programs.observeAsState(FetchedPrograms(0..0, listOf(), mapOf()))
|
||||
// val programsByChannel by viewModel.programsByChannel.observeAsState(mapOf())
|
||||
// val fetchedRange by viewModel.fetchedRange.observeAsState(0..0)
|
||||
when (val state = loading) {
|
||||
is LoadingState.Error -> ErrorMessage(state, modifier)
|
||||
LoadingState.Pending,
|
||||
|
|
@ -103,11 +104,11 @@ fun TvGuideGrid(
|
|||
TvGuideGrid(
|
||||
loading = state is LoadingState.Loading,
|
||||
channels = channels,
|
||||
programList = programs,
|
||||
programs = programsByChannel,
|
||||
// programList = programs,
|
||||
programs = programs,
|
||||
channelProgramCount = viewModel.channelProgramCount,
|
||||
start = viewModel.start,
|
||||
channelOffset = fetchedRange.start,
|
||||
// channelOffset = fetchedRange.start,
|
||||
onClickChannel = { index, channel ->
|
||||
viewModel.navigationManager.navigateTo(
|
||||
Destination.Playback(
|
||||
|
|
@ -195,14 +196,15 @@ const val CHANNEL_COLUMN = -1
|
|||
fun TvGuideGrid(
|
||||
loading: Boolean,
|
||||
channels: List<TvChannel>,
|
||||
programList: List<TvProgram>,
|
||||
programs: Map<UUID, List<TvProgram>>,
|
||||
// programList: List<TvProgram>,
|
||||
// programs: Map<UUID, List<TvProgram>>,
|
||||
programs: FetchedPrograms,
|
||||
channelProgramCount: Map<UUID, Int>,
|
||||
start: LocalDateTime,
|
||||
onClickChannel: (Int, TvChannel) -> Unit,
|
||||
onClickProgram: (Int, TvProgram) -> Unit,
|
||||
onFocus: (RowColumn) -> Unit,
|
||||
channelOffset: Int,
|
||||
// channelOffset: Int,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
|
@ -213,16 +215,18 @@ fun TvGuideGrid(
|
|||
var focusedItem by rememberPosition(RowColumn(0, 0))
|
||||
val focusedChannelIndex = focusedItem.row
|
||||
val focusedProgramIndex =
|
||||
remember(channelOffset, focusedItem) {
|
||||
remember(programs.range, focusedItem) {
|
||||
focusedItem.let { focus ->
|
||||
(channelOffset..<focus.row).sumOf { channelProgramCount[channels[it].id]!! } + focus.column
|
||||
(programs.range.start..<focus.row).sumOf {
|
||||
val channelId = channels[it].id
|
||||
channelProgramCount[channelId] ?: 0
|
||||
} + focus.column
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(focusedProgramIndex) {
|
||||
// Timber.v("Focusing on $focusedItem, programIndex=$focusedProgramIndex")
|
||||
scope.launch(ExceptionHandler()) {
|
||||
// Timber.v("program=${programList.getOrNull(focusedProgramIndex)}")
|
||||
state.animateToProgram(focusedProgramIndex, Alignment.Center)
|
||||
}
|
||||
}
|
||||
|
|
@ -250,6 +254,10 @@ fun TvGuideGrid(
|
|||
if (it.type == KeyEventType.KeyUp) {
|
||||
return@onPreviewKeyEvent false
|
||||
}
|
||||
if (isDpad(it) && loading) {
|
||||
// Prevent movement during loading
|
||||
return@onPreviewKeyEvent true
|
||||
}
|
||||
val item = focusedItem
|
||||
val newFocusedItem =
|
||||
when (it.key) {
|
||||
|
|
@ -295,13 +303,13 @@ fun TvGuideGrid(
|
|||
} else {
|
||||
val currentChannel = channels[item.row].id
|
||||
val currentProgram =
|
||||
programs[currentChannel]?.get(item.column)
|
||||
programs.programsByChannel[currentChannel]?.get(item.column)
|
||||
if (currentProgram == null) {
|
||||
item
|
||||
} else {
|
||||
val start = currentProgram.startHours
|
||||
val newChannelPrograms =
|
||||
programs[channels[newChannelIndex].id].orEmpty()
|
||||
programs.programsByChannel[channels[newChannelIndex].id].orEmpty()
|
||||
val pIndex =
|
||||
newChannelPrograms.indexOfFirst { start in (it.startHours..<it.endHours) }
|
||||
if (pIndex >= 0) {
|
||||
|
|
@ -335,7 +343,7 @@ fun TvGuideGrid(
|
|||
// Get current program & its start time
|
||||
val currentChannel = channels[item.row].id
|
||||
val currentProgram =
|
||||
programs[currentChannel]?.get(item.column)
|
||||
programs.programsByChannel[currentChannel]?.get(item.column)
|
||||
if (currentProgram == null) {
|
||||
// Data is loading in the background
|
||||
item
|
||||
|
|
@ -343,7 +351,7 @@ fun TvGuideGrid(
|
|||
val start = currentProgram.startHours
|
||||
// Get the new row/channel's programs
|
||||
val newChannelPrograms =
|
||||
programs[channels[newChannelIndex].id].orEmpty()
|
||||
programs.programsByChannel[channels[newChannelIndex].id].orEmpty()
|
||||
// Find the first program where the start time (of the previously focused program) is in the middle of a program
|
||||
val pIndex =
|
||||
newChannelPrograms.indexOfFirst { start in (it.startHours..<it.endHours) }
|
||||
|
|
@ -378,13 +386,16 @@ fun TvGuideGrid(
|
|||
} else {
|
||||
val currentChannel = channels[item.row].id
|
||||
val currentProgram =
|
||||
programs[currentChannel]?.get(item.column)
|
||||
programs.programsByChannel[currentChannel]?.get(item.column)
|
||||
if (currentProgram == null) {
|
||||
// Data is loading in the background
|
||||
return@onPreviewKeyEvent true
|
||||
}
|
||||
Timber.v("Clicked on %s", currentProgram)
|
||||
onClickProgram.invoke(focusedProgramIndex, currentProgram)
|
||||
onClickProgram.invoke(
|
||||
focusedProgramIndex,
|
||||
currentProgram,
|
||||
)
|
||||
}
|
||||
null
|
||||
}
|
||||
|
|
@ -396,7 +407,7 @@ fun TvGuideGrid(
|
|||
|
||||
if (newFocusedItem != null) {
|
||||
val channel = channels[newFocusedItem.row]
|
||||
val programs = programs[channel.id].orEmpty()
|
||||
val programs = programs.programsByChannel[channel.id].orEmpty()
|
||||
// Ensure it isn't going out of range
|
||||
val toFocus =
|
||||
newFocusedItem
|
||||
|
|
@ -424,7 +435,12 @@ fun TvGuideGrid(
|
|||
},
|
||||
) {
|
||||
Surface(
|
||||
colors = SurfaceDefaults.colors(MaterialTheme.colorScheme.tertiary.copy(alpha = .25f)),
|
||||
colors =
|
||||
SurfaceDefaults.colors(
|
||||
MaterialTheme.colorScheme.tertiary.copy(
|
||||
alpha = .25f,
|
||||
),
|
||||
),
|
||||
modifier = Modifier,
|
||||
) {
|
||||
// Empty
|
||||
|
|
@ -465,16 +481,15 @@ fun TvGuideGrid(
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
programs(
|
||||
count = programList.size,
|
||||
count = programs.programs.size,
|
||||
layoutInfo = { programIndex ->
|
||||
val program = programList[programIndex]
|
||||
val program = programs.programs[programIndex]
|
||||
val channelIndex = channels.indexOfFirst { it.id == program.channelId }
|
||||
ProgramGuideItem.Program(channelIndex, program.startHours, program.endHours)
|
||||
},
|
||||
) { programIndex ->
|
||||
val program = programList[programIndex]
|
||||
val program = programs.programs[programIndex]
|
||||
val focused =
|
||||
gridHasFocus && !channelColumnFocused && programIndex == focusedProgramIndex
|
||||
val background =
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue