diff --git a/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/video/BottomControls.kt b/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/video/BottomControls.kt index 64025cd0..ba860756 100644 --- a/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/video/BottomControls.kt +++ b/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/video/BottomControls.kt @@ -31,6 +31,8 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.media3.common.util.UnstableApi import dev.datlag.burningseries.common.toDuration +import dev.datlag.tooling.compose.platform.PlatformText +import dev.datlag.tooling.compose.platform.ProvideNonTvContentColor import dev.datlag.tooling.decompose.lifecycle.collectAsStateWithLifecycle import io.github.aakira.napier.Napier import kotlinx.coroutines.flow.StateFlow @@ -58,42 +60,44 @@ fun BottomControls( containerColor = Color.Black.copy(alpha = 0.5F), contentColor = Color.White ) { - val source = remember { MutableInteractionSource() } - val dragging by source.collectIsDraggedAsState() - var changingProgress by remember { mutableLongStateOf(progress) } - val displayProgress = remember(dragging, progress, changingProgress) { - if (dragging) { - changingProgress - } else { - progress - } - } - - Text( - text = displayProgress.toDuration(), - maxLines = 1 - ) - Slider( - modifier = Modifier.weight(1F), - value = displayProgress.toFloat(), - valueRange = 0F..length.toFloat(), - onValueChange = { - if (dragging) { - changingProgress = it.toLong() - playerWrapper.showControls() - } - }, - onValueChangeFinished = { + ProvideNonTvContentColor { + val source = remember { MutableInteractionSource() } + val dragging by source.collectIsDraggedAsState() + var changingProgress by remember { mutableLongStateOf(progress) } + val displayProgress = remember(dragging, progress, changingProgress) { if (dragging) { - playerWrapper.seekTo(changingProgress) + changingProgress + } else { + progress } - }, - interactionSource = source - ) - Text( - text = length.toDuration(), - maxLines = 1 - ) + } + + PlatformText( + text = displayProgress.toDuration(), + maxLines = 1 + ) + Slider( + modifier = Modifier.weight(1F), + value = displayProgress.toFloat(), + valueRange = 0F..length.toFloat(), + onValueChange = { + if (dragging) { + changingProgress = it.toLong() + playerWrapper.showControls() + } + }, + onValueChangeFinished = { + if (dragging) { + playerWrapper.seekTo(changingProgress) + } + }, + interactionSource = source + ) + PlatformText( + text = length.toDuration(), + maxLines = 1 + ) + } } } } \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/video/CenterControls.kt b/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/video/CenterControls.kt index 35d12767..78334dca 100644 --- a/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/video/CenterControls.kt +++ b/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/video/CenterControls.kt @@ -25,11 +25,20 @@ import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.IconButtonDefaults import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.SideEffect 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.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.draw.drawWithContent +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusProperties +import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.BlendMode import androidx.compose.ui.graphics.Color @@ -41,9 +50,15 @@ import dev.datlag.burningseries.common.drawProgress import dev.datlag.burningseries.model.Series import dev.datlag.burningseries.network.state.EpisodeState import dev.datlag.skeo.DirectLink +import dev.datlag.tooling.Platform +import dev.datlag.tooling.compose.platform.PlatformIcon +import dev.datlag.tooling.compose.platform.PlatformIconButton +import dev.datlag.tooling.compose.platform.localContentColor +import dev.datlag.tooling.compose.platform.rememberIsTv import dev.datlag.tooling.decompose.lifecycle.collectAsStateWithLifecycle import kotlinx.collections.immutable.ImmutableCollection +@ExperimentalComposeUiApi @OptIn(UnstableApi::class) @Composable fun CenterControls( @@ -58,6 +73,9 @@ fun CenterControls( onNext: (Series.Episode, ImmutableCollection) -> Unit ) { val isFinished by playerWrapper.isFinished.collectAsStateWithLifecycle() + val (replay, play, forward) = remember { FocusRequester.createRefs() } + + var focusPlay by remember { mutableStateOf(true) } AnimatedVisibility( modifier = modifier, @@ -69,31 +87,44 @@ fun CenterControls( horizontalArrangement = Arrangement.SpaceEvenly, verticalAlignment = Alignment.CenterVertically ) { - IconButton( + PlatformIconButton( modifier = Modifier.background( color = Color.Black.copy(alpha = 0.5F), shape = CircleShape - ), + ).focusRequester(replay).focusProperties { + next = play + }, onClick = onReplayClick ) { - Icon( + PlatformIcon( imageVector = Icons.Rounded.FastRewind, contentDescription = null, - tint = Color.White + tint = if (Platform.rememberIsTv()) { + Platform.localContentColor() + } else { + Color.White + } ) } - IconButton( + PlatformIconButton( modifier = Modifier.background( color = Color.Black.copy(alpha = 0.5F), shape = CircleShape - ), + ).focusRequester(play).focusProperties { + previous = replay + next = forward + }, onClick = onPauseToggle ) { - Icon( + PlatformIcon( imageVector = if (isPlaying) Icons.Rounded.Pause else Icons.Rounded.PlayArrow, contentDescription = null, - tint = Color.White + tint = if (Platform.rememberIsTv()) { + Platform.localContentColor() + } else { + Color.White + } ) } @@ -112,38 +143,72 @@ fun CenterControls( } ) if (isFinished && nextState is EpisodeState.SuccessStream) { - IconButton( + PlatformIconButton( modifier = Modifier.background( color = Color.Black.copy(alpha = 0.5F), shape = CircleShape - ), + ).focusRequester(forward).focusProperties { + previous = play + }, onClick = { onNext(nextState.episode, nextState.results) } ) { - Icon( - modifier = Modifier.fillMaxSize().drawProgress(Color.White, animatedProgress).padding(8.dp), + PlatformIcon( + modifier = Modifier + .fillMaxSize() + .drawProgress( + if (Platform.rememberIsTv()) { + Platform.localContentColor() + } else { + Color.White + }, + animatedProgress + ).padding(8.dp), imageVector = Icons.Rounded.SkipNext, contentDescription = null, - tint = Color.White + tint = if (Platform.rememberIsTv()) { + Platform.localContentColor() + } else { + Color.White + } ) } } else { - IconButton( + PlatformIconButton( modifier = Modifier.background( color = Color.Black.copy(alpha = 0.5F), shape = CircleShape - ), + ).focusRequester(forward).focusProperties { + previous = play + }, onClick = onForwardClick, enabled = !isFinished ) { - Icon( + PlatformIcon( imageVector = Icons.Rounded.FastForward, contentDescription = null, - tint = Color.White + tint = if (Platform.rememberIsTv()) { + Platform.localContentColor() + } else { + Color.White + } ) } } } + + SideEffect { + if (focusPlay) { + play.requestFocus() + focusPlay = false + } + } + + DisposableEffect(focusPlay) { + onDispose { + focusPlay = true + } + } } } \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/video/TopControls.kt b/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/video/TopControls.kt index eeea3355..3d744a5d 100644 --- a/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/video/TopControls.kt +++ b/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/video/TopControls.kt @@ -50,6 +50,10 @@ import dev.datlag.kast.DeviceType import dev.datlag.kast.Kast import dev.datlag.kast.UnselectReason import dev.datlag.tooling.Platform +import dev.datlag.tooling.compose.platform.PlatformIcon +import dev.datlag.tooling.compose.platform.PlatformIconButton +import dev.datlag.tooling.compose.platform.PlatformText +import dev.datlag.tooling.compose.platform.ProvideNonTvContentColor import dev.datlag.tooling.compose.platform.typography import dev.datlag.tooling.decompose.lifecycle.collectAsStateWithLifecycle import org.jetbrains.compose.resources.stringResource @@ -79,34 +83,38 @@ fun TopControls( Box(modifier = Modifier.matchParentSize().background(Color.Black.copy(alpha = 0.5F))) TopAppBar( navigationIcon = { - IconButton( - onClick = onBack - ) { - Icon( - imageVector = Icons.Rounded.ArrowBackIosNew, - contentDescription = null - ) + ProvideNonTvContentColor { + PlatformIconButton( + onClick = onBack + ) { + PlatformIcon( + imageVector = Icons.Rounded.ArrowBackIosNew, + contentDescription = null + ) + } } }, title = { - Column( - modifier = Modifier.fillMaxWidth(), - verticalArrangement = Arrangement.spacedBy(4.dp, Alignment.CenterVertically) - ) { - Text( - text = mainTitle, - softWrap = true, - overflow = TextOverflow.Ellipsis, - maxLines = 1 - ) - subTitle?.ifBlank { null }?.let { sub -> - Text( - text = sub, + ProvideNonTvContentColor { + Column( + modifier = Modifier.fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(4.dp, Alignment.CenterVertically) + ) { + PlatformText( + text = mainTitle, softWrap = true, overflow = TextOverflow.Ellipsis, - maxLines = 1, - style = Platform.typography().labelMedium + maxLines = 1 ) + subTitle?.ifBlank { null }?.let { sub -> + PlatformText( + text = sub, + softWrap = true, + overflow = TextOverflow.Ellipsis, + maxLines = 1, + style = Platform.typography().labelMedium + ) + } } } }, diff --git a/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/video/VideoScreen.android.kt b/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/video/VideoScreen.android.kt index e984f3e6..f33723ff 100644 --- a/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/video/VideoScreen.android.kt +++ b/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/video/VideoScreen.android.kt @@ -32,9 +32,8 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment +import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier -import androidx.compose.ui.focus.FocusRequester -import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.key.onKeyEvent import androidx.compose.ui.platform.LocalContext @@ -85,6 +84,7 @@ import kotlinx.coroutines.isActive import kotlinx.datetime.Clock import kotlin.random.Random +@ExperimentalComposeUiApi @OptIn(UnstableApi::class) @Composable actual fun VideoScreen(component: VideoComponent) { @@ -179,6 +179,9 @@ actual fun VideoScreen(component: VideoComponent) { } Scaffold( + modifier = Modifier.focusable().onKeyEvent { key -> + playerWrapper.dispatchKey(controlsVisible, key) + }, topBar = { TopControls( isVisible = controlsVisible, @@ -205,11 +208,7 @@ actual fun VideoScreen(component: VideoComponent) { AndroidView( modifier = Modifier .fillMaxSize() - .background(Color.Black) - .focusable() - .onKeyEvent { key -> - playerWrapper.dispatchKey(controlsVisible, key) - }, + .background(Color.Black), factory = { viewContext -> PlayerView(viewContext) }, diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/medium/component/AniFlowCard.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/medium/component/AniFlowCard.kt index c73e46a9..1c145628 100644 --- a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/medium/component/AniFlowCard.kt +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/medium/component/AniFlowCard.kt @@ -19,6 +19,8 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import dev.datlag.burningseries.other.AniFlow import dev.datlag.tooling.Platform +import dev.datlag.tooling.compose.platform.PlatformCard +import dev.datlag.tooling.compose.platform.PlatformText import dev.datlag.tooling.compose.platform.shapes import dev.datlag.tooling.compose.platform.typography import dev.icerock.moko.resources.compose.painterResource @@ -30,7 +32,7 @@ fun AniFlowCard( ) { val uriHandler = LocalUriHandler.current - Card( + PlatformCard( modifier = modifier, onClick = { uriHandler.openUri(AniFlow.googlePlay) @@ -49,12 +51,12 @@ fun AniFlowCard( Column( verticalArrangement = Arrangement.spacedBy(8.dp) ) { - Text( + PlatformText( text = stringResource(AniFlow.title), fontWeight = FontWeight.Bold, style = Platform.typography().titleLarge ) - Text(text = stringResource(AniFlow.subTitle)) + PlatformText(text = stringResource(AniFlow.subTitle)) } } } diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/medium/component/DescriptionSection.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/medium/component/DescriptionSection.kt index 9346ec29..c0c80e24 100644 --- a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/medium/component/DescriptionSection.kt +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/medium/component/DescriptionSection.kt @@ -29,6 +29,10 @@ import dev.datlag.burningseries.composeapp.generated.resources.Res import dev.datlag.burningseries.composeapp.generated.resources.description import dev.datlag.burningseries.ui.navigation.screen.medium.MediumComponent import dev.datlag.tooling.Platform +import dev.datlag.tooling.compose.platform.PlatformButtonScale +import dev.datlag.tooling.compose.platform.PlatformIcon +import dev.datlag.tooling.compose.platform.PlatformIconButton +import dev.datlag.tooling.compose.platform.PlatformText import dev.datlag.tooling.compose.platform.typography import dev.datlag.tooling.decompose.lifecycle.collectAsStateWithLifecycle import org.jetbrains.compose.resources.stringResource @@ -54,7 +58,7 @@ internal fun DescriptionSection( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(16.dp) ) { - Text( + PlatformText( modifier = Modifier.weight(1F), text = stringResource(Res.string.description), style = Platform.typography().headlineSmall @@ -71,7 +75,7 @@ internal fun DescriptionSection( ) SelectionContainer { - Text( + PlatformText( modifier = Modifier.padding(horizontal = 16.dp), text = description!!, maxLines = max(animatedLines, 1), @@ -85,11 +89,12 @@ internal fun DescriptionSection( ) } if (descriptionExpandable) { - IconButton( - modifier = Modifier.fillMaxWidth(), + PlatformIconButton( + modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp), onClick = { descriptionExpanded = !descriptionExpanded - } + }, + scale = PlatformButtonScale.icon(scale = 1F, focusedScale = 1.02F) ) { val icon = if (descriptionExpanded) { Icons.Rounded.ExpandLess @@ -97,7 +102,7 @@ internal fun DescriptionSection( Icons.Rounded.ExpandMore } - Icon( + PlatformIcon( imageVector = icon, contentDescription = null )