From 27e07dae05d197417dabf732eef1202e2827e44c Mon Sep 17 00:00:00 2001 From: Lsong Date: Thu, 29 Aug 2024 17:09:26 +0800 Subject: [PATCH] update --- app/src/main/java/me/lsong/mytv/epg/Epg.kt | 2 +- .../mytv/{iptv => providers}/IPTVProvider.kt | 2 +- .../main/java/me/lsong/mytv/ui/MainScreen.kt | 87 +++++-- .../main/java/me/lsong/mytv/ui/MainState.kt | 10 +- .../lsong/mytv/ui/components/ChannelInfo.kt | 2 +- .../java/me/lsong/mytv/ui/components/Menu.kt | 233 +++++------------- .../me/lsong/mytv/ui/components/NowPlaying.kt | 16 +- 7 files changed, 150 insertions(+), 202 deletions(-) rename app/src/main/java/me/lsong/mytv/{iptv => providers}/IPTVProvider.kt (99%) diff --git a/app/src/main/java/me/lsong/mytv/epg/Epg.kt b/app/src/main/java/me/lsong/mytv/epg/Epg.kt index 5eff953..b509b19 100644 --- a/app/src/main/java/me/lsong/mytv/epg/Epg.kt +++ b/app/src/main/java/me/lsong/mytv/epg/Epg.kt @@ -2,7 +2,7 @@ package me.lsong.mytv.epg import androidx.compose.runtime.Immutable import kotlinx.serialization.Serializable -import me.lsong.mytv.iptv.TVChannel +import me.lsong.mytv.providers.TVChannel import me.lsong.mytv.epg.EpgChannel.Companion.currentProgrammes import me.lsong.mytv.epg.EpgProgramme.Companion.isLive diff --git a/app/src/main/java/me/lsong/mytv/iptv/IPTVProvider.kt b/app/src/main/java/me/lsong/mytv/providers/IPTVProvider.kt similarity index 99% rename from app/src/main/java/me/lsong/mytv/iptv/IPTVProvider.kt rename to app/src/main/java/me/lsong/mytv/providers/IPTVProvider.kt index 0dba8a2..5fc646e 100644 --- a/app/src/main/java/me/lsong/mytv/iptv/IPTVProvider.kt +++ b/app/src/main/java/me/lsong/mytv/providers/IPTVProvider.kt @@ -1,4 +1,4 @@ -package me.lsong.mytv.iptv +package me.lsong.mytv.providers import android.util.Log import androidx.compose.runtime.Immutable diff --git a/app/src/main/java/me/lsong/mytv/ui/MainScreen.kt b/app/src/main/java/me/lsong/mytv/ui/MainScreen.kt index 79c244a..587f044 100644 --- a/app/src/main/java/me/lsong/mytv/ui/MainScreen.kt +++ b/app/src/main/java/me/lsong/mytv/ui/MainScreen.kt @@ -7,12 +7,19 @@ import androidx.compose.foundation.focusable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row +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.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.sizeIn +import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.widthIn +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Settings import androidx.compose.material3.LinearProgressIndicator import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text @@ -38,6 +45,7 @@ import androidx.compose.ui.unit.dp import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.tv.foundation.lazy.list.TvLazyColumn import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow @@ -46,16 +54,16 @@ import me.lsong.mytv.R import me.lsong.mytv.epg.EpgList import me.lsong.mytv.epg.EpgList.Companion.currentProgrammes import me.lsong.mytv.epg.EpgRepository -import me.lsong.mytv.iptv.IPTVProvider -import me.lsong.mytv.iptv.TVChannel -import me.lsong.mytv.iptv.TVGroupList -import me.lsong.mytv.iptv.TVGroupList.Companion.channels -import me.lsong.mytv.iptv.TVGroupList.Companion.findGroupIndex -import me.lsong.mytv.iptv.TVProvider +import me.lsong.mytv.providers.IPTVProvider +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.findGroupIndex +import me.lsong.mytv.providers.TVProvider import me.lsong.mytv.ui.components.LeanbackVisible import me.lsong.mytv.ui.components.MonitorScreen -import me.lsong.mytv.ui.components.MyTvMenu import me.lsong.mytv.ui.components.MyTvMenuItem +import me.lsong.mytv.ui.components.MyTvMenuItemList import me.lsong.mytv.ui.components.MyTvNowPlaying import me.lsong.mytv.ui.player.MyTvVideoScreen import me.lsong.mytv.ui.player.rememberLeanbackVideoPlayerState @@ -143,7 +151,7 @@ fun MyTvMenuWidget( channelProvider: () -> TVChannel = { TVChannel() }, groupListProvider: () -> TVGroupList = { TVGroupList() }, onSelected: (TVChannel) -> Unit = {}, - onSettings: () -> Unit = {}, + onSettings: (() -> Unit)? = null, onUserAction: () -> Unit = {} ) { val groupList = groupListProvider() @@ -179,21 +187,64 @@ fun MyTvMenuWidget( } ?: emptyList() } - Row { - MyTvMenu( - groups = groups, - itemsProvider = itemsProvider, - currentGroup = currentGroup, - currentItem = currentMenuItem, - onItemSelected = { selectedItem -> - val selectedChannel = groupList.channels.first { it.title == selectedItem.title } + var focusedGroup by remember { mutableStateOf(currentGroup) } + var focusedItem by remember { mutableStateOf(currentMenuItem) } + var items by remember { mutableStateOf(itemsProvider(focusedGroup.title)) } + val rightListFocusRequester = remember { FocusRequester() } + + Row(modifier = modifier) { + Column ( + verticalArrangement = Arrangement.SpaceBetween, + modifier = Modifier + .width(250.dp) + .fillMaxHeight(), + ) { + MyTvMenuItemList( + items = groups, + selectedItem = focusedGroup, + onFocused = { menuItem -> + focusedGroup = menuItem + items = itemsProvider(menuItem.title) + }, + onSelected = { menuItem -> + focusedGroup = menuItem + items = itemsProvider(menuItem.title) + focusedItem = items.firstOrNull() ?: MyTvMenuItem() + rightListFocusRequester.requestFocus() + }, + onUserAction = onUserAction, + modifier = Modifier.weight(1f) + ) + LeanbackVisible ({ onSettings != null }) { + TvLazyColumn( + modifier = Modifier.width(250.dp), + contentPadding = PaddingValues(8.dp), + ) { + item { + MyTvMenuItem( + item = MyTvMenuItem(icon = Icons.Default.Settings, title = "Settings"), + onSelected = onSettings!! + ) + } + } + } + } + MyTvMenuItemList( + items = items, + selectedItem = focusedItem, + onSelected = { menuItem -> + focusedItem = menuItem + val selectedChannel = groupList.channels.first { it.title == menuItem.title } onSelected(selectedChannel) }, - modifier = modifier, - onSettings = onSettings, onUserAction = onUserAction, + focusRequester = rightListFocusRequester ) } + + LaunchedEffect(Unit) { + rightListFocusRequester.requestFocus() + } } @Composable diff --git a/app/src/main/java/me/lsong/mytv/ui/MainState.kt b/app/src/main/java/me/lsong/mytv/ui/MainState.kt index 6a20efc..31474bb 100644 --- a/app/src/main/java/me/lsong/mytv/ui/MainState.kt +++ b/app/src/main/java/me/lsong/mytv/ui/MainState.kt @@ -10,13 +10,11 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.delay import kotlinx.coroutines.launch -import me.lsong.mytv.iptv.TVChannel -import me.lsong.mytv.iptv.TVGroupList -import me.lsong.mytv.iptv.TVGroupList.Companion.channels -import me.lsong.mytv.iptv.TVGroupList.Companion.findChannelIndex -import me.lsong.mytv.utils.Constants +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.findChannelIndex import me.lsong.mytv.ui.player.LeanbackVideoPlayerState import me.lsong.mytv.ui.player.rememberLeanbackVideoPlayerState import me.lsong.mytv.utils.Settings diff --git a/app/src/main/java/me/lsong/mytv/ui/components/ChannelInfo.kt b/app/src/main/java/me/lsong/mytv/ui/components/ChannelInfo.kt index 082901d..99804b5 100644 --- a/app/src/main/java/me/lsong/mytv/ui/components/ChannelInfo.kt +++ b/app/src/main/java/me/lsong/mytv/ui/components/ChannelInfo.kt @@ -17,7 +17,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import me.lsong.mytv.iptv.TVChannel +import me.lsong.mytv.providers.TVChannel import me.lsong.mytv.ui.theme.LeanbackTheme import me.lsong.mytv.utils.isIPv6 diff --git a/app/src/main/java/me/lsong/mytv/ui/components/Menu.kt b/app/src/main/java/me/lsong/mytv/ui/components/Menu.kt index 28d0129..ce4f95c 100644 --- a/app/src/main/java/me/lsong/mytv/ui/components/Menu.kt +++ b/app/src/main/java/me/lsong/mytv/ui/components/Menu.kt @@ -1,30 +1,19 @@ package me.lsong.mytv.ui.components 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.PaddingValues -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.wrapContentHeight -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Settings import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha -import androidx.compose.ui.draw.clip import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.onFocusChanged @@ -59,7 +48,7 @@ fun MyTvMenuItem( isSelected: Boolean = false, onFocused: () -> Unit = {}, onSelected: () -> Unit = {}, - onFavoriteToggle: () -> Unit = {}, + onLongSelect: () -> Unit = {}, focusRequester: FocusRequester = remember { FocusRequester() }, ) { LaunchedEffect(isSelected) { @@ -71,130 +60,67 @@ fun MyTvMenuItem( LocalContentColor provides if (isFocused) MaterialTheme.colorScheme.background else MaterialTheme.colorScheme.onBackground ) { - Box( - modifier = Modifier.clip(ListItemDefaults.shape().shape), - ) { - androidx.tv.material3.ListItem( - modifier = modifier - .align(Alignment.Center) - .focusRequester(focusRequester) - .onFocusChanged { if (it.isFocused) onFocused() } - .align(Alignment.Center) - .handleLeanbackKeyEvents( - key = item.hashCode(), - onSelect = onSelected, - onLongSelect = onFavoriteToggle, - ), - colors = ListItemDefaults.colors( - focusedContentColor = MaterialTheme.colorScheme.background, - focusedContainerColor = MaterialTheme.colorScheme.onBackground, - selectedContainerColor = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f), + androidx.tv.material3.ListItem( + modifier = modifier + .focusRequester(focusRequester) + .onFocusChanged { if (it.isFocused) onFocused() } + .handleLeanbackKeyEvents( + key = item.hashCode(), + onSelect = onSelected, + onLongSelect = onLongSelect, ), - onClick = onSelected, - selected = isSelected, - leadingContent = item.icon?.let { icon -> - { - when (icon) { - is ImageVector -> Icon( - imageVector = icon, + colors = ListItemDefaults.colors( + focusedContentColor = MaterialTheme.colorScheme.background, + focusedContainerColor = MaterialTheme.colorScheme.onBackground, + selectedContainerColor = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f), + ), + onClick = onSelected, + selected = isSelected, + leadingContent = item.icon?.let { icon -> + { + when (icon) { + is ImageVector -> Icon( + imageVector = icon, + contentDescription = item.title, + modifier = Modifier.size(24.dp) + ) + is String -> if (icon.isEmpty()) { + Text( + modifier = Modifier + .size(40.dp) + .background(color = MaterialTheme.colorScheme.primary) + .wrapContentHeight(align = Alignment.CenterVertically), + textAlign = TextAlign.Center, + text = item.title.take(2).uppercase(), + style = MaterialTheme.typography.titleMedium, + color = MaterialTheme.colorScheme.onPrimary, + ) + } else { + AsyncImage( + model = icon, contentDescription = item.title, - modifier = Modifier.size(24.dp) + modifier = Modifier.size(40.dp) ) - is String -> if (icon.isEmpty()) { - Text( - modifier = Modifier - .size(40.dp) - .background(color = MaterialTheme.colorScheme.primary) - .wrapContentHeight(align = Alignment.CenterVertically), - textAlign = TextAlign.Center, - text = item.title.take(2).uppercase(), - style = MaterialTheme.typography.titleMedium, - color = MaterialTheme.colorScheme.onPrimary, - ) - } else { - AsyncImage( - model = icon, - contentDescription = item.title, - modifier = Modifier.size(40.dp) - ) - } - else -> null } + else -> null } - }, - headlineContent = { Text(text = item.title, maxLines = 2) }, - supportingContent = item.description?.let { - { - Text( - text = it, - style = MaterialTheme.typography.labelMedium, - maxLines = 1, - modifier = Modifier.alpha(0.8f), - ) - } - }, - ) - } - } -} - -@Composable -fun MyTvMenu( - groups: List, - itemsProvider: (String) -> List, - currentGroup: MyTvMenuItem, - currentItem: MyTvMenuItem, - onGroupFocused: (MyTvMenuItem) -> Unit = {}, - onGroupSelected: (MyTvMenuItem) -> Unit = {}, - onItemSelected: (MyTvMenuItem) -> Unit = {}, - modifier: Modifier = Modifier, - onUserAction: () -> Unit = {}, - onSettings: () -> Unit = {} -) { - var focusedGroup by remember { mutableStateOf(currentGroup) } - var focusedItem by remember { mutableStateOf(currentItem) } - var items by remember { mutableStateOf(itemsProvider(focusedGroup.title)) } - val rightListFocusRequester = remember { FocusRequester() } - - Row(modifier = modifier) { - MyTvMenuItemList( - items = groups, - onSettings = onSettings, - selectedItem = focusedGroup, - onFocused = { menuGroupItem -> - focusedGroup = menuGroupItem - items = itemsProvider(menuGroupItem.title) - onGroupFocused(focusedGroup) + } }, - onSelected = { menuGroupItem -> - focusedGroup = menuGroupItem - items = itemsProvider(menuGroupItem.title) - focusedItem = items.firstOrNull() ?: MyTvMenuItem() - onGroupSelected(focusedGroup) - rightListFocusRequester.requestFocus() + headlineContent = { Text(text = item.title, maxLines = 2) }, + supportingContent = item.description?.let { + { + Text( + text = it, + style = MaterialTheme.typography.labelMedium, + maxLines = 1, + modifier = Modifier.alpha(0.8f), + ) + } }, - onUserAction = onUserAction ) - MyTvMenuItemList( - items = items, - selectedItem = focusedItem, - onSelected = { menuItem -> - focusedItem = menuItem - onItemSelected(focusedItem) - }, - onUserAction = onUserAction, - focusRequester = rightListFocusRequester - ) - - } - - LaunchedEffect(Unit) { - rightListFocusRequester.requestFocus() } } - - @Composable fun MyTvMenuItemList( items: List, @@ -202,15 +128,12 @@ fun MyTvMenuItemList( onUserAction: () -> Unit = {}, onFocused: (MyTvMenuItem) -> Unit = {}, onSelected: (MyTvMenuItem) -> Unit = {}, - onFavoriteToggle: (MyTvMenuItem) -> Unit = {}, + onLongSelect: (MyTvMenuItem) -> Unit = {}, focusRequester: FocusRequester = remember { FocusRequester() }, modifier: Modifier = Modifier, - onSettings: (() -> Unit)? = null, ) { - var focusedItem by remember { mutableStateOf(selectedItem) } val selectedIndex = remember(selectedItem, items) { items.indexOf(selectedItem) } val itemFocusRequesterList = remember(items) { List(items.size) { FocusRequester() } } - val settingsFocusRequester = remember { FocusRequester() } val listState = rememberTvLazyListState() LaunchedEffect(listState) { @@ -224,50 +147,26 @@ fun MyTvMenuItemList( listState.scrollToItem(maxOf(0, index)) } - Column( + TvLazyColumn( + state = listState, + contentPadding = PaddingValues(8.dp), + verticalArrangement = Arrangement.spacedBy(8.dp), modifier = modifier - .fillMaxHeight() .width(250.dp) .background(MaterialTheme.colorScheme.background.copy(0.8f)) .focusRequester(focusRequester) ) { - TvLazyColumn( - state = listState, - contentPadding = PaddingValues(8.dp), - verticalArrangement = Arrangement.spacedBy(8.dp), - modifier = Modifier.weight(1f).align(Alignment.CenterHorizontally) - ) { - itemsIndexed(items, key = { _, item -> item.hashCode() }) { index, item -> - MyTvMenuItem( - item = item, - focusRequester = itemFocusRequesterList[index], - isSelected = selectedIndex == index, - isFocused = selectedIndex == index, - onSelected = { onSelected(item) }, - onFocused = { - focusedItem = item - onFocused(item) - }, - onFavoriteToggle = { onFavoriteToggle(item) } - ) - } - } - // Settings button at the bottom - if (onSettings != null) { - Box( - modifier = Modifier - .fillMaxWidth() - .background(MaterialTheme.colorScheme.background) - .padding(8.dp) - ) { - MyTvMenuItem( - item = MyTvMenuItem(icon = Icons.Default.Settings, title = "Settings"), - focusRequester = settingsFocusRequester, - onSelected = onSettings - ) - } + itemsIndexed(items, key = { _, item -> item.hashCode() }) { index, item -> + MyTvMenuItem( + item = item, + isFocused = selectedIndex == index, + isSelected = selectedIndex == index, + onFocused = { onFocused(item) }, + onSelected = { onSelected(item) }, + onLongSelect = { onLongSelect(item) }, + focusRequester = itemFocusRequesterList[index], + ) } - } } diff --git a/app/src/main/java/me/lsong/mytv/ui/components/NowPlaying.kt b/app/src/main/java/me/lsong/mytv/ui/components/NowPlaying.kt index 38cd813..49644ff 100644 --- a/app/src/main/java/me/lsong/mytv/ui/components/NowPlaying.kt +++ b/app/src/main/java/me/lsong/mytv/ui/components/NowPlaying.kt @@ -17,7 +17,7 @@ import androidx.compose.ui.unit.dp import me.lsong.mytv.rememberLeanbackChildPadding import me.lsong.mytv.epg.EpgList import me.lsong.mytv.epg.EpgList.Companion.getEpgChannel -import me.lsong.mytv.iptv.TVChannel +import me.lsong.mytv.providers.TVChannel import me.lsong.mytv.ui.player.LeanbackVideoPlayer import me.lsong.mytv.ui.theme.LeanbackTheme @@ -56,13 +56,13 @@ fun MyTvNowPlaying( channelSourceIndexProvider = sourceIndexProvider, ) - val epg = epgListProvider().getEpgChannel(channelProvider()) - if (epg != null) { - MyTvEpgView( - modifier = modifier, - epgProvider = { epg }, - ) - } + // val epg = epgListProvider().getEpgChannel(channelProvider()) + // if (epg != null) { + // MyTvEpgView( + // modifier = modifier, + // epgProvider = { epg }, + // ) + // } MyTvPlayerInfo( modifier = modifier.padding(start = childPadding.start, bottom = childPadding.bottom), metadataProvider = videoPlayerMetadataProvider