Wire up live tv pages

This commit is contained in:
Damontecres 2025-10-20 17:00:24 -04:00
parent fb4e36168c
commit dc6a921693
No known key found for this signature in database
7 changed files with 413 additions and 106 deletions

View file

@ -0,0 +1,157 @@
package com.github.damontecres.wholphin.ui.detail
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.focusRestorer
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.unit.dp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.tv.material3.MaterialTheme
import androidx.tv.material3.Tab
import androidx.tv.material3.TabDefaults
import androidx.tv.material3.TabRow
import androidx.tv.material3.TabRowDefaults
import androidx.tv.material3.Text
import com.github.damontecres.wholphin.data.model.BaseItem
import com.github.damontecres.wholphin.preferences.UserPreferences
import com.github.damontecres.wholphin.preferences.rememberTab
import com.github.damontecres.wholphin.ui.components.ErrorMessage
import com.github.damontecres.wholphin.ui.detail.livetv.TvGuideGrid
import com.github.damontecres.wholphin.ui.ifElse
import com.github.damontecres.wholphin.ui.nav.Destination
import com.github.damontecres.wholphin.ui.preferences.PreferencesViewModel
import com.github.damontecres.wholphin.ui.tryRequestFocus
@Composable
fun CollectionFolderLiveTv(
preferences: UserPreferences,
destination: Destination.MediaItem,
modifier: Modifier = Modifier,
preferencesViewModel: PreferencesViewModel = hiltViewModel(),
) {
val uiPrefs = preferences.appPreferences.interfacePreferences
val rememberedTabIndex =
if (uiPrefs.rememberSelectedTab) {
uiPrefs.getRememberedTabsOrDefault(destination.itemId.toString(), 0)
} else {
0
}
val tabs = listOf("Guide", "DVR Schedule")
var focusTabIndex by rememberSaveable { mutableIntStateOf(rememberedTabIndex) }
var selectedTabIndex by rememberSaveable { mutableIntStateOf(rememberedTabIndex) }
val focusRequester = remember { FocusRequester() }
val firstTabFocusRequester = remember { FocusRequester() }
LaunchedEffect(Unit) { firstTabFocusRequester.tryRequestFocus() }
if (uiPrefs.rememberSelectedTab) {
LaunchedEffect(selectedTabIndex) {
preferencesViewModel.preferenceDataStore.updateData {
preferences.appPreferences.rememberTab(destination.itemId, selectedTabIndex)
}
}
}
val onClickItem = { item: BaseItem ->
preferencesViewModel.navigationManager.navigateTo(item.destination())
}
var showHeader by rememberSaveable { mutableStateOf(true) }
LaunchedEffect(Unit) { focusRequester.tryRequestFocus() }
Column(
modifier = modifier,
) {
AnimatedVisibility(
showHeader,
enter = slideInVertically() + fadeIn(),
exit = slideOutVertically() + fadeOut(),
) {
TabRow(
selectedTabIndex = focusTabIndex,
modifier =
Modifier
.padding(start = 32.dp, top = 16.dp, bottom = 16.dp)
.focusRestorer(firstTabFocusRequester)
.onFocusChanged {
if (!it.isFocused) {
focusTabIndex = selectedTabIndex
}
},
indicator =
@Composable { tabPositions, doesTabRowHaveFocus ->
tabPositions.getOrNull(focusTabIndex)?.let { currentTabPosition ->
// TabRowDefaults.PillIndicator(
// currentTabPosition = currentTabPosition,
// doesTabRowHaveFocus = doesTabRowHaveFocus,
// )
TabRowDefaults.UnderlinedIndicator(
currentTabPosition = currentTabPosition,
doesTabRowHaveFocus = doesTabRowHaveFocus,
activeColor = MaterialTheme.colorScheme.border,
)
}
},
tabs = {
tabs.forEachIndexed { index, title ->
Tab(
selected = focusTabIndex == index,
onClick = { selectedTabIndex = index },
onFocus = { focusTabIndex = index },
colors =
TabDefaults.pillIndicatorTabColors(
focusedContentColor = MaterialTheme.colorScheme.onSurfaceVariant,
),
) {
Text(
text = title,
style = MaterialTheme.typography.titleMedium,
modifier =
Modifier
.padding(8.dp)
.ifElse(
index == selectedTabIndex,
Modifier.focusRequester(firstTabFocusRequester),
),
)
}
}
},
)
}
when (selectedTabIndex) {
0 -> {
TvGuideGrid(
true,
Modifier
.fillMaxSize()
.focusRequester(focusRequester),
)
}
1 -> {
Text(
text = "Not yet implemented",
)
}
else -> ErrorMessage("Invalid tab index $selectedTabIndex", null)
}
}
}

View file

@ -3,9 +3,13 @@ package com.github.damontecres.wholphin.ui.detail.livetv
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.github.damontecres.wholphin.data.model.BaseItem
import com.github.damontecres.wholphin.ui.detail.series.SeasonEpisode import com.github.damontecres.wholphin.ui.detail.series.SeasonEpisode
import com.github.damontecres.wholphin.ui.isNotNullOrBlank import com.github.damontecres.wholphin.ui.isNotNullOrBlank
import com.github.damontecres.wholphin.ui.launchIO
import com.github.damontecres.wholphin.ui.nav.NavigationManager import com.github.damontecres.wholphin.ui.nav.NavigationManager
import com.github.damontecres.wholphin.ui.setValueOnMain
import com.github.damontecres.wholphin.util.ExceptionHandler
import com.github.damontecres.wholphin.util.GetProgramsDtoHandler import com.github.damontecres.wholphin.util.GetProgramsDtoHandler
import com.github.damontecres.wholphin.util.LoadingExceptionHandler import com.github.damontecres.wholphin.util.LoadingExceptionHandler
import com.github.damontecres.wholphin.util.LoadingState import com.github.damontecres.wholphin.util.LoadingState
@ -16,6 +20,7 @@ import kotlinx.coroutines.withContext
import org.jellyfin.sdk.api.client.ApiClient import org.jellyfin.sdk.api.client.ApiClient
import org.jellyfin.sdk.api.client.extensions.imageApi import org.jellyfin.sdk.api.client.extensions.imageApi
import org.jellyfin.sdk.api.client.extensions.liveTvApi import org.jellyfin.sdk.api.client.extensions.liveTvApi
import org.jellyfin.sdk.api.client.extensions.userLibraryApi
import org.jellyfin.sdk.model.api.GetProgramsDto import org.jellyfin.sdk.model.api.GetProgramsDto
import org.jellyfin.sdk.model.api.ImageType import org.jellyfin.sdk.model.api.ImageType
import org.jellyfin.sdk.model.api.ItemSortBy import org.jellyfin.sdk.model.api.ItemSortBy
@ -47,6 +52,9 @@ class LiveTvViewModel
val programs = MutableLiveData<List<TvProgram>>() val programs = MutableLiveData<List<TvProgram>>()
val programsByChannel = MutableLiveData<Map<UUID, List<TvProgram>>>(mapOf()) val programsByChannel = MutableLiveData<Map<UUID, List<TvProgram>>>(mapOf())
val fetchingItem = MutableLiveData<LoadingState>(LoadingState.Pending)
val fetchedItem = MutableLiveData<BaseItem?>(null)
private val programMap = mutableMapOf<UUID, MutableLiveData<List<TvProgram?>>>() private val programMap = mutableMapOf<UUID, MutableLiveData<List<TvProgram?>>>()
fun init() { fun init() {
@ -153,59 +161,55 @@ class LiveTvViewModel
} }
} }
// fun getPrograms(channelId: UUID): MutableLiveData<List<TvProgram?>> = fun getItem(itemId: UUID) {
// programMap.getOrPut(channelId) { fetchingItem.value = LoadingState.Loading
// val data = MutableLiveData<List<TvProgram?>>(listOf()) viewModelScope.launchIO(LoadingExceptionHandler(fetchingItem, "Error")) {
// viewModelScope.launch(Dispatchers.IO + ExceptionHandler()) { val result =
// val request = api.userLibraryApi
// GetProgramsDto( .getItem(itemId = itemId)
// minStartDate = start, .content
// channelIds = listOf(channelId), .let { BaseItem.from(it, api) }
// ) withContext(Dispatchers.Main) {
// val pager = ApiRequestPager(api, request, GetProgramsDtoHandler, viewModelScope).init() fetchedItem.value = result
// val programList = fetchingItem.value = LoadingState.Success
// if (pager.isEmpty()) { }
// object : AbstractList<TvProgram?>() { }
// override fun get(index: Int): TvProgram? { }
// val start = start.plusHours(index.toLong())
// val end = start.plusHours(1L) fun cancelRecording(
// return TvProgram( series: Boolean,
// id = UUID.randomUUID(), timerId: String?,
// channelId = channelId, ) {
// start = start, if (timerId != null) {
// end = start, viewModelScope.launch(ExceptionHandler(autoToast = true)) {
// duration = 60.minutes, if (series) {
// name = "Unknown", api.liveTvApi.cancelSeriesTimer(timerId)
// subtitle = null, } else {
// seasonEpisode = null, // TODO api.liveTvApi.cancelTimer(timerId)
// ) }
// } }
// }
// override val size: Int }
// get() = Int.MAX_VALUE
// } suspend fun refreshProgram(
// } else { index: Int,
// LazyList(pager) { item -> timerId: String?,
// item?.data?.let { dto -> seriesTimerId: String?,
// TvProgram( ) {
// id = dto.id, val newProgram =
// channelId = channelId, programs.value?.getOrNull(index)?.copy(
// start = dto.startDate!!, isRecording = timerId.isNotNullOrBlank(),
// end = dto.endDate!!, isSeriesRecording = seriesTimerId.isNotNullOrBlank(),
// duration = dto.runTimeTicks!!.ticks, )
// name = dto.seriesName ?: dto.name, if (newProgram != null) {
// subtitle = dto.name.takeIf { dto.seriesName.isNullOrBlank() }, programs.value
// seasonEpisode = null, // TODO ?.toMutableList()
// ) ?.apply { set(index, newProgram) }
// } ?.let {
// } programs.setValueOnMain(it)
// } }
// withContext(Dispatchers.Main) { }
// data.value = programList }
// }
// }
// data
// }
} }
/** /**

View file

@ -16,12 +16,15 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusDirection import androidx.compose.ui.focus.FocusDirection
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.key.Key import androidx.compose.ui.input.key.Key
import androidx.compose.ui.input.key.KeyEventType import androidx.compose.ui.input.key.KeyEventType
@ -29,8 +32,11 @@ import androidx.compose.ui.input.key.key
import androidx.compose.ui.input.key.onPreviewKeyEvent import androidx.compose.ui.input.key.onPreviewKeyEvent
import androidx.compose.ui.input.key.type import androidx.compose.ui.input.key.type
import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.tv.material3.Button
import androidx.tv.material3.MaterialTheme import androidx.tv.material3.MaterialTheme
import androidx.tv.material3.Surface import androidx.tv.material3.Surface
import androidx.tv.material3.SurfaceDefaults import androidx.tv.material3.SurfaceDefaults
@ -38,13 +44,18 @@ import androidx.tv.material3.Text
import androidx.tv.material3.contentColorFor import androidx.tv.material3.contentColorFor
import androidx.tv.material3.surfaceColorAtElevation import androidx.tv.material3.surfaceColorAtElevation
import coil3.compose.AsyncImage import coil3.compose.AsyncImage
import com.github.damontecres.wholphin.data.model.BaseItem
import com.github.damontecres.wholphin.ui.components.CircularProgress
import com.github.damontecres.wholphin.ui.components.ErrorMessage import com.github.damontecres.wholphin.ui.components.ErrorMessage
import com.github.damontecres.wholphin.ui.components.LoadingPage import com.github.damontecres.wholphin.ui.components.LoadingPage
import com.github.damontecres.wholphin.ui.enableMarquee import com.github.damontecres.wholphin.ui.enableMarquee
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
import com.github.damontecres.wholphin.ui.nav.Destination import com.github.damontecres.wholphin.ui.nav.Destination
import com.github.damontecres.wholphin.ui.rememberInt 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.ExceptionHandler
import com.github.damontecres.wholphin.util.LoadingState import com.github.damontecres.wholphin.util.LoadingState
import com.github.damontecres.wholphin.util.seasonEpisode
import com.google.common.cache.CacheBuilder import com.google.common.cache.CacheBuilder
import com.google.common.cache.CacheLoader import com.google.common.cache.CacheLoader
import eu.wewox.programguide.ProgramGuide import eu.wewox.programguide.ProgramGuide
@ -57,7 +68,8 @@ import java.time.LocalDateTime
import java.util.UUID import java.util.UUID
@Composable @Composable
fun TvGrid( fun TvGuideGrid(
requestFocusAfterLoading: Boolean,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
viewModel: LiveTvViewModel = hiltViewModel(), viewModel: LiveTvViewModel = hiltViewModel(),
) { ) {
@ -69,43 +81,193 @@ fun TvGrid(
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())
when (val state = loading) { when (val state = loading) {
is LoadingState.Error -> ErrorMessage(state) is LoadingState.Error -> ErrorMessage(state, modifier)
LoadingState.Loading, LoadingState.Loading,
LoadingState.Pending, LoadingState.Pending,
-> LoadingPage() -> LoadingPage(modifier)
LoadingState.Success -> LoadingState.Success -> {
val fetchedItem by viewModel.fetchedItem.observeAsState(null)
val loadingItem by viewModel.fetchingItem.observeAsState(LoadingState.Pending)
var showItemDialog by remember { mutableStateOf<Int?>(null) }
val focusRequester = remember { FocusRequester() }
if (requestFocusAfterLoading) {
LaunchedEffect(Unit) {
focusRequester.tryRequestFocus()
}
}
Column(modifier = modifier) { Column(modifier = modifier) {
TvGrid( TvGuideGrid(
channels = channels, channels = channels,
programList = programs, programList = programs,
programs = programsByChannel, programs = programsByChannel,
start = viewModel.start, start = viewModel.start,
onClick = { program -> onClick = { index, program ->
viewModel.navigationManager.navigateTo( viewModel.getItem(program.id)
Destination.Playback( showItemDialog = index
itemId = program.channelId,
positionMs = 0L,
),
)
}, },
modifier = modifier =
Modifier Modifier
.fillMaxSize() .fillMaxSize()
.background(MaterialTheme.colorScheme.surface), .background(MaterialTheme.colorScheme.surface)
.focusRequester(focusRequester),
) )
} }
if (showItemDialog != null) {
val onDismissRequest = { showItemDialog = null }
ProgramDialog(
item = fetchedItem,
loading = loadingItem,
onDismissRequest = onDismissRequest,
onWatch = {
onDismissRequest.invoke()
fetchedItem?.data?.channelId?.let {
viewModel.navigationManager.navigateTo(
Destination.Playback(
itemId = it,
positionMs = 0L,
),
)
}
},
onRecord = { series ->
onDismissRequest.invoke()
},
onCancelRecord = { series ->
onDismissRequest.invoke()
fetchedItem?.data?.let {
viewModel.cancelRecording(
series,
if (series) it.seriesTimerId else it.timerId,
)
}
},
)
}
}
} }
} }
@Composable @Composable
fun TvGrid( fun ProgramDialog(
item: BaseItem?,
loading: LoadingState,
onDismissRequest: () -> Unit,
onWatch: () -> Unit,
onRecord: (series: Boolean) -> Unit,
onCancelRecord: (series: Boolean) -> Unit,
) {
Dialog(
onDismissRequest = onDismissRequest,
) {
Box(
modifier =
Modifier.background(
MaterialTheme.colorScheme.surfaceColorAtElevation(10.dp),
shape = RoundedCornerShape(16.dp),
),
) {
Column(
verticalArrangement = Arrangement.spacedBy(8.dp),
modifier =
Modifier
.padding(16.dp),
) {
when (val st = loading) {
is LoadingState.Error -> ErrorMessage(st)
LoadingState.Loading,
LoadingState.Pending,
->
CircularProgress(
Modifier
.padding(8.dp)
.size(48.dp),
)
LoadingState.Success ->
item?.let { item ->
val now = LocalDateTime.now()
val dto = item.data
val isRecording = dto.timerId.isNotNullOrBlank()
val isSeriesRecording = dto.seriesTimerId.isNotNullOrBlank()
Text(
text = item.name ?: "",
color = MaterialTheme.colorScheme.onSurface,
style = MaterialTheme.typography.titleLarge,
)
if (dto.isSeries ?: false) {
listOfNotNull(dto.seasonEpisode, dto.episodeTitle)
.joinToString(" - ")
.ifBlank { null }
?.let {
Text(
text = it,
color = MaterialTheme.colorScheme.onSurface,
style = MaterialTheme.typography.titleMedium,
)
}
}
dto.overview?.let { overview ->
Text(
text = overview,
color = MaterialTheme.colorScheme.onSurface,
style = MaterialTheme.typography.bodyMedium,
overflow = TextOverflow.Ellipsis,
maxLines = 3,
)
}
if (dto.isSeries ?: false) {
Button(
onClick = {
if (isSeriesRecording) {
onCancelRecord.invoke(true)
} else {
onRecord.invoke(true)
}
},
) {
Text(
text = if (isSeriesRecording) "Cancel Series Recording" else "Record Series",
)
}
}
Button(
onClick = {
if (isRecording) {
onCancelRecord.invoke(false)
} else {
onRecord.invoke(false)
}
},
) {
Text(
text = if (isRecording) "Cancel Recording" else "Record Program",
)
}
if (now.isAfter(dto.startDate!!) && now.isBefore(dto.endDate!!)) {
Button(
onClick = onWatch,
) {
Text(
text = "Watch live",
)
}
}
}
}
}
}
}
}
@Composable
fun TvGuideGrid(
channels: List<TvChannel>, channels: List<TvChannel>,
programList: List<TvProgram>, programList: List<TvProgram>,
programs: Map<UUID, List<TvProgram>>, programs: Map<UUID, List<TvProgram>>,
start: LocalDateTime, start: LocalDateTime,
onClick: (TvProgram) -> Unit, onClick: (Int, TvProgram) -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
val focusManager = LocalFocusManager.current val focusManager = LocalFocusManager.current
@ -224,7 +386,7 @@ fun TvGrid(
Key.DirectionCenter, Key.Enter, Key.NumPadEnter -> { Key.DirectionCenter, Key.Enter, Key.NumPadEnter -> {
val program = programList[focusedProgramIndex] val program = programList[focusedProgramIndex]
Timber.v("Clicked on %s", program) Timber.v("Clicked on %s", program)
onClick.invoke(program) onClick.invoke(focusedProgramIndex, program)
null null
} }
@ -255,7 +417,7 @@ fun TvGrid(
}, },
) { ) {
Surface( Surface(
colors = SurfaceDefaults.colors(MaterialTheme.colorScheme.tertiary), colors = SurfaceDefaults.colors(MaterialTheme.colorScheme.tertiary.copy(alpha = .5f)),
modifier = Modifier, modifier = Modifier,
) { ) {
// Empty // Empty
@ -293,7 +455,7 @@ fun TvGrid(
if (focused) { if (focused) {
MaterialTheme.colorScheme.inverseSurface MaterialTheme.colorScheme.inverseSurface
} else { } else {
MaterialTheme.colorScheme.surfaceColorAtElevation(2.dp) MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp)
} }
val textColor = MaterialTheme.colorScheme.contentColorFor(background) val textColor = MaterialTheme.colorScheme.contentColorFor(background)
Box( Box(

View file

@ -85,9 +85,6 @@ sealed class Destination(
val recursive: Boolean, val recursive: Boolean,
) : Destination(false) ) : Destination(false)
@Serializable
data object LiveTvGuide : Destination(false)
@Serializable @Serializable
data object UpdateApp : Destination(true) data object UpdateApp : Destination(true)

View file

@ -6,12 +6,12 @@ import androidx.tv.material3.Text
import com.github.damontecres.wholphin.preferences.UserPreferences import com.github.damontecres.wholphin.preferences.UserPreferences
import com.github.damontecres.wholphin.ui.components.LicenseInfo import com.github.damontecres.wholphin.ui.components.LicenseInfo
import com.github.damontecres.wholphin.ui.detail.CollectionFolderGeneric import com.github.damontecres.wholphin.ui.detail.CollectionFolderGeneric
import com.github.damontecres.wholphin.ui.detail.CollectionFolderLiveTv
import com.github.damontecres.wholphin.ui.detail.CollectionFolderMovie import com.github.damontecres.wholphin.ui.detail.CollectionFolderMovie
import com.github.damontecres.wholphin.ui.detail.CollectionFolderTv import com.github.damontecres.wholphin.ui.detail.CollectionFolderTv
import com.github.damontecres.wholphin.ui.detail.DebugPage import com.github.damontecres.wholphin.ui.detail.DebugPage
import com.github.damontecres.wholphin.ui.detail.PlaylistDetails import com.github.damontecres.wholphin.ui.detail.PlaylistDetails
import com.github.damontecres.wholphin.ui.detail.SeriesDetails import com.github.damontecres.wholphin.ui.detail.SeriesDetails
import com.github.damontecres.wholphin.ui.detail.livetv.TvGrid
import com.github.damontecres.wholphin.ui.detail.movie.MovieDetails import com.github.damontecres.wholphin.ui.detail.movie.MovieDetails
import com.github.damontecres.wholphin.ui.detail.series.SeriesOverview import com.github.damontecres.wholphin.ui.detail.series.SeriesOverview
import com.github.damontecres.wholphin.ui.main.HomePage import com.github.damontecres.wholphin.ui.main.HomePage
@ -144,13 +144,23 @@ fun DestinationContent(
) )
BaseItemKind.USER_VIEW -> BaseItemKind.USER_VIEW ->
CollectionFolderGeneric( when (destination.item?.data?.collectionType) {
preferences, CollectionType.LIVETV ->
destination.itemId, CollectionFolderLiveTv(
destination.item, preferences,
true, destination,
modifier, modifier,
) )
else ->
CollectionFolderGeneric(
preferences,
destination.itemId,
destination.item,
true,
modifier,
)
}
else -> { else -> {
Text("Unsupported item type: ${destination.type}") Text("Unsupported item type: ${destination.type}")
@ -167,11 +177,6 @@ fun DestinationContent(
modifier = modifier, modifier = modifier,
) )
Destination.LiveTvGuide ->
TvGrid(
modifier = modifier,
)
Destination.UpdateApp -> InstallUpdatePage(preferences, modifier) Destination.UpdateApp -> InstallUpdatePage(preferences, modifier)
Destination.License -> LicenseInfo(modifier) Destination.License -> LicenseInfo(modifier)

View file

@ -15,7 +15,6 @@ import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.AccountCircle import androidx.compose.material.icons.filled.AccountCircle
import androidx.compose.material.icons.filled.Build
import androidx.compose.material.icons.filled.Home import androidx.compose.material.icons.filled.Home
import androidx.compose.material.icons.filled.Search import androidx.compose.material.icons.filled.Search
import androidx.compose.material.icons.filled.Settings import androidx.compose.material.icons.filled.Settings
@ -213,24 +212,6 @@ fun NavDrawer(
), ),
) )
} }
// TODO placeholder
item {
IconNavItem(
text = "Live TV",
icon = Icons.Default.Build,
selected = selectedIndex == -3,
onClick = {
viewModel.setIndex(-3)
viewModel.navigationManager.navigateToFromDrawer(Destination.LiveTvGuide)
},
modifier =
Modifier.ifElse(
selectedIndex == -2,
Modifier.focusRequester(focusRequester),
),
)
}
itemsIndexed(libraries) { index, it -> itemsIndexed(libraries) { index, it ->
LibraryNavItem( LibraryNavItem(
library = it, library = it,

View file

@ -35,4 +35,5 @@ val supportedCollectionTypes =
CollectionType.HOMEVIDEOS, CollectionType.HOMEVIDEOS,
CollectionType.PLAYLISTS, CollectionType.PLAYLISTS,
CollectionType.BOXSETS, CollectionType.BOXSETS,
CollectionType.LIVETV,
) )