This commit is contained in:
Lsong 2024-08-29 17:09:26 +08:00
parent 2c59a4e06d
commit 27e07dae05
7 changed files with 155 additions and 207 deletions

View File

@ -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

View File

@ -1,4 +1,4 @@
package me.lsong.mytv.iptv
package me.lsong.mytv.providers
import android.util.Log
import androidx.compose.runtime.Immutable

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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) {
@ -70,20 +59,15 @@ fun MyTvMenuItem(
CompositionLocalProvider(
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,
onLongSelect = onLongSelect,
),
colors = ListItemDefaults.colors(
focusedContentColor = MaterialTheme.colorScheme.background,
@ -136,64 +120,6 @@ fun MyTvMenuItem(
)
}
}
}
@Composable
fun MyTvMenu(
groups: List<MyTvMenuItem>,
itemsProvider: (String) -> List<MyTvMenuItem>,
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()
},
onUserAction = onUserAction
)
MyTvMenuItemList(
items = items,
selectedItem = focusedItem,
onSelected = { menuItem ->
focusedItem = menuItem
onItemSelected(focusedItem)
},
onUserAction = onUserAction,
focusRequester = rightListFocusRequester
)
}
LaunchedEffect(Unit) {
rightListFocusRequester.requestFocus()
}
}
@Composable
fun MyTvMenuItemList(
@ -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,51 +147,27 @@ fun MyTvMenuItemList(
listState.scrollToItem(maxOf(0, index))
}
Column(
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)
modifier = modifier
.width(250.dp)
.background(MaterialTheme.colorScheme.background.copy(0.8f))
.focusRequester(focusRequester)
) {
itemsIndexed(items, key = { _, item -> item.hashCode() }) { index, item ->
MyTvMenuItem(
item = item,
focusRequester = itemFocusRequesterList[index],
isSelected = selectedIndex == index,
isFocused = selectedIndex == index,
isSelected = selectedIndex == index,
onFocused = { onFocused(item) },
onSelected = { onSelected(item) },
onFocused = {
focusedItem = item
onFocused(item)
},
onFavoriteToggle = { onFavoriteToggle(item) }
onLongSelect = { onLongSelect(item) },
focusRequester = itemFocusRequesterList[index],
)
}
}
// 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
)
}
}
}
}
@Preview

View File

@ -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