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
@@ -11,6 +11,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.ColorScheme
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
@@ -18,6 +19,7 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialExpressiveTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarDefaults
import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.NavigationRail
import androidx.compose.material3.NavigationRailItem
@@ -30,6 +32,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.Immutable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.painter.Painter
@@ -37,7 +40,6 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import ru.shadowsparky.ui.nav.Route
import ru.shadowsparky.ui.theme.ComponentDefaults
import ru.shadowsparky.ui.theme.getColorScheme
@Immutable
@@ -138,8 +140,8 @@ private fun <T : Route> BottomBar(
.windowInsetsPadding(WindowInsets.navigationBars)
) {
NavigationBar(
containerColor = ComponentDefaults.navBarContentColor(),
modifier = ComponentDefaults.roundedClip().blurEffect()
containerColor = navBarContentColor(),
modifier = Modifier.clip(RoundedCornerShape(24.dp)).blurEffect()
) {
entries.forEach { entry ->
NavigationBarItem(
@@ -153,6 +155,14 @@ private fun <T : Route> BottomBar(
}
}
@Composable
private fun navBarContentColor(): Color {
return if (isBlurEnabled()) {
MaterialTheme.colorScheme.surfaceContainerHigh.copy(alpha = 0.5f)
} else {
NavigationBarDefaults.containerColor
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@@ -11,10 +11,11 @@ import androidx.compose.material3.SearchBarState
import androidx.compose.material3.rememberSearchBarState
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.unit.Dp
import ru.shadowsparky.ui.blurEffect
import ru.shadowsparky.ui.theme.ComponentDefaults
import ru.shadowsparky.ui.isBlurEnabled
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@@ -25,7 +26,7 @@ fun AppBarWithSearch(
navigationIcon: @Composable (() -> Unit)? = null,
actions: @Composable (RowScope.() -> Unit)? = null,
shape: Shape = SearchBarDefaults.inputFieldShape,
colors: AppBarWithSearchColors = ComponentDefaults.appBarWithSearchDefaults(),
colors: AppBarWithSearchColors = appBarWithSearchDefaults(),
tonalElevation: Dp = SearchBarDefaults.TonalElevation,
shadowElevation: Dp = SearchBarDefaults.ShadowElevation,
contentPadding: PaddingValues = SearchBarDefaults.AppBarContentPadding,
@@ -47,3 +48,15 @@ fun AppBarWithSearch(
scrollBehavior
)
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun appBarWithSearchDefaults(): AppBarWithSearchColors {
return if (isBlurEnabled()) {
SearchBarDefaults.appBarWithSearchColors(
appBarContainerColor = Color.Transparent
)
} else {
SearchBarDefaults.appBarWithSearchColors()
}
}
@@ -10,9 +10,10 @@ import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.Dp
import ru.shadowsparky.ui.blurEffect
import ru.shadowsparky.ui.theme.ComponentDefaults
import ru.shadowsparky.ui.isBlurEnabled
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@@ -23,7 +24,7 @@ fun TopAppBar(
actions: @Composable RowScope.() -> Unit = {},
expandedHeight: Dp = TopAppBarDefaults.TopAppBarExpandedHeight,
windowInsets: WindowInsets = TopAppBarDefaults.windowInsets,
colors: TopAppBarColors = ComponentDefaults.topAppBarColors(),
colors: TopAppBarColors = topAppBarColors(),
scrollBehavior: TopAppBarScrollBehavior? = null,
contentPadding: PaddingValues = TopAppBarDefaults.ContentPadding
) {
@@ -39,3 +40,16 @@ fun TopAppBar(
contentPadding = contentPadding
)
}
@Composable
private fun topAppBarColors(): TopAppBarColors {
return if (isBlurEnabled()) {
TopAppBarDefaults.topAppBarColors(
Color.Transparent,
Color.Transparent
)
} else {
TopAppBarDefaults.topAppBarColors()
}
}
@@ -1,56 +1,68 @@
package ru.shadowsparky.ui.theme
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.border
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.AppBarWithSearchColors
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.NavigationBarDefaults
import androidx.compose.material3.SearchBarDefaults
import androidx.compose.material3.TopAppBarColors
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.unit.dp
import ru.shadowsparky.ui.isBlurEnabled
object ComponentDefaults {
@Composable
fun topAppBarColors(): TopAppBarColors {
return if (isBlurEnabled()) {
TopAppBarDefaults.topAppBarColors(
Color.Transparent,
Color.Transparent
internal fun focusBorder(
modifier: Modifier,
needScale: Boolean
): Modifier {
var isFocused by remember { mutableStateOf(false) }
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"
)
val scale by animateFloatAsState(
targetValue = if (isFocused) 1.0f else 0.92f,
animationSpec = spring(stiffness = Spring.StiffnessMediumLow),
label = "scale"
)
val mod = if (needScale) {
modifier.graphicsLayer {
scaleX = scale
scaleY = scale
clip = true
shape = RoundedCornerShape(12.dp)
}
} else {
modifier
}
return mod.onFocusChanged { isFocused = it.isFocused }
.border(
width = borderWidth,
color = borderColor,
shape = RoundedCornerShape(12.dp)
)
} else {
TopAppBarDefaults.topAppBarColors()
}
}
@Composable
fun navBarContentColor(): Color {
return if (isBlurEnabled()) {
MaterialTheme.colorScheme.surfaceContainerHigh.copy(alpha = 0.5f)
} else {
NavigationBarDefaults.containerColor
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun appBarWithSearchDefaults(): AppBarWithSearchColors {
return if (isBlurEnabled()) {
SearchBarDefaults.appBarWithSearchColors(
appBarContainerColor = Color.Transparent
)
} else {
SearchBarDefaults.appBarWithSearchColors()
}
}
fun roundedClip(modifier: Modifier = Modifier): Modifier {
return modifier.clip(RoundedCornerShape(24.dp))
}
}
@Composable
fun Modifier.focusBorder(needScale: Boolean = true): Modifier {
return ComponentDefaults.focusBorder(this, needScale)
}
@@ -38,10 +38,10 @@ fun hookPageVisibility(lifecycle: LifecycleRegistry) {
fun onPageHideShow(hidden: Boolean) {
if (hidden) {
// уходим глубже: STARTED → CREATED
when (lifecycle.state) {
Lifecycle.State.RESUMED -> {
lifecycle.pause(); lifecycle.stop()
lifecycle.pause();
lifecycle.stop()
}
Lifecycle.State.STARTED -> {
@@ -51,14 +51,16 @@ fun hookPageVisibility(lifecycle: LifecycleRegistry) {
else -> Unit
}
} else {
// pageshow: поднимемся обратно
when (lifecycle.state) {
Lifecycle.State.INITIALIZED -> {
lifecycle.create(); lifecycle.start(); if (pageVisibilityState() == "visible") lifecycle.resume()
lifecycle.create()
lifecycle.start()
if (pageVisibilityState() == "visible") lifecycle.resume()
}
Lifecycle.State.CREATED -> {
lifecycle.start(); if (pageVisibilityState() == "visible") lifecycle.resume()
lifecycle.start()
if (pageVisibilityState() == "visible") lifecycle.resume()
}
Lifecycle.State.STARTED -> {