chat android tv adaptation

This commit is contained in:
2026-08-09 11:59:33 +03:00
parent db47d8c2ce
commit 523b164964
8 changed files with 302 additions and 164 deletions
@@ -5,15 +5,8 @@ import androiddev.apps.vbox.client.shared.generated.resources.not_found
import androiddev.apps.vbox.client.shared.generated.resources.recommendations
import androiddev.apps.vbox.client.shared.generated.resources.retry
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
@@ -24,7 +17,6 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.itemsIndexed
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
@@ -32,17 +24,13 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
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.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
@@ -54,6 +42,7 @@ import org.jetbrains.compose.resources.stringResource
import ru.shadowsparky.ui.components.ErrorContent
import ru.shadowsparky.ui.components.LazyVerticalGrid
import ru.shadowsparky.ui.components.Loading
import ru.shadowsparky.ui.theme.focusBorder
import ru.shadowsparky.vbox.shared.domain.model.VideoItem
import ru.shadowsparky.vbox.shared.presentation.AppScaffold
import ru.shadowsparky.vbox.shared.presentation.search.AppBarWithSearch
@@ -179,41 +168,11 @@ private fun Poster(
onClick: () -> Unit,
modifier: Modifier = Modifier
) {
var isFocused by remember { mutableStateOf(false) }
val scale by animateFloatAsState(
targetValue = if (isFocused) 1.0f else 0.92f,
animationSpec = spring(stiffness = Spring.StiffnessMediumLow),
label = "scale"
)
val borderColor by animateColorAsState(
targetValue = if (isFocused) MaterialTheme.colorScheme.primary else Color.Transparent,
animationSpec = tween(200),
label = "borderColor"
)
val borderWidth by animateDpAsState(
targetValue = if (isFocused) 3.dp else 0.dp,
animationSpec = tween(200),
label = "borderWidth"
)
Box(
modifier = modifier
.aspectRatio(0.7f)
.graphicsLayer {
scaleX = scale
scaleY = scale
clip = true
shape = RoundedCornerShape(12.dp)
}
.onFocusChanged { isFocused = it.isFocused }
.border(
width = borderWidth,
color = borderColor,
shape = RoundedCornerShape(12.dp)
)
.focusBorder()
.clickable(onClick = onClick)
) {
Image(
@@ -7,7 +7,6 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.window.ComposeViewport
@@ -62,24 +61,19 @@ fun mainInternal() {
startKoin<VBox>()
val lifecycle = LifecycleRegistry()
val composeRoot = document.getElementById("compose-root") as HTMLElement
var rootRef: VBoxRootComponent? = null
val root: VBoxRootComponent = withWebHistory { stateKeeper: StateKeeper, deepLinkUrl: String? ->
if (rootRef == null) {
rootRef = createRoot(lifecycle, stateKeeper, deepLinkUrl)
}
rootRef
createRoot(lifecycle, stateKeeper, deepLinkUrl)
}
hookPageVisibility(lifecycle)
lifecycle.doOnResume { hideElementById("loader") }
ComposeViewport(viewportContainer = composeRoot, content = {
val r = remember { root }
ComposeViewport(composeRoot) {
LaunchedEffect(null) {
r.onFinish.collect { r.navReplace(Route.Loading()) }
root.onFinish.collect { root.navReplace(Route.Loading()) }
}
Box(Modifier.fillMaxSize().padding(WindowInsets.safeDrawing.asPaddingValues())) {
Nav(r)
Nav(root)
}
})
}
}
private fun createRoot(
@@ -87,9 +81,7 @@ private fun createRoot(
stateKeeper: StateKeeper,
deepLinkUrl: String?
): VBoxRootComponent {
val ctx = DefaultComponentContext(
lifecycle = lifecycle, stateKeeper = stateKeeper
)
val ctx = DefaultComponentContext(lifecycle, stateKeeper)
val deep = deepLinkUrl?.let { UrlRoutes.parseUrl(it) }
val initRoute: Route = deep ?: Route.default
return koin.get<RootFactory>().create(context = ctx, initStack = initRoute)