mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 23:51:21 +02:00
Minimal UI implementation
This commit is contained in:
parent
24763d6d8c
commit
8af376cf63
2 changed files with 301 additions and 107 deletions
|
|
@ -4,10 +4,7 @@ import androidx.lifecycle.MutableLiveData
|
|||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.github.damontecres.wholphin.ui.detail.series.SeasonEpisode
|
||||
import com.github.damontecres.wholphin.util.ApiRequestPager
|
||||
import com.github.damontecres.wholphin.util.ExceptionHandler
|
||||
import com.github.damontecres.wholphin.util.GetProgramsDtoHandler
|
||||
import com.github.damontecres.wholphin.util.LazyList
|
||||
import com.github.damontecres.wholphin.util.LoadingExceptionHandler
|
||||
import com.github.damontecres.wholphin.util.LoadingState
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
|
|
@ -19,14 +16,19 @@ import org.jellyfin.sdk.api.client.extensions.imageApi
|
|||
import org.jellyfin.sdk.api.client.extensions.liveTvApi
|
||||
import org.jellyfin.sdk.model.api.GetProgramsDto
|
||||
import org.jellyfin.sdk.model.api.ImageType
|
||||
import org.jellyfin.sdk.model.api.ItemSortBy
|
||||
import org.jellyfin.sdk.model.api.request.GetLiveTvChannelsRequest
|
||||
import org.jellyfin.sdk.model.extensions.ticks
|
||||
import timber.log.Timber
|
||||
import java.time.LocalDateTime
|
||||
import java.util.AbstractList
|
||||
import java.time.ZoneOffset
|
||||
import java.time.temporal.ChronoUnit
|
||||
import java.util.UUID
|
||||
import javax.inject.Inject
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.minutes
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
const val MAX_HOURS = 24L
|
||||
|
||||
@HiltViewModel
|
||||
class LiveTvViewModel
|
||||
|
|
@ -36,9 +38,12 @@ class LiveTvViewModel
|
|||
) : ViewModel() {
|
||||
val loading = MutableLiveData<LoadingState>(LoadingState.Pending)
|
||||
|
||||
val start = LocalDateTime.now()
|
||||
val start =
|
||||
LocalDateTime.now().minusMinutes(30).truncatedTo(ChronoUnit.HOURS)
|
||||
|
||||
val channels = MutableLiveData<List<TvChannel>>()
|
||||
val programs = MutableLiveData<List<TvProgram?>>()
|
||||
val programs = MutableLiveData<List<TvProgram>>()
|
||||
val programsByChannel = MutableLiveData<Map<UUID, List<TvProgram>>>(mapOf())
|
||||
|
||||
private val programMap = mutableMapOf<UUID, MutableLiveData<List<TvProgram?>>>()
|
||||
|
||||
|
|
@ -47,101 +52,180 @@ class LiveTvViewModel
|
|||
viewModelScope.launch(Dispatchers.IO + LoadingExceptionHandler(loading, "Could not fetch channels")) {
|
||||
val channelData by api.liveTvApi.getLiveTvChannels(
|
||||
GetLiveTvChannelsRequest(
|
||||
startIndex = 8,
|
||||
startIndex = 0,
|
||||
),
|
||||
)
|
||||
val channels =
|
||||
LazyList(channelData.items) {
|
||||
TvChannel(
|
||||
it.id,
|
||||
it.channelNumber,
|
||||
it.channelName,
|
||||
api.imageApi.getItemImageUrl(it.id, ImageType.PRIMARY),
|
||||
)
|
||||
}
|
||||
channelData.items
|
||||
.map {
|
||||
TvChannel(
|
||||
it.id,
|
||||
it.channelNumber,
|
||||
it.channelName,
|
||||
api.imageApi.getItemImageUrl(it.id, ImageType.PRIMARY),
|
||||
)
|
||||
}
|
||||
Timber.d("Got ${channels.size} channels")
|
||||
val channelsIdToIndex =
|
||||
channels.withIndex().associateBy({ it.value.id }, { it.index })
|
||||
val maxStartDate = start.plusHours(MAX_HOURS).minusMinutes(1)
|
||||
val minEndDate = start.plusMinutes(1L)
|
||||
val request =
|
||||
GetProgramsDto(
|
||||
minStartDate = start,
|
||||
maxStartDate = maxStartDate,
|
||||
minEndDate = minEndDate,
|
||||
// maxEndDate = start.plusHours(25),
|
||||
channelIds = channels.map { it.id },
|
||||
sortBy = listOf(ItemSortBy.START_DATE),
|
||||
)
|
||||
val pager = ApiRequestPager(api, request, GetProgramsDtoHandler, viewModelScope).init()
|
||||
(0..<pager.size).forEach { pager.getBlocking(it) }
|
||||
// val pager = ApiRequestPager(api, request, GetProgramsDtoHandler, viewModelScope).init()
|
||||
// (0..<pager.size).forEach { pager.getBlocking(it) }
|
||||
val programs =
|
||||
LazyList(pager) { item ->
|
||||
item?.data?.let { dto ->
|
||||
GetProgramsDtoHandler
|
||||
.execute(api, request)
|
||||
.content.items
|
||||
.map { dto ->
|
||||
val utcStart = dto.startDate!!.toEpochSecond(ZoneOffset.UTC)
|
||||
utcStart
|
||||
val ttt =
|
||||
java.time.Duration
|
||||
.between(
|
||||
start,
|
||||
dto.startDate!!,
|
||||
)
|
||||
ttt
|
||||
TvProgram(
|
||||
id = dto.id,
|
||||
channelId = dto.channelId!!,
|
||||
start = dto.startDate!!,
|
||||
end = dto.endDate!!,
|
||||
startHours =
|
||||
java.time.Duration
|
||||
.between(start, dto.startDate!!)
|
||||
.seconds / (60f * 60f),
|
||||
// dto.startDate!!.hours +
|
||||
// (
|
||||
// java.time.Duration
|
||||
// .between(
|
||||
// start,
|
||||
// dto.startDate!!,
|
||||
// ).toDaysPart() / 24
|
||||
// ).coerceAtLeast(0),
|
||||
endHours =
|
||||
java.time.Duration
|
||||
.between(start, dto.endDate!!)
|
||||
.seconds / (60f * 60f),
|
||||
// dto.endDate!!.hours +
|
||||
// (
|
||||
// java.time.Duration
|
||||
// .between(
|
||||
// start,
|
||||
// dto.endDate!!,
|
||||
// ).toHours() / 24
|
||||
// ).coerceAtLeast(0),
|
||||
duration = dto.runTimeTicks!!.ticks,
|
||||
name = dto.seriesName ?: dto.name,
|
||||
subtitle = dto.name.takeIf { dto.seriesName.isNullOrBlank() },
|
||||
seasonEpisode = null, // TODO
|
||||
)
|
||||
}.filter { it.startHours >= 0 && it.endHours >= 0 } // TODO shouldn't need to filter client side
|
||||
|
||||
val programsByChannel = programs.groupBy { it.channelId }
|
||||
val emptyChannels = channels.filter { programsByChannel[it.id].orEmpty().isEmpty() }
|
||||
val fake = mutableListOf<TvProgram>()
|
||||
val finalProgramsByChannel =
|
||||
programsByChannel.toMutableMap().apply {
|
||||
emptyChannels.forEach { channel ->
|
||||
val fakePrograms =
|
||||
(0..<MAX_HOURS).map {
|
||||
TvProgram(
|
||||
id = UUID.randomUUID(), // TODO
|
||||
channelId = channel.id,
|
||||
start = start.plusHours(it),
|
||||
end = start.plusHours(it + 1),
|
||||
startHours = it.toFloat(),
|
||||
endHours = (it + 1).toFloat(),
|
||||
duration = 60.seconds,
|
||||
name = "No date",
|
||||
subtitle = null,
|
||||
seasonEpisode = null,
|
||||
)
|
||||
}
|
||||
put(channel.id, fakePrograms)
|
||||
fake.addAll(fakePrograms)
|
||||
}
|
||||
}
|
||||
val finalProgramList =
|
||||
(programs + fake).sortedWith(
|
||||
compareBy(
|
||||
{ channelsIdToIndex[it.channelId]!! },
|
||||
{ it.start },
|
||||
),
|
||||
)
|
||||
|
||||
Timber.d("Got ${programs.size} programs & ${fake.size} fake programs")
|
||||
withContext(Dispatchers.Main) {
|
||||
this@LiveTvViewModel.channels.value = channels
|
||||
this@LiveTvViewModel.programs.value = programs
|
||||
this@LiveTvViewModel.programs.value = finalProgramList
|
||||
this@LiveTvViewModel.programsByChannel.value = finalProgramsByChannel
|
||||
loading.value = LoadingState.Success
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getPrograms(channelId: UUID): MutableLiveData<List<TvProgram?>> =
|
||||
programMap.getOrPut(channelId) {
|
||||
val data = MutableLiveData<List<TvProgram?>>(listOf())
|
||||
viewModelScope.launch(Dispatchers.IO + ExceptionHandler()) {
|
||||
val request =
|
||||
GetProgramsDto(
|
||||
minStartDate = start,
|
||||
channelIds = listOf(channelId),
|
||||
)
|
||||
val pager = ApiRequestPager(api, request, GetProgramsDtoHandler, viewModelScope).init()
|
||||
val programList =
|
||||
if (pager.isEmpty()) {
|
||||
object : AbstractList<TvProgram?>() {
|
||||
override fun get(index: Int): TvProgram? {
|
||||
val start = start.plusHours(index.toLong())
|
||||
val end = start.plusHours(1L)
|
||||
return TvProgram(
|
||||
id = UUID.randomUUID(),
|
||||
channelId = channelId,
|
||||
start = start,
|
||||
end = start,
|
||||
duration = 60.minutes,
|
||||
name = "Unknown",
|
||||
subtitle = null,
|
||||
seasonEpisode = null, // TODO
|
||||
)
|
||||
}
|
||||
|
||||
override val size: Int
|
||||
get() = Int.MAX_VALUE
|
||||
}
|
||||
} else {
|
||||
LazyList(pager) { item ->
|
||||
item?.data?.let { dto ->
|
||||
TvProgram(
|
||||
id = dto.id,
|
||||
channelId = channelId,
|
||||
start = dto.startDate!!,
|
||||
end = dto.endDate!!,
|
||||
duration = dto.runTimeTicks!!.ticks,
|
||||
name = dto.seriesName ?: dto.name,
|
||||
subtitle = dto.name.takeIf { dto.seriesName.isNullOrBlank() },
|
||||
seasonEpisode = null, // TODO
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
withContext(Dispatchers.Main) {
|
||||
data.value = programList
|
||||
}
|
||||
}
|
||||
data
|
||||
}
|
||||
// fun getPrograms(channelId: UUID): MutableLiveData<List<TvProgram?>> =
|
||||
// programMap.getOrPut(channelId) {
|
||||
// val data = MutableLiveData<List<TvProgram?>>(listOf())
|
||||
// viewModelScope.launch(Dispatchers.IO + ExceptionHandler()) {
|
||||
// val request =
|
||||
// GetProgramsDto(
|
||||
// minStartDate = start,
|
||||
// channelIds = listOf(channelId),
|
||||
// )
|
||||
// val pager = ApiRequestPager(api, request, GetProgramsDtoHandler, viewModelScope).init()
|
||||
// val programList =
|
||||
// if (pager.isEmpty()) {
|
||||
// object : AbstractList<TvProgram?>() {
|
||||
// override fun get(index: Int): TvProgram? {
|
||||
// val start = start.plusHours(index.toLong())
|
||||
// val end = start.plusHours(1L)
|
||||
// return TvProgram(
|
||||
// id = UUID.randomUUID(),
|
||||
// channelId = channelId,
|
||||
// start = start,
|
||||
// end = start,
|
||||
// duration = 60.minutes,
|
||||
// name = "Unknown",
|
||||
// subtitle = null,
|
||||
// seasonEpisode = null, // TODO
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// override val size: Int
|
||||
// get() = Int.MAX_VALUE
|
||||
// }
|
||||
// } else {
|
||||
// LazyList(pager) { item ->
|
||||
// item?.data?.let { dto ->
|
||||
// TvProgram(
|
||||
// id = dto.id,
|
||||
// channelId = channelId,
|
||||
// start = dto.startDate!!,
|
||||
// end = dto.endDate!!,
|
||||
// duration = dto.runTimeTicks!!.ticks,
|
||||
// name = dto.seriesName ?: dto.name,
|
||||
// subtitle = dto.name.takeIf { dto.seriesName.isNullOrBlank() },
|
||||
// seasonEpisode = null, // TODO
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// withContext(Dispatchers.Main) {
|
||||
// data.value = programList
|
||||
// }
|
||||
// }
|
||||
// data
|
||||
// }
|
||||
}
|
||||
|
||||
data class TvChannel(
|
||||
|
|
@ -156,6 +240,8 @@ data class TvProgram(
|
|||
val channelId: UUID,
|
||||
val start: LocalDateTime,
|
||||
val end: LocalDateTime,
|
||||
val startHours: Float,
|
||||
val endHours: Float,
|
||||
val duration: Duration,
|
||||
val name: String?,
|
||||
val subtitle: String?,
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import androidx.compose.foundation.layout.Column
|
|||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.width
|
||||
|
|
@ -16,6 +17,7 @@ import androidx.compose.foundation.lazy.itemsIndexed
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.runtime.mutableFloatStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
|
|
@ -23,6 +25,7 @@ import androidx.compose.runtime.rememberCoroutineScope
|
|||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusDirection
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusProperties
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
|
|
@ -34,19 +37,25 @@ import androidx.compose.ui.input.key.key
|
|||
import androidx.compose.ui.input.key.onKeyEvent
|
||||
import androidx.compose.ui.input.key.onPreviewKeyEvent
|
||||
import androidx.compose.ui.input.key.type
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.times
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.tv.material3.Button
|
||||
import androidx.tv.material3.ListItem
|
||||
import androidx.tv.material3.MaterialTheme
|
||||
import androidx.tv.material3.Text
|
||||
import androidx.tv.material3.surfaceColorAtElevation
|
||||
import coil3.compose.AsyncImage
|
||||
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
||||
import com.github.damontecres.wholphin.ui.components.LoadingPage
|
||||
import com.github.damontecres.wholphin.ui.ifElse
|
||||
import com.github.damontecres.wholphin.ui.playback.isDown
|
||||
import com.github.damontecres.wholphin.ui.playback.isUp
|
||||
import com.github.damontecres.wholphin.ui.rememberInt
|
||||
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
||||
import com.github.damontecres.wholphin.util.ExceptionHandler
|
||||
import com.github.damontecres.wholphin.util.LoadingState
|
||||
import eu.wewox.programguide.ProgramGuide
|
||||
import eu.wewox.programguide.ProgramGuideDimensions
|
||||
import eu.wewox.programguide.ProgramGuideItem
|
||||
|
|
@ -66,14 +75,57 @@ fun translate(
|
|||
programIndex - before
|
||||
}
|
||||
|
||||
fun programsBeforeChannel(channelIndex: Int): Int = channels.subList(0, channelIndex).map { it.id }.sumOf { programs[it]?.size ?: 0 }
|
||||
@Composable
|
||||
fun TvGrid(
|
||||
modifier: Modifier = Modifier,
|
||||
viewModel: LiveTvViewModel = hiltViewModel(),
|
||||
) {
|
||||
LaunchedEffect(Unit) {
|
||||
viewModel.init()
|
||||
}
|
||||
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())
|
||||
when (val state = loading) {
|
||||
is LoadingState.Error -> ErrorMessage(state)
|
||||
|
||||
LoadingState.Loading,
|
||||
LoadingState.Pending,
|
||||
-> LoadingPage()
|
||||
|
||||
LoadingState.Success ->
|
||||
Column(modifier = modifier) {
|
||||
Button(
|
||||
onClick = {},
|
||||
) {
|
||||
Text(
|
||||
text = "Button",
|
||||
)
|
||||
}
|
||||
TvGrid(
|
||||
channels = channels,
|
||||
programList = programs,
|
||||
programs = programsByChannel,
|
||||
start = viewModel.start,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TvGrid(modifier: Modifier = Modifier) {
|
||||
fun TvGrid(
|
||||
channels: List<TvChannel>,
|
||||
programList: List<TvProgram>,
|
||||
programs: Map<UUID, List<TvProgram>>,
|
||||
start: LocalDateTime,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
val state = rememberSaveableProgramGuideState()
|
||||
val scope = rememberCoroutineScope()
|
||||
val startHours = 6
|
||||
val timeline = 16..24
|
||||
// val timeline = 0..<24
|
||||
var focusedProgram by rememberInt(0)
|
||||
var focusedChannel by rememberInt(0)
|
||||
|
||||
|
|
@ -86,6 +138,8 @@ fun TvGrid(modifier: Modifier = Modifier) {
|
|||
currentTimeWidth = 2.dp,
|
||||
)
|
||||
|
||||
fun programsBeforeChannel(channelIndex: Int): Int = channels.subList(0, channelIndex).map { it.id }.sumOf { programs[it]?.size ?: 0 }
|
||||
|
||||
ProgramGuide(
|
||||
state = state,
|
||||
dimensions = dimensions,
|
||||
|
|
@ -101,12 +155,32 @@ fun TvGrid(modifier: Modifier = Modifier) {
|
|||
Key.DirectionRight -> focusedProgram + 1
|
||||
Key.DirectionLeft -> focusedProgram - 1
|
||||
Key.DirectionUp -> {
|
||||
val start = programList[focusedProgram].start.hours
|
||||
focusedChannel = (focusedChannel - 1).coerceAtLeast(0)
|
||||
val start = programList[focusedProgram].startHours
|
||||
focusedChannel = (focusedChannel - 1)
|
||||
if (focusedChannel < 0) {
|
||||
focusManager.moveFocus(FocusDirection.Up)
|
||||
null
|
||||
} else {
|
||||
val channelId = channels[focusedChannel].id
|
||||
val pIndex =
|
||||
programs[channelId]?.indexOfFirst { start in (it.startHours..<it.endHours) }
|
||||
?: -1
|
||||
if (pIndex >= 0) {
|
||||
programsBeforeChannel(focusedChannel) + pIndex
|
||||
} else {
|
||||
programsBeforeChannel(focusedChannel) + programs[channelId]!!.size
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Key.DirectionDown -> {
|
||||
val start = programList[focusedProgram].startHours
|
||||
focusedChannel =
|
||||
(focusedChannel + 1).coerceAtMost(channels.size - 1)
|
||||
val channelId = channels[focusedChannel].id
|
||||
val pro = programs[channelId].orEmpty()
|
||||
val pIndex =
|
||||
programs[channelId]?.indexOfFirst { start in (it.start.hours..<it.end.hours) }
|
||||
?: -1
|
||||
pro.indexOfFirst { start in (it.startHours..<it.endHours) }
|
||||
if (pIndex >= 0) {
|
||||
programsBeforeChannel(focusedChannel) + pIndex
|
||||
} else {
|
||||
|
|
@ -114,20 +188,9 @@ fun TvGrid(modifier: Modifier = Modifier) {
|
|||
}
|
||||
}
|
||||
|
||||
Key.DirectionDown -> {
|
||||
val start = programList[focusedProgram].start.hours
|
||||
focusedChannel =
|
||||
(focusedChannel + 1).coerceAtMost(channels.size - 1)
|
||||
val channelId = channels[focusedChannel].id
|
||||
val pro = programs[channelId]!!
|
||||
val pIndex =
|
||||
pro.indexOfFirst { start in (it.start.hours..<it.end.hours) }
|
||||
?: -1
|
||||
if (pIndex >= 0) {
|
||||
programsBeforeChannel(focusedChannel) + pIndex
|
||||
} else {
|
||||
programsBeforeChannel(focusedChannel) + programs[channelId]!!.size
|
||||
}
|
||||
Key.DirectionCenter, Key.Enter, Key.NumPadEnter -> {
|
||||
Timber.v("Clicked on ${programList[focusedProgram]}")
|
||||
null
|
||||
}
|
||||
|
||||
else -> {
|
||||
|
|
@ -148,20 +211,19 @@ fun TvGrid(modifier: Modifier = Modifier) {
|
|||
return@onPreviewKeyEvent false
|
||||
},
|
||||
) {
|
||||
guideStartHour = timeline.first.toFloat()
|
||||
guideStartHour = 0f
|
||||
timeline(
|
||||
count = timeline.count(),
|
||||
layoutInfo = {
|
||||
val start = timeline.toList()[it].toFloat()
|
||||
count = MAX_HOURS.toInt(),
|
||||
layoutInfo = { index ->
|
||||
ProgramGuideItem.Timeline(
|
||||
startHour = start,
|
||||
endHour = start + 1f,
|
||||
startHour = index.toFloat(),
|
||||
endHour = index + 1f,
|
||||
)
|
||||
},
|
||||
) { index ->
|
||||
val start = timeline.toList()[index].toFloat()
|
||||
val time = start.plusHours(index.toLong()).toLocalTime()
|
||||
Text(
|
||||
text = "$start o'clock",
|
||||
text = time.toString(),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -170,7 +232,7 @@ fun TvGrid(modifier: Modifier = Modifier) {
|
|||
layoutInfo = { programIndex ->
|
||||
val program = programList[programIndex]
|
||||
val channelIndex = channels.indexOfFirst { it.id == program.channelId }
|
||||
ProgramGuideItem.Program(channelIndex, program.start.hours, program.end.hours)
|
||||
ProgramGuideItem.Program(channelIndex, program.startHours, program.endHours)
|
||||
},
|
||||
) { programIndex ->
|
||||
val program = programList[programIndex]
|
||||
|
|
@ -191,7 +253,7 @@ fun TvGrid(modifier: Modifier = Modifier) {
|
|||
},
|
||||
) { channelIndex ->
|
||||
Text(
|
||||
text = channels[channelIndex].name ?: channelIndex.toString(),
|
||||
text = channels[channelIndex].number ?: channelIndex.toString(),
|
||||
modifier = Modifier.background(MaterialTheme.colorScheme.background),
|
||||
)
|
||||
}
|
||||
|
|
@ -442,6 +504,8 @@ val programs =
|
|||
channelId = channel1Id,
|
||||
start = LocalDateTime.of(2025, 10, 16, 18, 0, 0),
|
||||
end = LocalDateTime.of(2025, 10, 16, 19, 0, 0),
|
||||
startHours = 18f,
|
||||
endHours = 19f,
|
||||
duration = 60.minutes,
|
||||
name = "C1 Program #1",
|
||||
subtitle = null,
|
||||
|
|
@ -452,6 +516,8 @@ val programs =
|
|||
channelId = channel1Id,
|
||||
start = LocalDateTime.of(2025, 10, 16, 19, 0, 0),
|
||||
end = LocalDateTime.of(2025, 10, 16, 19, 30, 0),
|
||||
startHours = 19f,
|
||||
endHours = 19.5f,
|
||||
duration = 30.minutes,
|
||||
name = "C1 Program #2",
|
||||
subtitle = null,
|
||||
|
|
@ -462,6 +528,8 @@ val programs =
|
|||
channelId = channel1Id,
|
||||
start = LocalDateTime.of(2025, 10, 16, 19, 30, 0),
|
||||
end = LocalDateTime.of(2025, 10, 16, 20, 0, 0),
|
||||
startHours = 19.5f,
|
||||
endHours = 20f,
|
||||
duration = 30.minutes,
|
||||
name = "C1 Program #3",
|
||||
subtitle = null,
|
||||
|
|
@ -472,6 +540,8 @@ val programs =
|
|||
channelId = channel1Id,
|
||||
start = LocalDateTime.of(2025, 10, 16, 20, 0, 0, 0),
|
||||
end = LocalDateTime.of(2025, 10, 16, 21, 0, 0),
|
||||
startHours = 20f,
|
||||
endHours = 21f,
|
||||
duration = 60.minutes,
|
||||
name = "C1 Program #3",
|
||||
subtitle = null,
|
||||
|
|
@ -482,6 +552,8 @@ val programs =
|
|||
channelId = channel1Id,
|
||||
start = LocalDateTime.of(2025, 10, 16, 21, 0, 0, 0),
|
||||
end = LocalDateTime.of(2025, 10, 16, 22, 0, 0),
|
||||
startHours = 21f,
|
||||
endHours = 22f,
|
||||
duration = 60.minutes,
|
||||
name = "C1 Program #3",
|
||||
subtitle = null,
|
||||
|
|
@ -492,11 +564,25 @@ val programs =
|
|||
channelId = channel1Id,
|
||||
start = LocalDateTime.of(2025, 10, 16, 22, 0, 0, 0),
|
||||
end = LocalDateTime.of(2025, 10, 16, 23, 0, 0),
|
||||
startHours = 22f,
|
||||
endHours = 23f,
|
||||
duration = 60.minutes,
|
||||
name = "C1 Program #3",
|
||||
subtitle = null,
|
||||
seasonEpisode = null,
|
||||
),
|
||||
TvProgram(
|
||||
id = UUID.randomUUID(),
|
||||
channelId = channel1Id,
|
||||
start = LocalDateTime.of(2025, 10, 17, 1, 0, 0, 0),
|
||||
end = LocalDateTime.of(2025, 10, 17, 2, 0, 0),
|
||||
startHours = 25f,
|
||||
endHours = 26f,
|
||||
duration = 60.minutes,
|
||||
name = "C1 Program #4",
|
||||
subtitle = null,
|
||||
seasonEpisode = null,
|
||||
),
|
||||
),
|
||||
channel2Id to
|
||||
listOf(
|
||||
|
|
@ -505,6 +591,8 @@ val programs =
|
|||
channelId = channel2Id,
|
||||
start = LocalDateTime.of(2025, 10, 16, 18, 0, 0),
|
||||
end = LocalDateTime.of(2025, 10, 16, 18, 30, 0),
|
||||
startHours = 18f,
|
||||
endHours = 18.5f,
|
||||
duration = 30.minutes,
|
||||
name = "C2 Program #1",
|
||||
subtitle = null,
|
||||
|
|
@ -515,6 +603,8 @@ val programs =
|
|||
channelId = channel2Id,
|
||||
start = LocalDateTime.of(2025, 10, 16, 18, 30, 0),
|
||||
end = LocalDateTime.of(2025, 10, 16, 19, 30, 0),
|
||||
startHours = 18.5f,
|
||||
endHours = 19f,
|
||||
duration = 60.minutes,
|
||||
name = "C2 Program #2",
|
||||
subtitle = null,
|
||||
|
|
@ -525,6 +615,8 @@ val programs =
|
|||
channelId = channel2Id,
|
||||
start = LocalDateTime.of(2025, 10, 16, 19, 30, 0),
|
||||
end = LocalDateTime.of(2025, 10, 16, 20, 0, 0),
|
||||
startHours = 19.5f,
|
||||
endHours = 20f,
|
||||
duration = 30.minutes,
|
||||
name = "C2 Program #3",
|
||||
subtitle = null,
|
||||
|
|
@ -535,6 +627,8 @@ val programs =
|
|||
channelId = channel2Id,
|
||||
start = LocalDateTime.of(2025, 10, 16, 21, 0, 0, 0),
|
||||
end = LocalDateTime.of(2025, 10, 16, 22, 0, 0),
|
||||
startHours = 21f,
|
||||
endHours = 22f,
|
||||
duration = 60.minutes,
|
||||
name = "C2 Program #4",
|
||||
subtitle = null,
|
||||
|
|
@ -549,6 +643,8 @@ val programs =
|
|||
start = LocalDateTime.of(2025, 10, 16, 18, 0, 0),
|
||||
end = LocalDateTime.of(2025, 10, 16, 18, 15, 0),
|
||||
duration = 15.minutes,
|
||||
startHours = 18f,
|
||||
endHours = 18.25f,
|
||||
name = "C3 Program #1",
|
||||
subtitle = null,
|
||||
seasonEpisode = null,
|
||||
|
|
@ -558,6 +654,8 @@ val programs =
|
|||
channelId = channel3Id,
|
||||
start = LocalDateTime.of(2025, 10, 16, 18, 15, 0),
|
||||
end = LocalDateTime.of(2025, 10, 16, 18, 45, 0),
|
||||
startHours = 18.25f,
|
||||
endHours = 18.75f,
|
||||
duration = 30.minutes,
|
||||
name = "C3 Program #2",
|
||||
subtitle = null,
|
||||
|
|
@ -568,6 +666,8 @@ val programs =
|
|||
channelId = channel3Id,
|
||||
start = LocalDateTime.of(2025, 10, 16, 18, 45, 0),
|
||||
end = LocalDateTime.of(2025, 10, 16, 19, 0, 0),
|
||||
startHours = 18.75f,
|
||||
endHours = 19f,
|
||||
duration = 15.minutes,
|
||||
name = "C3 Program #3",
|
||||
subtitle = null,
|
||||
|
|
@ -578,6 +678,8 @@ val programs =
|
|||
channelId = channel3Id,
|
||||
start = LocalDateTime.of(2025, 10, 16, 19, 0, 0),
|
||||
end = LocalDateTime.of(2025, 10, 16, 20, 0, 0),
|
||||
startHours = 19f,
|
||||
endHours = 20f,
|
||||
duration = 60.minutes,
|
||||
name = "C3 Program #3",
|
||||
subtitle = null,
|
||||
|
|
@ -591,6 +693,8 @@ val programs =
|
|||
channelId = channel4Id,
|
||||
start = LocalDateTime.of(2025, 10, 16, 18, 0, 0),
|
||||
end = LocalDateTime.of(2025, 10, 16, 19, 0, 0),
|
||||
startHours = 18f,
|
||||
endHours = 19f,
|
||||
duration = 60.minutes,
|
||||
name = "C4 Program #1",
|
||||
subtitle = null,
|
||||
|
|
@ -601,6 +705,8 @@ val programs =
|
|||
channelId = channel4Id,
|
||||
start = LocalDateTime.of(2025, 10, 16, 19, 0, 0),
|
||||
end = LocalDateTime.of(2025, 10, 16, 19, 30, 0),
|
||||
startHours = 19f,
|
||||
endHours = 19.5f,
|
||||
duration = 30.minutes,
|
||||
name = "C4 Program #2",
|
||||
subtitle = null,
|
||||
|
|
@ -611,6 +717,8 @@ val programs =
|
|||
channelId = channel4Id,
|
||||
start = LocalDateTime.of(2025, 10, 16, 19, 30, 0),
|
||||
end = LocalDateTime.of(2025, 10, 16, 20, 0, 0),
|
||||
startHours = 19.5f,
|
||||
endHours = 20f,
|
||||
duration = 30.minutes,
|
||||
name = "C4 Program #3",
|
||||
subtitle = null,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue