mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 15:50:16 +02:00
WIP
This commit is contained in:
parent
b288f0f100
commit
841b4a0907
2 changed files with 85 additions and 13 deletions
|
|
@ -17,6 +17,7 @@ import com.github.damontecres.wholphin.util.LoadingExceptionHandler
|
|||
import com.github.damontecres.wholphin.util.LoadingState
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
|
|
@ -29,7 +30,6 @@ import org.jellyfin.sdk.model.api.ImageType
|
|||
import org.jellyfin.sdk.model.api.ItemSortBy
|
||||
import org.jellyfin.sdk.model.api.TimerInfoDto
|
||||
import org.jellyfin.sdk.model.api.request.GetLiveTvChannelsRequest
|
||||
import org.jellyfin.sdk.model.api.request.GetLiveTvProgramsRequest
|
||||
import org.jellyfin.sdk.model.extensions.ticks
|
||||
import timber.log.Timber
|
||||
import java.time.LocalDateTime
|
||||
|
|
@ -39,7 +39,7 @@ import javax.inject.Inject
|
|||
import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
const val MAX_HOURS = 48L
|
||||
const val MAX_HOURS = 3L
|
||||
|
||||
@HiltViewModel
|
||||
class LiveTvViewModel
|
||||
|
|
@ -56,12 +56,18 @@ class LiveTvViewModel
|
|||
private val mutex = Mutex()
|
||||
|
||||
val channels = MutableLiveData<List<TvChannel>>()
|
||||
val channelProgramCount = mutableMapOf<UUID, Int>()
|
||||
val programs = MutableLiveData<List<TvProgram>>()
|
||||
val programsByChannel = MutableLiveData<Map<UUID, List<TvProgram>>>(mapOf())
|
||||
|
||||
val fetchingItem = MutableLiveData<LoadingState>(LoadingState.Pending)
|
||||
val fetchedItem = MutableLiveData<BaseItem?>(null)
|
||||
|
||||
val offset = MutableLiveData(0)
|
||||
val programOffset = MutableLiveData(0)
|
||||
private val range = 10
|
||||
private var currentIndex = 0
|
||||
|
||||
fun init(firstLoad: Boolean) {
|
||||
start = LocalDateTime.now().truncatedTo(ChronoUnit.HOURS)
|
||||
if (!firstLoad) {
|
||||
|
|
@ -86,7 +92,7 @@ class LiveTvViewModel
|
|||
Timber.d("Got ${channels.size} channels")
|
||||
channelsIdToIndex =
|
||||
channels.withIndex().associateBy({ it.value.id }, { it.index })
|
||||
fetchPrograms(channels)
|
||||
fetchPrograms(channels.subList(0, range.coerceAtMost(channels.size)))
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
this@LiveTvViewModel.channels.value = channels
|
||||
|
|
@ -105,9 +111,9 @@ 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 request =
|
||||
GetLiveTvProgramsRequest(
|
||||
GetProgramsDto(
|
||||
maxStartDate = maxStartDate,
|
||||
minEndDate = minEndDate,
|
||||
channelIds = channels.map { it.id },
|
||||
|
|
@ -115,7 +121,7 @@ class LiveTvViewModel
|
|||
)
|
||||
val programs =
|
||||
api.liveTvApi
|
||||
.getLiveTvPrograms(request)
|
||||
.getPrograms(request)
|
||||
.content.items
|
||||
.map { dto ->
|
||||
val category =
|
||||
|
|
@ -182,6 +188,9 @@ class LiveTvViewModel
|
|||
fake.addAll(fakePrograms)
|
||||
}
|
||||
}
|
||||
finalProgramsByChannel.forEach { (channelId, programs) ->
|
||||
channelProgramCount[channelId] = programs.size
|
||||
}
|
||||
val finalProgramList =
|
||||
(programs + fake).sortedWith(
|
||||
compareBy(
|
||||
|
|
@ -300,6 +309,34 @@ class LiveTvViewModel
|
|||
}
|
||||
loading.setValueOnMain(LoadingState.Success)
|
||||
}
|
||||
|
||||
fun onFocusChannel(index: Int): Job? {
|
||||
return channels.value?.let { channels ->
|
||||
val offset = offset.value!!
|
||||
val absoluteIndex = offset + index
|
||||
val rangeStart = (currentIndex - range / 2).coerceAtLeast(0)
|
||||
val rangeEnd = (currentIndex + range / 2).coerceAtMost(channels.size)
|
||||
Timber.v(
|
||||
"onFocusChannel: index=$index, currentIndex=$currentIndex, offset=$offset, rangeStart=$rangeStart, rangeEnd=$rangeEnd",
|
||||
)
|
||||
if (absoluteIndex !in (rangeStart..<rangeEnd)) {
|
||||
val fetchStart = (absoluteIndex - range).coerceAtLeast(0)
|
||||
val fetchEnd = (absoluteIndex + range).coerceAtMost(channels.size)
|
||||
Timber.v("Loading more programs for channels $fetchStart=>$fetchEnd")
|
||||
return viewModelScope.launchIO {
|
||||
fetchPrograms(channels.subList(fetchStart, fetchEnd))
|
||||
val programOffset =
|
||||
(offset..<fetchStart).sumOf { channelProgramCount[channels[it].id]!! }
|
||||
withContext(Dispatchers.Main) {
|
||||
this@LiveTvViewModel.offset.value = fetchStart
|
||||
currentIndex = index
|
||||
this@LiveTvViewModel.programOffset.value = programOffset
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -81,6 +81,7 @@ fun TvGuideGrid(
|
|||
val channels by viewModel.channels.observeAsState(listOf())
|
||||
val programs by viewModel.programs.observeAsState(listOf())
|
||||
val programsByChannel by viewModel.programsByChannel.observeAsState(mapOf())
|
||||
val programOffset by viewModel.programOffset.observeAsState(0)
|
||||
when (val state = loading) {
|
||||
is LoadingState.Error -> ErrorMessage(state, modifier)
|
||||
LoadingState.Pending,
|
||||
|
|
@ -105,7 +106,9 @@ fun TvGuideGrid(
|
|||
channels = channels,
|
||||
programList = programs,
|
||||
programs = programsByChannel,
|
||||
channelProgramCount = viewModel.channelProgramCount,
|
||||
start = viewModel.start,
|
||||
programOffset = programOffset,
|
||||
onClickChannel = { index, channel ->
|
||||
viewModel.navigationManager.navigateTo(
|
||||
Destination.Playback(
|
||||
|
|
@ -114,6 +117,9 @@ fun TvGuideGrid(
|
|||
),
|
||||
)
|
||||
},
|
||||
onFocusChannel = { index ->
|
||||
viewModel.onFocusChannel(index)
|
||||
},
|
||||
onClickProgram = { index, program ->
|
||||
if (program.isFake) {
|
||||
val now = LocalDateTime.now()
|
||||
|
|
@ -194,9 +200,12 @@ fun TvGuideGrid(
|
|||
channels: List<TvChannel>,
|
||||
programList: List<TvProgram>,
|
||||
programs: Map<UUID, List<TvProgram>>,
|
||||
channelProgramCount: Map<UUID, Int>,
|
||||
start: LocalDateTime,
|
||||
onClickChannel: (Int, TvChannel) -> Unit,
|
||||
onClickProgram: (Int, TvProgram) -> Unit,
|
||||
onFocusChannel: (Int) -> Unit,
|
||||
programOffset: Int,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
|
@ -223,13 +232,25 @@ fun TvGuideGrid(
|
|||
.build<Int, Int>(
|
||||
object : CacheLoader<Int, Int>() {
|
||||
override fun load(key: Int): Int =
|
||||
(
|
||||
channels
|
||||
.subList(0, key)
|
||||
.map { it.id }
|
||||
.sumOf { programs[it]?.size ?: 0 }
|
||||
.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 channelColumnFocused by rememberSaveable { mutableStateOf(false) }
|
||||
|
|
@ -247,6 +268,9 @@ fun TvGuideGrid(
|
|||
if (it.type == KeyEventType.KeyUp) {
|
||||
return@onPreviewKeyEvent false
|
||||
}
|
||||
if (!movementEnabled) {
|
||||
return@onPreviewKeyEvent true
|
||||
}
|
||||
val newIndex =
|
||||
when (it.key) {
|
||||
Key.Back -> {
|
||||
|
|
@ -328,7 +352,10 @@ fun TvGuideGrid(
|
|||
if (pIndex >= 0) {
|
||||
programsBeforeChannel.get(focusedChannelIndex) + pIndex
|
||||
} else {
|
||||
programsBeforeChannel.get(focusedChannelIndex) + programs[channelId]!!.size
|
||||
programsBeforeChannel.get(focusedChannelIndex) + (
|
||||
programs[channelId]?.size
|
||||
?: 0
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -371,7 +398,10 @@ fun TvGuideGrid(
|
|||
programsBeforeChannel.get(focusedChannelIndex) + pIndex
|
||||
} else {
|
||||
// Did not find one, so focus on the final program in the list
|
||||
programsBeforeChannel.get(focusedChannelIndex) + programs[channelId]!!.size
|
||||
programsBeforeChannel.get(focusedChannelIndex) + (
|
||||
programs[channelId]?.size
|
||||
?: 0
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -399,11 +429,16 @@ fun TvGuideGrid(
|
|||
if (newIndex >= 0) {
|
||||
val before = programsBeforeChannel.get(focusedChannelIndex)
|
||||
val max =
|
||||
before + channels[focusedChannelIndex].let { programs[it.id]!!.size } - 1
|
||||
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 {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue