update
This commit is contained in:
parent
2c59a4e06d
commit
27e07dae05
@ -2,7 +2,7 @@ package me.lsong.mytv.epg
|
|||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import kotlinx.serialization.Serializable
|
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.EpgChannel.Companion.currentProgrammes
|
||||||
import me.lsong.mytv.epg.EpgProgramme.Companion.isLive
|
import me.lsong.mytv.epg.EpgProgramme.Companion.isLive
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package me.lsong.mytv.iptv
|
package me.lsong.mytv.providers
|
||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
@ -7,12 +7,19 @@ import androidx.compose.foundation.focusable
|
|||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
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.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.widthIn
|
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.LinearProgressIndicator
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
@ -38,6 +45,7 @@ import androidx.compose.ui.unit.dp
|
|||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import androidx.tv.foundation.lazy.list.TvLazyColumn
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
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
|
||||||
import me.lsong.mytv.epg.EpgList.Companion.currentProgrammes
|
import me.lsong.mytv.epg.EpgList.Companion.currentProgrammes
|
||||||
import me.lsong.mytv.epg.EpgRepository
|
import me.lsong.mytv.epg.EpgRepository
|
||||||
import me.lsong.mytv.iptv.IPTVProvider
|
import me.lsong.mytv.providers.IPTVProvider
|
||||||
import me.lsong.mytv.iptv.TVChannel
|
import me.lsong.mytv.providers.TVChannel
|
||||||
import me.lsong.mytv.iptv.TVGroupList
|
import me.lsong.mytv.providers.TVGroupList
|
||||||
import me.lsong.mytv.iptv.TVGroupList.Companion.channels
|
import me.lsong.mytv.providers.TVGroupList.Companion.channels
|
||||||
import me.lsong.mytv.iptv.TVGroupList.Companion.findGroupIndex
|
import me.lsong.mytv.providers.TVGroupList.Companion.findGroupIndex
|
||||||
import me.lsong.mytv.iptv.TVProvider
|
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.MyTvMenu
|
|
||||||
import me.lsong.mytv.ui.components.MyTvMenuItem
|
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.components.MyTvNowPlaying
|
||||||
import me.lsong.mytv.ui.player.MyTvVideoScreen
|
import me.lsong.mytv.ui.player.MyTvVideoScreen
|
||||||
import me.lsong.mytv.ui.player.rememberLeanbackVideoPlayerState
|
import me.lsong.mytv.ui.player.rememberLeanbackVideoPlayerState
|
||||||
@ -143,7 +151,7 @@ fun MyTvMenuWidget(
|
|||||||
channelProvider: () -> TVChannel = { TVChannel() },
|
channelProvider: () -> TVChannel = { TVChannel() },
|
||||||
groupListProvider: () -> TVGroupList = { TVGroupList() },
|
groupListProvider: () -> TVGroupList = { TVGroupList() },
|
||||||
onSelected: (TVChannel) -> Unit = {},
|
onSelected: (TVChannel) -> Unit = {},
|
||||||
onSettings: () -> Unit = {},
|
onSettings: (() -> Unit)? = null,
|
||||||
onUserAction: () -> Unit = {}
|
onUserAction: () -> Unit = {}
|
||||||
) {
|
) {
|
||||||
val groupList = groupListProvider()
|
val groupList = groupListProvider()
|
||||||
@ -179,21 +187,64 @@ fun MyTvMenuWidget(
|
|||||||
} ?: emptyList()
|
} ?: emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
Row {
|
var focusedGroup by remember { mutableStateOf(currentGroup) }
|
||||||
MyTvMenu(
|
var focusedItem by remember { mutableStateOf(currentMenuItem) }
|
||||||
groups = groups,
|
var items by remember { mutableStateOf(itemsProvider(focusedGroup.title)) }
|
||||||
itemsProvider = itemsProvider,
|
val rightListFocusRequester = remember { FocusRequester() }
|
||||||
currentGroup = currentGroup,
|
|
||||||
currentItem = currentMenuItem,
|
Row(modifier = modifier) {
|
||||||
onItemSelected = { selectedItem ->
|
Column (
|
||||||
val selectedChannel = groupList.channels.first { it.title == selectedItem.title }
|
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)
|
onSelected(selectedChannel)
|
||||||
},
|
},
|
||||||
modifier = modifier,
|
|
||||||
onSettings = onSettings,
|
|
||||||
onUserAction = onUserAction,
|
onUserAction = onUserAction,
|
||||||
|
focusRequester = rightListFocusRequester
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
rightListFocusRequester.requestFocus()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
@ -10,13 +10,11 @@ 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 kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import me.lsong.mytv.iptv.TVChannel
|
import me.lsong.mytv.providers.TVChannel
|
||||||
import me.lsong.mytv.iptv.TVGroupList
|
import me.lsong.mytv.providers.TVGroupList
|
||||||
import me.lsong.mytv.iptv.TVGroupList.Companion.channels
|
import me.lsong.mytv.providers.TVGroupList.Companion.channels
|
||||||
import me.lsong.mytv.iptv.TVGroupList.Companion.findChannelIndex
|
import me.lsong.mytv.providers.TVGroupList.Companion.findChannelIndex
|
||||||
import me.lsong.mytv.utils.Constants
|
|
||||||
import me.lsong.mytv.ui.player.LeanbackVideoPlayerState
|
import me.lsong.mytv.ui.player.LeanbackVideoPlayerState
|
||||||
import me.lsong.mytv.ui.player.rememberLeanbackVideoPlayerState
|
import me.lsong.mytv.ui.player.rememberLeanbackVideoPlayerState
|
||||||
import me.lsong.mytv.utils.Settings
|
import me.lsong.mytv.utils.Settings
|
||||||
|
@ -17,7 +17,7 @@ import androidx.compose.ui.Alignment
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
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.ui.theme.LeanbackTheme
|
||||||
import me.lsong.mytv.utils.isIPv6
|
import me.lsong.mytv.utils.isIPv6
|
||||||
|
|
||||||
|
@ -1,30 +1,19 @@
|
|||||||
package me.lsong.mytv.ui.components
|
package me.lsong.mytv.ui.components
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
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.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.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.layout.wrapContentHeight
|
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.Composable
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.runtime.snapshotFlow
|
import androidx.compose.runtime.snapshotFlow
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.alpha
|
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.focusRequester
|
import androidx.compose.ui.focus.focusRequester
|
||||||
import androidx.compose.ui.focus.onFocusChanged
|
import androidx.compose.ui.focus.onFocusChanged
|
||||||
@ -59,7 +48,7 @@ fun MyTvMenuItem(
|
|||||||
isSelected: Boolean = false,
|
isSelected: Boolean = false,
|
||||||
onFocused: () -> Unit = {},
|
onFocused: () -> Unit = {},
|
||||||
onSelected: () -> Unit = {},
|
onSelected: () -> Unit = {},
|
||||||
onFavoriteToggle: () -> Unit = {},
|
onLongSelect: () -> Unit = {},
|
||||||
focusRequester: FocusRequester = remember { FocusRequester() },
|
focusRequester: FocusRequester = remember { FocusRequester() },
|
||||||
) {
|
) {
|
||||||
LaunchedEffect(isSelected) {
|
LaunchedEffect(isSelected) {
|
||||||
@ -71,130 +60,67 @@ fun MyTvMenuItem(
|
|||||||
LocalContentColor provides if (isFocused) MaterialTheme.colorScheme.background
|
LocalContentColor provides if (isFocused) MaterialTheme.colorScheme.background
|
||||||
else MaterialTheme.colorScheme.onBackground
|
else MaterialTheme.colorScheme.onBackground
|
||||||
) {
|
) {
|
||||||
Box(
|
androidx.tv.material3.ListItem(
|
||||||
modifier = Modifier.clip(ListItemDefaults.shape().shape),
|
modifier = modifier
|
||||||
) {
|
.focusRequester(focusRequester)
|
||||||
androidx.tv.material3.ListItem(
|
.onFocusChanged { if (it.isFocused) onFocused() }
|
||||||
modifier = modifier
|
.handleLeanbackKeyEvents(
|
||||||
.align(Alignment.Center)
|
key = item.hashCode(),
|
||||||
.focusRequester(focusRequester)
|
onSelect = onSelected,
|
||||||
.onFocusChanged { if (it.isFocused) onFocused() }
|
onLongSelect = onLongSelect,
|
||||||
.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),
|
|
||||||
),
|
),
|
||||||
onClick = onSelected,
|
colors = ListItemDefaults.colors(
|
||||||
selected = isSelected,
|
focusedContentColor = MaterialTheme.colorScheme.background,
|
||||||
leadingContent = item.icon?.let { icon ->
|
focusedContainerColor = MaterialTheme.colorScheme.onBackground,
|
||||||
{
|
selectedContainerColor = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f),
|
||||||
when (icon) {
|
),
|
||||||
is ImageVector -> Icon(
|
onClick = onSelected,
|
||||||
imageVector = icon,
|
selected = isSelected,
|
||||||
contentDescription = item.title,
|
leadingContent = item.icon?.let { icon ->
|
||||||
modifier = Modifier.size(24.dp)
|
{
|
||||||
)
|
when (icon) {
|
||||||
is String -> if (icon.isEmpty()) {
|
is ImageVector -> Icon(
|
||||||
Text(
|
imageVector = icon,
|
||||||
modifier = Modifier
|
contentDescription = item.title,
|
||||||
.size(40.dp)
|
modifier = Modifier.size(24.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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
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),
|
|
||||||
)
|
)
|
||||||
|
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
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
)
|
},
|
||||||
}
|
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<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
|
@Composable
|
||||||
fun MyTvMenuItemList(
|
fun MyTvMenuItemList(
|
||||||
items: List<MyTvMenuItem>,
|
items: List<MyTvMenuItem>,
|
||||||
@ -202,15 +128,12 @@ fun MyTvMenuItemList(
|
|||||||
onUserAction: () -> Unit = {},
|
onUserAction: () -> Unit = {},
|
||||||
onFocused: (MyTvMenuItem) -> Unit = {},
|
onFocused: (MyTvMenuItem) -> Unit = {},
|
||||||
onSelected: (MyTvMenuItem) -> Unit = {},
|
onSelected: (MyTvMenuItem) -> Unit = {},
|
||||||
onFavoriteToggle: (MyTvMenuItem) -> Unit = {},
|
onLongSelect: (MyTvMenuItem) -> Unit = {},
|
||||||
focusRequester: FocusRequester = remember { FocusRequester() },
|
focusRequester: FocusRequester = remember { FocusRequester() },
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
onSettings: (() -> Unit)? = null,
|
|
||||||
) {
|
) {
|
||||||
var focusedItem by remember { mutableStateOf(selectedItem) }
|
|
||||||
val selectedIndex = remember(selectedItem, items) { items.indexOf(selectedItem) }
|
val selectedIndex = remember(selectedItem, items) { items.indexOf(selectedItem) }
|
||||||
val itemFocusRequesterList = remember(items) { List(items.size) { FocusRequester() } }
|
val itemFocusRequesterList = remember(items) { List(items.size) { FocusRequester() } }
|
||||||
val settingsFocusRequester = remember { FocusRequester() }
|
|
||||||
val listState = rememberTvLazyListState()
|
val listState = rememberTvLazyListState()
|
||||||
|
|
||||||
LaunchedEffect(listState) {
|
LaunchedEffect(listState) {
|
||||||
@ -224,50 +147,26 @@ fun MyTvMenuItemList(
|
|||||||
listState.scrollToItem(maxOf(0, index))
|
listState.scrollToItem(maxOf(0, index))
|
||||||
}
|
}
|
||||||
|
|
||||||
Column(
|
TvLazyColumn(
|
||||||
|
state = listState,
|
||||||
|
contentPadding = PaddingValues(8.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.fillMaxHeight()
|
|
||||||
.width(250.dp)
|
.width(250.dp)
|
||||||
.background(MaterialTheme.colorScheme.background.copy(0.8f))
|
.background(MaterialTheme.colorScheme.background.copy(0.8f))
|
||||||
.focusRequester(focusRequester)
|
.focusRequester(focusRequester)
|
||||||
) {
|
) {
|
||||||
TvLazyColumn(
|
itemsIndexed(items, key = { _, item -> item.hashCode() }) { index, item ->
|
||||||
state = listState,
|
MyTvMenuItem(
|
||||||
contentPadding = PaddingValues(8.dp),
|
item = item,
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
isFocused = selectedIndex == index,
|
||||||
modifier = Modifier.weight(1f).align(Alignment.CenterHorizontally)
|
isSelected = selectedIndex == index,
|
||||||
) {
|
onFocused = { onFocused(item) },
|
||||||
itemsIndexed(items, key = { _, item -> item.hashCode() }) { index, item ->
|
onSelected = { onSelected(item) },
|
||||||
MyTvMenuItem(
|
onLongSelect = { onLongSelect(item) },
|
||||||
item = item,
|
focusRequester = itemFocusRequesterList[index],
|
||||||
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
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ import androidx.compose.ui.unit.dp
|
|||||||
import me.lsong.mytv.rememberLeanbackChildPadding
|
import me.lsong.mytv.rememberLeanbackChildPadding
|
||||||
import me.lsong.mytv.epg.EpgList
|
import me.lsong.mytv.epg.EpgList
|
||||||
import me.lsong.mytv.epg.EpgList.Companion.getEpgChannel
|
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.player.LeanbackVideoPlayer
|
||||||
import me.lsong.mytv.ui.theme.LeanbackTheme
|
import me.lsong.mytv.ui.theme.LeanbackTheme
|
||||||
|
|
||||||
@ -56,13 +56,13 @@ fun MyTvNowPlaying(
|
|||||||
channelSourceIndexProvider = sourceIndexProvider,
|
channelSourceIndexProvider = sourceIndexProvider,
|
||||||
)
|
)
|
||||||
|
|
||||||
val epg = epgListProvider().getEpgChannel(channelProvider())
|
// val epg = epgListProvider().getEpgChannel(channelProvider())
|
||||||
if (epg != null) {
|
// if (epg != null) {
|
||||||
MyTvEpgView(
|
// MyTvEpgView(
|
||||||
modifier = modifier,
|
// modifier = modifier,
|
||||||
epgProvider = { epg },
|
// epgProvider = { epg },
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
MyTvPlayerInfo(
|
MyTvPlayerInfo(
|
||||||
modifier = modifier.padding(start = childPadding.start, bottom = childPadding.bottom),
|
modifier = modifier.padding(start = childPadding.start, bottom = childPadding.bottom),
|
||||||
metadataProvider = videoPlayerMetadataProvider
|
metadataProvider = videoPlayerMetadataProvider
|
||||||
|
Loading…
x
Reference in New Issue
Block a user