mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 23:51:21 +02:00
Handle DST and ranges
This commit is contained in:
parent
bf28209255
commit
8792c7dcb2
2 changed files with 85 additions and 65 deletions
|
|
@ -59,13 +59,11 @@ class LiveTvViewModel
|
||||||
val channels = MutableLiveData<List<TvChannel>>()
|
val channels = MutableLiveData<List<TvChannel>>()
|
||||||
val channelProgramCount = mutableMapOf<UUID, Int>()
|
val channelProgramCount = mutableMapOf<UUID, Int>()
|
||||||
val programs = MutableLiveData<FetchedPrograms>()
|
val programs = MutableLiveData<FetchedPrograms>()
|
||||||
// val programsByChannel = MutableLiveData<Map<UUID, List<TvProgram>>>(mapOf())
|
|
||||||
|
|
||||||
val fetchingItem = MutableLiveData<LoadingState>(LoadingState.Pending)
|
val fetchingItem = MutableLiveData<LoadingState>(LoadingState.Pending)
|
||||||
val fetchedItem = MutableLiveData<BaseItem?>(null)
|
val fetchedItem = MutableLiveData<BaseItem?>(null)
|
||||||
|
|
||||||
private val range = 8
|
private val range = 150
|
||||||
// val fetchedRange = MutableLiveData<IntRange>(0..<range)
|
|
||||||
|
|
||||||
fun init(firstLoad: Boolean) {
|
fun init(firstLoad: Boolean) {
|
||||||
start = LocalDateTime.now().truncatedTo(ChronoUnit.HOURS)
|
start = LocalDateTime.now().truncatedTo(ChronoUnit.HOURS)
|
||||||
|
|
@ -115,7 +113,7 @@ class LiveTvViewModel
|
||||||
) = mutex.withLock {
|
) = mutex.withLock {
|
||||||
val maxStartDate = start.plusHours(MAX_HOURS).minusMinutes(1)
|
val maxStartDate = start.plusHours(MAX_HOURS).minusMinutes(1)
|
||||||
val minEndDate = start.plusMinutes(1L)
|
val minEndDate = start.plusMinutes(1L)
|
||||||
val channelsToFetch = channels.subList(range.first, range.last)
|
val channelsToFetch = channels.subList(range.first, range.last + 1)
|
||||||
Timber.v("Fetching programs for $range channels ${channelsToFetch.size}")
|
Timber.v("Fetching programs for $range channels ${channelsToFetch.size}")
|
||||||
val request =
|
val request =
|
||||||
GetProgramsDto(
|
GetProgramsDto(
|
||||||
|
|
@ -199,20 +197,13 @@ class LiveTvViewModel
|
||||||
}
|
}
|
||||||
val finalProgramList =
|
val finalProgramList =
|
||||||
(programs + fake)
|
(programs + fake)
|
||||||
.filterNot {
|
.sortedWith(
|
||||||
// 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 =
|
this@LiveTvViewModel.programs.value =
|
||||||
FetchedPrograms(range, finalProgramList, finalProgramsByChannel)
|
FetchedPrograms(range, finalProgramList, finalProgramsByChannel)
|
||||||
|
|
@ -251,11 +242,10 @@ class LiveTvViewModel
|
||||||
viewModelScope.launchIO(ExceptionHandler(autoToast = true)) {
|
viewModelScope.launchIO(ExceptionHandler(autoToast = true)) {
|
||||||
if (series) {
|
if (series) {
|
||||||
api.liveTvApi.cancelSeriesTimer(timerId)
|
api.liveTvApi.cancelSeriesTimer(timerId)
|
||||||
fetchProgramsWithLoading(channels.value.orEmpty(), programs.value!!.range)
|
|
||||||
} else {
|
} else {
|
||||||
api.liveTvApi.cancelTimer(timerId)
|
api.liveTvApi.cancelTimer(timerId)
|
||||||
refreshProgram(programIndex, programId)
|
|
||||||
}
|
}
|
||||||
|
fetchProgramsWithLoading(channels.value.orEmpty(), programs.value!!.range)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -269,7 +259,6 @@ class LiveTvViewModel
|
||||||
val d by api.liveTvApi.getDefaultTimer(programId.toServerString())
|
val d by api.liveTvApi.getDefaultTimer(programId.toServerString())
|
||||||
if (series) {
|
if (series) {
|
||||||
api.liveTvApi.createSeriesTimer(d)
|
api.liveTvApi.createSeriesTimer(d)
|
||||||
fetchProgramsWithLoading(channels.value.orEmpty(), programs.value!!.range)
|
|
||||||
} else {
|
} else {
|
||||||
val payload =
|
val payload =
|
||||||
TimerInfoDto(
|
TimerInfoDto(
|
||||||
|
|
@ -295,8 +284,8 @@ class LiveTvViewModel
|
||||||
keepUntil = d.keepUntil,
|
keepUntil = d.keepUntil,
|
||||||
)
|
)
|
||||||
api.liveTvApi.createTimer(payload)
|
api.liveTvApi.createTimer(payload)
|
||||||
refreshProgram(programIndex, programId)
|
|
||||||
}
|
}
|
||||||
|
fetchProgramsWithLoading(channels.value.orEmpty(), programs.value!!.range)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -325,17 +314,33 @@ class LiveTvViewModel
|
||||||
loading.setValueOnMain(LoadingState.Success)
|
loading.setValueOnMain(LoadingState.Success)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onFocusChannel(position: RowColumn): Job? {
|
private var focusLoadingJob: Job? = null
|
||||||
return channels.value?.let { channels ->
|
|
||||||
|
fun onFocusChannel(position: RowColumn) {
|
||||||
|
channels.value?.let { channels ->
|
||||||
val fetchedRange = programs.value!!.range
|
val fetchedRange = programs.value!!.range
|
||||||
val quarter = (fetchedRange.last - fetchedRange.start) / 4
|
val quarter = range / 4
|
||||||
val rangeStart = fetchedRange.start + quarter
|
var rangeStart = fetchedRange.start + quarter
|
||||||
val rangeEnd = fetchedRange.last - quarter
|
var rangeEnd = fetchedRange.last - quarter
|
||||||
|
|
||||||
|
if (rangeEnd - rangeStart < range) {
|
||||||
|
if (position.row < range / 2) {
|
||||||
|
// Close to beginning
|
||||||
|
rangeStart = 0
|
||||||
|
} else if (position.row > (channels.size - range / 2)) {
|
||||||
|
// Close to the end
|
||||||
|
rangeEnd = channels.size
|
||||||
|
}
|
||||||
|
}
|
||||||
val testRange = rangeStart..<rangeEnd
|
val testRange = rangeStart..<rangeEnd
|
||||||
|
|
||||||
// Timber.v(
|
Timber.v(
|
||||||
// "onFocusChannel: position=$position, fetchedRange=$fetchedRange, testRange=$testRange",
|
"onFocusChannel: position=%s, fetchedRange=%s, testRange=%s",
|
||||||
// )
|
position,
|
||||||
|
fetchedRange,
|
||||||
|
testRange,
|
||||||
|
)
|
||||||
|
|
||||||
val fetchStart = (position.row - range).coerceAtLeast(0)
|
val fetchStart = (position.row - range).coerceAtLeast(0)
|
||||||
val fetchEnd = (position.row + range).coerceAtMost(channels.size)
|
val fetchEnd = (position.row + range).coerceAtMost(channels.size)
|
||||||
val newFetchRange = fetchStart..<fetchEnd
|
val newFetchRange = fetchStart..<fetchEnd
|
||||||
|
|
@ -344,11 +349,12 @@ class LiveTvViewModel
|
||||||
// Fetch new data
|
// Fetch new data
|
||||||
if (position.row !in testRange && !newFetchRange.within(fetchedRange)) {
|
if (position.row !in testRange && !newFetchRange.within(fetchedRange)) {
|
||||||
Timber.v("Loading more programs for channels $newFetchRange")
|
Timber.v("Loading more programs for channels $newFetchRange")
|
||||||
return viewModelScope.launchIO {
|
focusLoadingJob?.cancel()
|
||||||
|
focusLoadingJob =
|
||||||
|
viewModelScope.launchIO {
|
||||||
fetchProgramsWithLoading(channels, newFetchRange)
|
fetchProgramsWithLoading(channels, newFetchRange)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,6 @@ import com.github.damontecres.wholphin.ui.components.LoadingPage
|
||||||
import com.github.damontecres.wholphin.ui.data.RowColumn
|
import com.github.damontecres.wholphin.ui.data.RowColumn
|
||||||
import com.github.damontecres.wholphin.ui.enableMarquee
|
import com.github.damontecres.wholphin.ui.enableMarquee
|
||||||
import com.github.damontecres.wholphin.ui.nav.Destination
|
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.rememberPosition
|
||||||
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
||||||
import com.github.damontecres.wholphin.util.ExceptionHandler
|
import com.github.damontecres.wholphin.util.ExceptionHandler
|
||||||
|
|
@ -65,6 +64,7 @@ import timber.log.Timber
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import java.time.OffsetDateTime
|
import java.time.OffsetDateTime
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
import kotlin.math.abs
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun TvGuideGrid(
|
fun TvGuideGrid(
|
||||||
|
|
@ -254,10 +254,6 @@ fun TvGuideGrid(
|
||||||
if (it.type == KeyEventType.KeyUp) {
|
if (it.type == KeyEventType.KeyUp) {
|
||||||
return@onPreviewKeyEvent false
|
return@onPreviewKeyEvent false
|
||||||
}
|
}
|
||||||
if (isDpad(it) && loading) {
|
|
||||||
// Prevent movement during loading
|
|
||||||
return@onPreviewKeyEvent true
|
|
||||||
}
|
|
||||||
val item = focusedItem
|
val item = focusedItem
|
||||||
val newFocusedItem =
|
val newFocusedItem =
|
||||||
when (it.key) {
|
when (it.key) {
|
||||||
|
|
@ -304,12 +300,16 @@ fun TvGuideGrid(
|
||||||
val currentChannel = channels[item.row].id
|
val currentChannel = channels[item.row].id
|
||||||
val currentProgram =
|
val currentProgram =
|
||||||
programs.programsByChannel[currentChannel]?.get(item.column)
|
programs.programsByChannel[currentChannel]?.get(item.column)
|
||||||
|
val newChannelId = channels[newChannelIndex].id
|
||||||
|
val newChannelPrograms =
|
||||||
|
programs.programsByChannel[newChannelId]
|
||||||
|
if (newChannelPrograms == null) {
|
||||||
|
return@onPreviewKeyEvent true
|
||||||
|
}
|
||||||
if (currentProgram == null) {
|
if (currentProgram == null) {
|
||||||
item
|
item
|
||||||
} else {
|
} else {
|
||||||
val start = currentProgram.startHours
|
val start = currentProgram.startHours
|
||||||
val newChannelPrograms =
|
|
||||||
programs.programsByChannel[channels[newChannelIndex].id].orEmpty()
|
|
||||||
val pIndex =
|
val pIndex =
|
||||||
newChannelPrograms.indexOfFirst { start in (it.startHours..<it.endHours) }
|
newChannelPrograms.indexOfFirst { start in (it.startHours..<it.endHours) }
|
||||||
if (pIndex >= 0) {
|
if (pIndex >= 0) {
|
||||||
|
|
@ -335,11 +335,20 @@ fun TvGuideGrid(
|
||||||
// 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) {
|
} else {
|
||||||
|
// Otherwise, moving to a new row
|
||||||
|
// Get the new row/channel's programs
|
||||||
|
val newChannelId = channels[newChannelIndex].id
|
||||||
|
val newChannelPrograms =
|
||||||
|
programs.programsByChannel[newChannelId]
|
||||||
|
if (newChannelPrograms == null) {
|
||||||
|
// This means there is no data for the new channel and it is loading in the background
|
||||||
|
return@onPreviewKeyEvent true
|
||||||
|
}
|
||||||
|
if (channelColumnFocused) {
|
||||||
// If focused on the channel column, move down a channel
|
// If focused on the channel column, move down a channel
|
||||||
RowColumn(newChannelIndex, 0)
|
RowColumn(newChannelIndex, 0)
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, moving to a new row
|
|
||||||
// Get current program & its start time
|
// Get current program & its start time
|
||||||
val currentChannel = channels[item.row].id
|
val currentChannel = channels[item.row].id
|
||||||
val currentProgram =
|
val currentProgram =
|
||||||
|
|
@ -349,9 +358,6 @@ fun TvGuideGrid(
|
||||||
item
|
item
|
||||||
} else {
|
} else {
|
||||||
val start = currentProgram.startHours
|
val start = currentProgram.startHours
|
||||||
// Get the new row/channel's programs
|
|
||||||
val newChannelPrograms =
|
|
||||||
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
|
// Find the first program where the start time (of the previously focused program) is in the middle of a program
|
||||||
val pIndex =
|
val pIndex =
|
||||||
newChannelPrograms.indexOfFirst { start in (it.startHours..<it.endHours) }
|
newChannelPrograms.indexOfFirst { start in (it.startHours..<it.endHours) }
|
||||||
|
|
@ -377,6 +383,7 @@ fun TvGuideGrid(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Key.DirectionCenter, Key.Enter, Key.NumPadEnter -> {
|
Key.DirectionCenter, Key.Enter, Key.NumPadEnter -> {
|
||||||
if (channelColumnFocused) {
|
if (channelColumnFocused) {
|
||||||
|
|
@ -416,7 +423,7 @@ fun TvGuideGrid(
|
||||||
column =
|
column =
|
||||||
newFocusedItem.column.coerceIn(
|
newFocusedItem.column.coerceIn(
|
||||||
0,
|
0,
|
||||||
programs.size - 1,
|
(programs.size - 1).coerceAtLeast(0),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
focusedItem = toFocus
|
focusedItem = toFocus
|
||||||
|
|
@ -486,7 +493,14 @@ fun TvGuideGrid(
|
||||||
layoutInfo = { programIndex ->
|
layoutInfo = { programIndex ->
|
||||||
val program = programs.programs[programIndex]
|
val program = programs.programs[programIndex]
|
||||||
val channelIndex = channels.indexOfFirst { it.id == program.channelId }
|
val channelIndex = channels.indexOfFirst { it.id == program.channelId }
|
||||||
ProgramGuideItem.Program(channelIndex, program.startHours, program.endHours)
|
// Using the duration for endHour accounts for daylight savings switch
|
||||||
|
// Eg a 1:30am-1:00am show
|
||||||
|
val duration = abs(program.endHours - program.startHours)
|
||||||
|
ProgramGuideItem.Program(
|
||||||
|
channelIndex,
|
||||||
|
program.startHours,
|
||||||
|
program.startHours + duration,
|
||||||
|
)
|
||||||
},
|
},
|
||||||
) { programIndex ->
|
) { programIndex ->
|
||||||
val program = programs.programs[programIndex]
|
val program = programs.programs[programIndex]
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue