This commit is contained in:
Lsong 2024-08-29 19:03:03 +08:00
parent e74c15571a
commit b88cd7f48f
5 changed files with 217 additions and 190 deletions

View File

@ -13,12 +13,6 @@ import me.lsong.mytv.utils.Settings
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
// 接口定义
interface TVProvider {
suspend fun load()
fun groups(): TVGroupList
suspend fun epg(): EpgList
}
// 数据类定义 // 数据类定义
@Immutable @Immutable
@ -149,10 +143,13 @@ class IPTVProvider(private val epgRepository: EpgRepository) : TVProvider {
groupList = process(sources) groupList = process(sources)
epgList = fetchEPGData(epgUrls) epgList = fetchEPGData(epgUrls)
} }
override fun groups(): TVGroupList {
return groupList
}
override suspend fun epg(): EpgList = epgList override fun channels(groupTitle: String): TVChannelList {
override fun groups(): TVGroupList = groupList return groupList.find { it.title == groupTitle }?.channels ?: TVChannelList()
}
private suspend fun fetchIPTVSources(): Pair<List<TVSource>, List<String>> { private suspend fun fetchIPTVSources(): Pair<List<TVSource>, List<String>> {
val allSources = mutableListOf<TVSource>() val allSources = mutableListOf<TVSource>()
@ -227,4 +224,23 @@ class IPTVProvider(private val epgRepository: EpgRepository) : TVProvider {
Log.i("getM3uChannels", "解析直播源完成:${it.sources.size}个资源, $sourceUrl") Log.i("getM3uChannels", "解析直播源完成:${it.sources.size}个资源, $sourceUrl")
} }
} }
}
// Interface definition
interface TVProvider {
suspend fun load()
fun groups(): TVGroupList
fun channels(groupTitle: String): TVChannelList
}
class MyTvProviderManager : TVProvider {
private val providers: List<TVProvider> = listOf(
IPTVProvider(EpgRepository())
)
override suspend fun load() {
providers.forEach { it.load() }
}
override fun groups(): TVGroupList = TVGroupList(providers.flatMap { it.groups() })
override fun channels(groupTitle: String): TVChannelList = TVChannelList(providers.flatMap { it.channels(groupTitle) })
} }

View File

@ -1,5 +1,6 @@
package me.lsong.mytv.ui package me.lsong.mytv.ui
import android.util.Log
import androidx.activity.compose.BackHandler import androidx.activity.compose.BackHandler
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.background import androidx.compose.foundation.background
@ -11,9 +12,7 @@ import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.sizeIn import androidx.compose.foundation.layout.sizeIn
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
@ -51,15 +50,10 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import me.lsong.mytv.R import me.lsong.mytv.R
import me.lsong.mytv.epg.EpgList import me.lsong.mytv.providers.MyTvProviderManager
import me.lsong.mytv.epg.EpgList.Companion.currentProgrammes
import me.lsong.mytv.epg.EpgRepository
import me.lsong.mytv.providers.IPTVProvider
import me.lsong.mytv.providers.TVChannel import me.lsong.mytv.providers.TVChannel
import me.lsong.mytv.providers.TVGroupList
import me.lsong.mytv.providers.TVGroupList.Companion.channels import me.lsong.mytv.providers.TVGroupList.Companion.channels
import me.lsong.mytv.providers.TVGroupList.Companion.findGroupIndex import me.lsong.mytv.providers.TVGroupList.Companion.findGroupIndex
import me.lsong.mytv.providers.TVProvider
import me.lsong.mytv.ui.components.LeanbackVisible import me.lsong.mytv.ui.components.LeanbackVisible
import me.lsong.mytv.ui.components.MonitorScreen import me.lsong.mytv.ui.components.MonitorScreen
import me.lsong.mytv.ui.components.MyTvMenuItem import me.lsong.mytv.ui.components.MyTvMenuItem
@ -75,88 +69,35 @@ import me.lsong.mytv.utils.handleLeanbackDragGestures
import me.lsong.mytv.utils.handleLeanbackKeyEvents import me.lsong.mytv.utils.handleLeanbackKeyEvents
@Composable @Composable
private fun StartScreen(state: LeanbackMainUiState) { fun MainScreen(
var isSettingsVisible by remember { mutableStateOf(false) } modifier: Modifier = Modifier,
BackHandler(enabled = !isSettingsVisible) { mainViewModel: MainViewModel = viewModel(),
isSettingsVisible = true ) {
} val uiState by mainViewModel.uiState.collectAsState()
Box( when (val state = uiState) {
contentAlignment = Alignment.Center, is LeanbackMainUiState.Loading,
modifier = Modifier is LeanbackMainUiState.Error -> StartScreen(state)
.fillMaxSize() is LeanbackMainUiState.Ready -> MainContent(
.background(MaterialTheme.colorScheme.background) modifier = modifier,
.onPreviewKeyEvent { event -> providerManager = state.providerManager,
if (event.key == Key.Menu && event.type == KeyEventType.KeyUp) { )
isSettingsVisible = !isSettingsVisible
true
} else {
false
}
},
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(10.dp)
) {
Image(
painter = painterResource(id = R.mipmap.ic_launcher),
contentDescription = "DuckTV",
modifier = Modifier.size(96.dp)
)
Text(
text = Constants.APP_NAME,
style = MaterialTheme.typography.headlineSmall,
color = MaterialTheme.colorScheme.onBackground,
)
when (state) {
is LeanbackMainUiState.Loading -> {
LinearProgressIndicator(
modifier = Modifier
.widthIn(300.dp, 800.dp)
.height(8.dp)
)
state.message?.let { message ->
Text(
text = message,
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.8f),
modifier = Modifier.sizeIn(maxWidth = 500.dp),
)
}
}
is LeanbackMainUiState.Error -> {
state.message?.let { message ->
Text(
text = message,
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.error.copy(alpha = 0.8f),
modifier = Modifier.sizeIn(maxWidth = 500.dp),
)
}
}
else -> {} // This case should never happen
}
}
}
LeanbackVisible({ isSettingsVisible }) {
SettingsScreen()
} }
} }
@Composable @Composable
fun MyTvMenuWidget( fun MyTvMenuWidget(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
epgListProvider: () -> EpgList = { EpgList() }, providerManager: MyTvProviderManager,
// epgListProvider: () -> EpgList = { EpgList() },
// groupListProvider: () -> TVGroupList = { TVGroupList() },
channelProvider: () -> TVChannel = { TVChannel() }, channelProvider: () -> TVChannel = { TVChannel() },
groupListProvider: () -> TVGroupList = { TVGroupList() },
onSelected: (TVChannel) -> Unit = {}, onSelected: (TVChannel) -> Unit = {},
onSettings: (() -> Unit)? = null, onSettings: (() -> Unit)? = null,
onUserAction: () -> Unit = {} onUserAction: () -> Unit = {}
) { ) {
val groupList = groupListProvider() // val epgList = epgListProvider()
val groupList = providerManager.groups();
val currentChannel = channelProvider() val currentChannel = channelProvider()
val epgList = epgListProvider()
val groups = remember(groupList) { val groups = remember(groupList) {
groupList.map { group -> groupList.map { group ->
@ -165,15 +106,17 @@ fun MyTvMenuWidget(
} }
val currentGroup = remember(groupList, currentChannel) { val currentGroup = remember(groupList, currentChannel) {
groups.firstOrNull { it.title == groupList[groupList.findGroupIndex(currentChannel)].title } groups.firstOrNull { it.title == currentChannel.groupTitle }
?: MyTvMenuItem() ?: MyTvMenuItem()
} }
Log.d("currentGroup", "$currentGroup $currentChannel")
val currentMenuItem = remember(currentChannel) { val currentMenuItem = remember(currentChannel) {
MyTvMenuItem( MyTvMenuItem(
icon = currentChannel.logo ?: "", icon = currentChannel.logo ?: "",
title = currentChannel.title, title = currentChannel.title,
description = epgList.currentProgrammes(currentChannel)?.now?.title // description = epgList.currentProgrammes(currentChannel)?.now?.title
) )
} }
@ -182,7 +125,7 @@ fun MyTvMenuWidget(
MyTvMenuItem( MyTvMenuItem(
icon = channel.logo ?: "", icon = channel.logo ?: "",
title = channel.title, title = channel.title,
description = epgList.currentProgrammes(channel)?.now?.title // description = epgList.currentProgrammes(channel)?.now?.title
) )
} ?: emptyList() } ?: emptyList()
} }
@ -205,11 +148,11 @@ fun MyTvMenuWidget(
selectedItem = focusedGroup, selectedItem = focusedGroup,
onFocused = { menuItem -> onFocused = { menuItem ->
focusedGroup = menuItem focusedGroup = menuItem
items = itemsProvider(menuItem.title) items = itemsProvider(focusedGroup.title)
}, },
onSelected = { menuItem -> onSelected = { menuItem ->
focusedGroup = menuItem focusedGroup = menuItem
items = itemsProvider(menuItem.title) items = itemsProvider(focusedGroup.title)
focusedItem = items.firstOrNull() ?: MyTvMenuItem() focusedItem = items.firstOrNull() ?: MyTvMenuItem()
rightListFocusRequester.requestFocus() rightListFocusRequester.requestFocus()
}, },
@ -232,6 +175,9 @@ fun MyTvMenuWidget(
} }
MyTvMenuItemList( MyTvMenuItemList(
items = items, items = items,
modifier = Modifier
.fillMaxHeight()
.background(androidx.tv.material3.MaterialTheme.colorScheme.background.copy(0.8f)),
selectedItem = focusedItem, selectedItem = focusedItem,
onSelected = { menuItem -> onSelected = { menuItem ->
focusedItem = menuItem focusedItem = menuItem
@ -240,7 +186,6 @@ fun MyTvMenuWidget(
}, },
onUserAction = onUserAction, onUserAction = onUserAction,
focusRequester = rightListFocusRequester, focusRequester = rightListFocusRequester,
modifier = Modifier.background(androidx.tv.material3.MaterialTheme.colorScheme.background.copy(0.8f)),
) )
} }
@ -249,43 +194,16 @@ fun MyTvMenuWidget(
} }
} }
@Composable
fun MainScreen(
modifier: Modifier = Modifier,
mainViewModel: MainViewModel = viewModel(),
) {
val uiState by mainViewModel.uiState.collectAsState()
when (val state = uiState) {
is LeanbackMainUiState.Loading,
is LeanbackMainUiState.Error -> StartScreen(state)
is LeanbackMainUiState.Ready -> MainContent(
modifier = modifier,
groups = state.groups,
epgList = state.epgList,
)
}
}
sealed interface LeanbackMainUiState {
data class Loading(val message: String? = null) : LeanbackMainUiState
data class Error(val message: String? = null) : LeanbackMainUiState
data class Ready(
val groups: TVGroupList = TVGroupList(),
val epgList: EpgList = EpgList(),
) : LeanbackMainUiState
}
@Composable @Composable
fun MainContent( fun MainContent(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
epgList: EpgList = EpgList(), providerManager: MyTvProviderManager,
groups: TVGroupList = TVGroupList(),
settingsViewModel: MyTvSettingsViewModel = viewModel(), settingsViewModel: MyTvSettingsViewModel = viewModel(),
) { ) {
val videoPlayerState = rememberLeanbackVideoPlayerState() val videoPlayerState = rememberLeanbackVideoPlayerState()
val mainContentState = rememberMainContentState( val mainContentState = rememberMainContentState(
providerManager = providerManager,
videoPlayerState = videoPlayerState, videoPlayerState = videoPlayerState,
groups = groups,
) )
BackHandler ( BackHandler (
@ -324,6 +242,8 @@ fun MainContent(
onNumber = {}, onNumber = {},
) )
.handleLeanbackDragGestures( .handleLeanbackDragGestures(
onSwipeLeft = { mainContentState.changeToPrevSource() },
onSwipeRight = { mainContentState.changeToNextSource() },
onSwipeDown = { onSwipeDown = {
if (settingsViewModel.iptvChannelChangeFlip) mainContentState.changeCurrentChannelToNext() if (settingsViewModel.iptvChannelChangeFlip) mainContentState.changeCurrentChannelToNext()
else mainContentState.changeCurrentChannelToPrev() else mainContentState.changeCurrentChannelToPrev()
@ -332,19 +252,12 @@ fun MainContent(
if (settingsViewModel.iptvChannelChangeFlip) mainContentState.changeCurrentChannelToPrev() if (settingsViewModel.iptvChannelChangeFlip) mainContentState.changeCurrentChannelToPrev()
else mainContentState.changeCurrentChannelToNext() else mainContentState.changeCurrentChannelToNext()
}, },
onSwipeLeft = {
mainContentState.changeToPrevSource()
},
onSwipeRight = {
mainContentState.changeToNextSource()
},
), ),
) )
LeanbackVisible({ mainContentState.isMenuVisible && !mainContentState.isChannelInfoVisible }) { LeanbackVisible({ mainContentState.isMenuVisible && !mainContentState.isChannelInfoVisible }) {
MyTvMenuWidget( MyTvMenuWidget(
epgListProvider = { epgList }, providerManager = providerManager,
groupListProvider = { groups },
channelProvider = { mainContentState.currentChannel }, channelProvider = { mainContentState.currentChannel },
onSelected = { channel -> mainContentState.changeCurrentChannel(channel) }, onSelected = { channel -> mainContentState.changeCurrentChannel(channel) },
onSettings = { mainContentState.showSettings() } onSettings = { mainContentState.showSettings() }
@ -354,7 +267,6 @@ fun MainContent(
LeanbackVisible({ mainContentState.isChannelInfoVisible }) { LeanbackVisible({ mainContentState.isChannelInfoVisible }) {
MyTvNowPlaying( MyTvNowPlaying(
modifier = modifier, modifier = modifier,
epgListProvider = { epgList },
channelProvider = { mainContentState.currentChannel }, channelProvider = { mainContentState.currentChannel },
channelIndexProvider = { mainContentState.currentChannelIndex }, channelIndexProvider = { mainContentState.currentChannelIndex },
sourceIndexProvider = { mainContentState.currentSourceIndex }, sourceIndexProvider = { mainContentState.currentSourceIndex },
@ -372,41 +284,6 @@ fun MainContent(
} }
} }
// MainViewModel.kt
class MainViewModel : ViewModel() {
private val providers: List<TVProvider> = listOf(
IPTVProvider(EpgRepository())
)
private val _uiState = MutableStateFlow<LeanbackMainUiState>(LeanbackMainUiState.Loading())
val uiState: StateFlow<LeanbackMainUiState> = _uiState.asStateFlow()
init {
viewModelScope.launch {
refreshData()
}
}
private suspend fun refreshData() {
try {
_uiState.value = LeanbackMainUiState.Loading("Initializing providers...")
providers.forEachIndexed { index, provider ->
_uiState.value = LeanbackMainUiState.Loading("Initializing provider ${index + 1}/${providers.size}...")
provider.load()
}
val groupList = providers.flatMap { it.groups() }
val epgList = providers.map { it.epg() }.reduce { acc, epgList -> (acc + epgList) as EpgList }
_uiState.value = LeanbackMainUiState.Ready(
groups = TVGroupList(groupList),
epgList = epgList
)
} catch (error: Exception) {
_uiState.value = LeanbackMainUiState.Error(error.message)
}
}
}
@Preview(device = "id:pixel_5") @Preview(device = "id:pixel_5")
@Composable @Composable
private fun MyTvMainScreenPreview() { private fun MyTvMainScreenPreview() {

View File

@ -7,10 +7,8 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import kotlinx.coroutines.CoroutineScope import me.lsong.mytv.providers.MyTvProviderManager
import kotlinx.coroutines.launch
import me.lsong.mytv.providers.TVChannel import me.lsong.mytv.providers.TVChannel
import me.lsong.mytv.providers.TVGroupList import me.lsong.mytv.providers.TVGroupList
import me.lsong.mytv.providers.TVGroupList.Companion.channels import me.lsong.mytv.providers.TVGroupList.Companion.channels
@ -22,10 +20,10 @@ import kotlin.math.max
@Stable @Stable
class MainContentState( class MainContentState(
coroutineScope: CoroutineScope,
private val videoPlayerState: LeanbackVideoPlayerState, private val videoPlayerState: LeanbackVideoPlayerState,
private val groups: TVGroupList, providerManager: MyTvProviderManager,
) { ) {
private val groups: TVGroupList = providerManager.groups();
private var _currentChannel by mutableStateOf(TVChannel()) private var _currentChannel by mutableStateOf(TVChannel())
val currentChannel get() = _currentChannel val currentChannel get() = _currentChannel
@ -58,15 +56,10 @@ class MainContentState(
init { init {
changeCurrentChannel(groups.channels.getOrElse(Settings.iptvLastIptvIdx) { changeCurrentChannel(groups.channels.getOrElse(Settings.iptvLastIptvIdx) {
groups.firstOrNull()?.channels?.firstOrNull() ?: TVChannel() groups.channels.firstOrNull() ?: TVChannel()
}) })
videoPlayerState.onReady { videoPlayerState.onReady {
coroutineScope.launch {
// val name = _currentChannel.name
// val urlIdx = _currentIptvUrlIdx
}
// 记忆可播放的域名 // 记忆可播放的域名
Settings.iptvPlayableHostList += getUrlHost(_currentChannel.urls[_currentIptvUrlIdx]) Settings.iptvPlayableHostList += getUrlHost(_currentChannel.urls[_currentIptvUrlIdx])
} }
@ -100,13 +93,10 @@ class MainContentState(
} }
fun changeCurrentChannel(channel: TVChannel, urlIdx: Int? = null) { fun changeCurrentChannel(channel: TVChannel, urlIdx: Int? = null) {
// isChannelInfoVisible = false
if (channel == _currentChannel && urlIdx == null) return if (channel == _currentChannel && urlIdx == null) return
if (channel == _currentChannel && urlIdx != _currentIptvUrlIdx) { if (channel == _currentChannel && urlIdx != _currentIptvUrlIdx) {
Settings.iptvPlayableHostList -= getUrlHost(_currentChannel.urls[_currentIptvUrlIdx]) Settings.iptvPlayableHostList -= getUrlHost(_currentChannel.urls[_currentIptvUrlIdx])
} }
// _isTempPanelVisible = true
_currentChannel = channel _currentChannel = channel
Settings.iptvLastIptvIdx = currentChannelIndex Settings.iptvLastIptvIdx = currentChannelIndex
@ -131,7 +121,6 @@ class MainContentState(
changeCurrentChannel(getNextChannel()) changeCurrentChannel(getNextChannel())
} }
fun changeToPrevSource(){ fun changeToPrevSource(){
if (currentChannel.urls.size > 1) { if (currentChannel.urls.size > 1) {
changeCurrentChannel( changeCurrentChannel(
@ -149,16 +138,18 @@ class MainContentState(
} }
} }
fun showChannelInfo() {
isMenuVisible = false
isChannelInfoVisible = true
}
fun showMenu() { fun showMenu() {
isMenuVisible = true isMenuVisible = true
isSettingsVisale = false
isChannelInfoVisible = false isChannelInfoVisible = false
} }
fun showChannelInfo() {
isMenuVisible = false
isSettingsVisale = false
isChannelInfoVisible = true
}
fun showSettings() { fun showSettings() {
isMenuVisible = false isMenuVisible = false
isSettingsVisale = true isSettingsVisale = true
@ -168,14 +159,12 @@ class MainContentState(
@Composable @Composable
fun rememberMainContentState( fun rememberMainContentState(
coroutineScope: CoroutineScope = rememberCoroutineScope(), providerManager: MyTvProviderManager,
videoPlayerState: LeanbackVideoPlayerState = rememberLeanbackVideoPlayerState(), videoPlayerState: LeanbackVideoPlayerState = rememberLeanbackVideoPlayerState(),
groups: TVGroupList = TVGroupList(),
) = remember { ) = remember {
MainContentState( MainContentState(
coroutineScope = coroutineScope, providerManager = providerManager,
videoPlayerState = videoPlayerState, videoPlayerState = videoPlayerState,
groups = groups,
) )
} }

View File

@ -0,0 +1,41 @@
package me.lsong.mytv.ui
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import me.lsong.mytv.providers.MyTvProviderManager
// MainViewModel.kt
class MainViewModel : ViewModel() {
private val providerManager = MyTvProviderManager()
private val _uiState = MutableStateFlow<LeanbackMainUiState>(LeanbackMainUiState.Loading())
val uiState: StateFlow<LeanbackMainUiState> = _uiState.asStateFlow()
init {
viewModelScope.launch {
loadData()
}
}
private suspend fun loadData() {
try {
_uiState.value = LeanbackMainUiState.Loading("Initializing providers...")
providerManager.load()
_uiState.value = LeanbackMainUiState.Ready(providerManager)
} catch (error: Exception) {
_uiState.value = LeanbackMainUiState.Error(error.message)
}
}
}
sealed interface LeanbackMainUiState {
data class Loading(val message: String? = null) : LeanbackMainUiState
data class Error(val message: String? = null) : LeanbackMainUiState
data class Ready(
val providerManager: MyTvProviderManager,
) : LeanbackMainUiState
}

View File

@ -0,0 +1,104 @@
package me.lsong.mytv.ui
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.sizeIn
import androidx.compose.foundation.layout.widthIn
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.key.Key
import androidx.compose.ui.input.key.KeyEventType
import androidx.compose.ui.input.key.key
import androidx.compose.ui.input.key.onPreviewKeyEvent
import androidx.compose.ui.input.key.type
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import me.lsong.mytv.R
import me.lsong.mytv.ui.components.LeanbackVisible
import me.lsong.mytv.ui.settings.SettingsScreen
import me.lsong.mytv.utils.Constants
@Composable
fun StartScreen(state: LeanbackMainUiState) {
var isSettingsVisible by remember { mutableStateOf(false) }
BackHandler(enabled = !isSettingsVisible) {
isSettingsVisible = true
}
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.fillMaxSize()
.background(MaterialTheme.colorScheme.background)
.onPreviewKeyEvent { event ->
if (event.key == Key.Menu && event.type == KeyEventType.KeyUp) {
isSettingsVisible = !isSettingsVisible
true
} else {
false
}
},
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(10.dp)
) {
Image(
painter = painterResource(id = R.mipmap.ic_launcher),
contentDescription = "DuckTV",
modifier = Modifier.size(96.dp)
)
Text(
text = Constants.APP_NAME,
style = MaterialTheme.typography.headlineSmall,
color = MaterialTheme.colorScheme.onBackground,
)
when (state) {
is LeanbackMainUiState.Loading -> {
LinearProgressIndicator(
modifier = Modifier
.widthIn(300.dp, 800.dp)
.height(8.dp)
)
state.message?.let { message ->
Text(
text = message,
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.8f),
modifier = Modifier.sizeIn(maxWidth = 500.dp),
)
}
}
is LeanbackMainUiState.Error -> {
state.message?.let { message ->
Text(
text = message,
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.error.copy(alpha = 0.8f),
modifier = Modifier.sizeIn(maxWidth = 500.dp),
)
}
}
else -> {} // This case should never happen
}
}
}
LeanbackVisible({ isSettingsVisible }) {
SettingsScreen()
}
}