chat android tv adaptation
This commit is contained in:
+2
-43
@@ -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.recommendations
|
||||||
import androiddev.apps.vbox.client.shared.generated.resources.retry
|
import androiddev.apps.vbox.client.shared.generated.resources.retry
|
||||||
import androidx.compose.animation.AnimatedContent
|
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.Image
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.border
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
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.GridCells
|
||||||
import androidx.compose.foundation.lazy.grid.itemsIndexed
|
import androidx.compose.foundation.lazy.grid.itemsIndexed
|
||||||
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
|
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
@@ -32,17 +24,13 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
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.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
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.graphics.Brush
|
import androidx.compose.ui.graphics.Brush
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.graphicsLayer
|
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
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.ErrorContent
|
||||||
import ru.shadowsparky.ui.components.LazyVerticalGrid
|
import ru.shadowsparky.ui.components.LazyVerticalGrid
|
||||||
import ru.shadowsparky.ui.components.Loading
|
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.domain.model.VideoItem
|
||||||
import ru.shadowsparky.vbox.shared.presentation.AppScaffold
|
import ru.shadowsparky.vbox.shared.presentation.AppScaffold
|
||||||
import ru.shadowsparky.vbox.shared.presentation.search.AppBarWithSearch
|
import ru.shadowsparky.vbox.shared.presentation.search.AppBarWithSearch
|
||||||
@@ -179,41 +168,11 @@ private fun Poster(
|
|||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
modifier: Modifier = Modifier
|
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(
|
Box(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.aspectRatio(0.7f)
|
.aspectRatio(0.7f)
|
||||||
.graphicsLayer {
|
.focusBorder()
|
||||||
scaleX = scale
|
|
||||||
scaleY = scale
|
|
||||||
clip = true
|
|
||||||
shape = RoundedCornerShape(12.dp)
|
|
||||||
}
|
|
||||||
.onFocusChanged { isFocused = it.isFocused }
|
|
||||||
.border(
|
|
||||||
width = borderWidth,
|
|
||||||
color = borderColor,
|
|
||||||
shape = RoundedCornerShape(12.dp)
|
|
||||||
)
|
|
||||||
.clickable(onClick = onClick)
|
.clickable(onClick = onClick)
|
||||||
) {
|
) {
|
||||||
Image(
|
Image(
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import androidx.compose.foundation.layout.fillMaxSize
|
|||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.safeDrawing
|
import androidx.compose.foundation.layout.safeDrawing
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.window.ComposeViewport
|
import androidx.compose.ui.window.ComposeViewport
|
||||||
@@ -62,24 +61,19 @@ fun mainInternal() {
|
|||||||
startKoin<VBox>()
|
startKoin<VBox>()
|
||||||
val lifecycle = LifecycleRegistry()
|
val lifecycle = LifecycleRegistry()
|
||||||
val composeRoot = document.getElementById("compose-root") as HTMLElement
|
val composeRoot = document.getElementById("compose-root") as HTMLElement
|
||||||
var rootRef: VBoxRootComponent? = null
|
|
||||||
val root: VBoxRootComponent = withWebHistory { stateKeeper: StateKeeper, deepLinkUrl: String? ->
|
val root: VBoxRootComponent = withWebHistory { stateKeeper: StateKeeper, deepLinkUrl: String? ->
|
||||||
if (rootRef == null) {
|
createRoot(lifecycle, stateKeeper, deepLinkUrl)
|
||||||
rootRef = createRoot(lifecycle, stateKeeper, deepLinkUrl)
|
|
||||||
}
|
|
||||||
rootRef
|
|
||||||
}
|
}
|
||||||
hookPageVisibility(lifecycle)
|
hookPageVisibility(lifecycle)
|
||||||
lifecycle.doOnResume { hideElementById("loader") }
|
lifecycle.doOnResume { hideElementById("loader") }
|
||||||
ComposeViewport(viewportContainer = composeRoot, content = {
|
ComposeViewport(composeRoot) {
|
||||||
val r = remember { root }
|
|
||||||
LaunchedEffect(null) {
|
LaunchedEffect(null) {
|
||||||
r.onFinish.collect { r.navReplace(Route.Loading()) }
|
root.onFinish.collect { root.navReplace(Route.Loading()) }
|
||||||
}
|
}
|
||||||
Box(Modifier.fillMaxSize().padding(WindowInsets.safeDrawing.asPaddingValues())) {
|
Box(Modifier.fillMaxSize().padding(WindowInsets.safeDrawing.asPaddingValues())) {
|
||||||
Nav(r)
|
Nav(root)
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createRoot(
|
private fun createRoot(
|
||||||
@@ -87,9 +81,7 @@ private fun createRoot(
|
|||||||
stateKeeper: StateKeeper,
|
stateKeeper: StateKeeper,
|
||||||
deepLinkUrl: String?
|
deepLinkUrl: String?
|
||||||
): VBoxRootComponent {
|
): VBoxRootComponent {
|
||||||
val ctx = DefaultComponentContext(
|
val ctx = DefaultComponentContext(lifecycle, stateKeeper)
|
||||||
lifecycle = lifecycle, stateKeeper = stateKeeper
|
|
||||||
)
|
|
||||||
val deep = deepLinkUrl?.let { UrlRoutes.parseUrl(it) }
|
val deep = deepLinkUrl?.let { UrlRoutes.parseUrl(it) }
|
||||||
val initRoute: Route = deep ?: Route.default
|
val initRoute: Route = deep ?: Route.default
|
||||||
return koin.get<RootFactory>().create(context = ctx, initStack = initRoute)
|
return koin.get<RootFactory>().create(context = ctx, initStack = initRoute)
|
||||||
|
|||||||
+190
-54
@@ -7,6 +7,7 @@ import androiddev.feature.chat.chat_client.generated.resources.chat_ok
|
|||||||
import androiddev.feature.chat.chat_client.generated.resources.chat_retry_button
|
import androiddev.feature.chat.chat_client.generated.resources.chat_retry_button
|
||||||
import androiddev.feature.chat.chat_client.generated.resources.send
|
import androiddev.feature.chat.chat_client.generated.resources.send
|
||||||
import androidx.compose.foundation.focusable
|
import androidx.compose.foundation.focusable
|
||||||
|
import androidx.compose.foundation.gestures.animateScrollBy
|
||||||
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
|
||||||
@@ -20,12 +21,14 @@ 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.padding
|
||||||
import androidx.compose.foundation.layout.widthIn
|
import androidx.compose.foundation.layout.widthIn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.foundation.text.KeyboardActions
|
import androidx.compose.foundation.text.KeyboardActions
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
import androidx.compose.foundation.text.selection.SelectionContainer
|
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.Card
|
import androidx.compose.material3.Card
|
||||||
import androidx.compose.material3.CardDefaults
|
import androidx.compose.material3.CardDefaults
|
||||||
@@ -37,20 +40,25 @@ import androidx.compose.material3.OutlinedTextField
|
|||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.runtime.withFrameNanos
|
import androidx.compose.runtime.withFrameNanos
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
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.graphics.Color
|
||||||
import androidx.compose.ui.input.key.Key
|
import androidx.compose.ui.input.key.Key
|
||||||
|
import androidx.compose.ui.input.key.KeyEvent
|
||||||
import androidx.compose.ui.input.key.KeyEventType
|
import androidx.compose.ui.input.key.KeyEventType
|
||||||
import androidx.compose.ui.input.key.key
|
import androidx.compose.ui.input.key.key
|
||||||
import androidx.compose.ui.input.key.onKeyEvent
|
import androidx.compose.ui.input.key.onKeyEvent
|
||||||
import androidx.compose.ui.input.key.type
|
import androidx.compose.ui.input.key.type
|
||||||
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
import androidx.compose.ui.text.LinkAnnotation
|
import androidx.compose.ui.text.LinkAnnotation
|
||||||
import androidx.compose.ui.text.SpanStyle
|
import androidx.compose.ui.text.SpanStyle
|
||||||
import androidx.compose.ui.text.TextLinkStyles
|
import androidx.compose.ui.text.TextLinkStyles
|
||||||
@@ -61,6 +69,7 @@ import androidx.compose.ui.text.style.TextDecoration
|
|||||||
import androidx.compose.ui.text.withLink
|
import androidx.compose.ui.text.withLink
|
||||||
import androidx.compose.ui.unit.LayoutDirection
|
import androidx.compose.ui.unit.LayoutDirection
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import org.jetbrains.compose.resources.painterResource
|
import org.jetbrains.compose.resources.painterResource
|
||||||
import org.jetbrains.compose.resources.stringResource
|
import org.jetbrains.compose.resources.stringResource
|
||||||
import ru.shadowsparky.chat.domain.ChatRoles
|
import ru.shadowsparky.chat.domain.ChatRoles
|
||||||
@@ -69,6 +78,9 @@ import ru.shadowsparky.chat.domain.TextToken
|
|||||||
import ru.shadowsparky.ui.components.AlertContent
|
import ru.shadowsparky.ui.components.AlertContent
|
||||||
import ru.shadowsparky.ui.components.LazyColumn
|
import ru.shadowsparky.ui.components.LazyColumn
|
||||||
import ru.shadowsparky.ui.components.Loading
|
import ru.shadowsparky.ui.components.Loading
|
||||||
|
import ru.shadowsparky.ui.components.TextPreference
|
||||||
|
import ru.shadowsparky.ui.components.isTv
|
||||||
|
import ru.shadowsparky.ui.theme.focusBorder
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ChatContent(
|
fun ChatContent(
|
||||||
@@ -132,7 +144,6 @@ private fun ChatContent(
|
|||||||
val listState = rememberLazyListState()
|
val listState = rememberLazyListState()
|
||||||
var textInput by remember { mutableStateOf("") }
|
var textInput by remember { mutableStateOf("") }
|
||||||
val textFieldFocusRequester = remember { FocusRequester() }
|
val textFieldFocusRequester = remember { FocusRequester() }
|
||||||
val listFocusRequester = remember { FocusRequester() }
|
|
||||||
|
|
||||||
LaunchedEffect(state.messages.size) {
|
LaunchedEffect(state.messages.size) {
|
||||||
if (state.messages.isNotEmpty()) {
|
if (state.messages.isNotEmpty()) {
|
||||||
@@ -145,18 +156,25 @@ private fun ChatContent(
|
|||||||
Column(modifier = Modifier.fillMaxSize()) {
|
Column(modifier = Modifier.fillMaxSize()) {
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
state = listState,
|
state = listState,
|
||||||
modifier = Modifier.focusRequester(listFocusRequester)
|
modifier = Modifier.weight(1f)
|
||||||
.weight(1f)
|
|
||||||
.fillMaxWidth(),
|
.fillMaxWidth(),
|
||||||
contentPadding = contentPadding,
|
contentPadding = contentPadding,
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
reverseLayout = true
|
reverseLayout = true
|
||||||
) {
|
) {
|
||||||
items(items = state.messages, key = { it.id ?: it.hashCode() }) { message ->
|
itemsIndexed(
|
||||||
|
items = state.messages,
|
||||||
|
key = { _, item -> item.id ?: item.hashCode() }
|
||||||
|
) { index, message ->
|
||||||
MessageBubble(
|
MessageBubble(
|
||||||
message = message,
|
message = message,
|
||||||
onParseMessage = onParseMessage,
|
onParseMessage = onParseMessage,
|
||||||
onClickLink = onClickLink
|
onClickLink = onClickLink,
|
||||||
|
onKeyEvent = {
|
||||||
|
if (index == 0) {
|
||||||
|
if (it.key == Key.DirectionDown) textFieldFocusRequester.requestFocus()
|
||||||
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
item { Spacer(Modifier.height(4.dp)) }
|
item { Spacer(Modifier.height(4.dp)) }
|
||||||
@@ -178,7 +196,7 @@ private fun ChatContent(
|
|||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = textInput,
|
value = textInput,
|
||||||
onValueChange = { textInput = it },
|
onValueChange = { textInput = it },
|
||||||
modifier = Modifier.focusRequester(textFieldFocusRequester).resetFocusOnUpEvent(listFocusRequester).weight(1f),
|
modifier = Modifier.focusRequester(textFieldFocusRequester).weight(1f),
|
||||||
shape = RoundedCornerShape(24.dp),
|
shape = RoundedCornerShape(24.dp),
|
||||||
placeholder = { Text(stringResource(Res.string.chat_input_placeholder)) },
|
placeholder = { Text(stringResource(Res.string.chat_input_placeholder)) },
|
||||||
enabled = !state.isSending,
|
enabled = !state.isSending,
|
||||||
@@ -207,73 +225,191 @@ private fun ChatContent(
|
|||||||
private fun MessageBubble(
|
private fun MessageBubble(
|
||||||
message: Message,
|
message: Message,
|
||||||
onParseMessage: (String) -> List<TextToken>,
|
onParseMessage: (String) -> List<TextToken>,
|
||||||
onClickLink: (String) -> Unit
|
onClickLink: (String) -> Unit,
|
||||||
|
onKeyEvent: (KeyEvent) -> Unit
|
||||||
) {
|
) {
|
||||||
val isUser = message.role == ChatRoles.USER
|
val isUser = message.role == ChatRoles.USER
|
||||||
val alignment = if (isUser) Alignment.CenterEnd else Alignment.CenterStart
|
|
||||||
val containerColor = if (isUser) MaterialTheme.colorScheme.primaryContainer else MaterialTheme.colorScheme.surfaceVariant
|
|
||||||
val contentColor = if (isUser) MaterialTheme.colorScheme.onPrimaryContainer else MaterialTheme.colorScheme.onSurfaceVariant
|
|
||||||
val tokens = remember(message.content) { onParseMessage(message.content) }
|
val tokens = remember(message.content) { onParseMessage(message.content) }
|
||||||
|
val linkTokens by derivedStateOf { tokens.filterIsInstance<TextToken.Link>() }
|
||||||
val primaryColor = MaterialTheme.colorScheme.primary
|
val primaryColor = MaterialTheme.colorScheme.primary
|
||||||
val focusBackgroundColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.15f)
|
val focusedColor = MaterialTheme.colorScheme.error
|
||||||
val annotatedString = remember(tokens, primaryColor, focusBackgroundColor) {
|
val isTv = isTv()
|
||||||
buildAnnotatedString {
|
val annotatedString = remember(tokens) {
|
||||||
tokens.forEach { token ->
|
if (isTv) {
|
||||||
when (token) {
|
buildAnnotatedString { append(message.content) }
|
||||||
is TextToken.Plain -> append(token.text)
|
} else {
|
||||||
is TextToken.Link -> {
|
buildText(tokens, primaryColor, focusedColor, onClickLink)
|
||||||
val linkAnnotation = LinkAnnotation.Clickable(
|
}
|
||||||
tag = "LINK",
|
}
|
||||||
styles = TextLinkStyles(
|
var dialog by remember { mutableStateOf(false) }
|
||||||
style = SpanStyle(
|
var boxModifier = Modifier.widthIn(max = 280.dp)
|
||||||
color = primaryColor,
|
if (isTv) {
|
||||||
fontWeight = FontWeight.Bold,
|
boxModifier = boxModifier.focusBorder(false).onKeyEvent {
|
||||||
textDecoration = TextDecoration.Underline
|
if (it.type == KeyEventType.KeyDown) {
|
||||||
),
|
when (it.key) {
|
||||||
focusedStyle = SpanStyle(
|
Key.Enter -> dialog = true
|
||||||
background = focusBackgroundColor,
|
else -> onKeyEvent(it)
|
||||||
color = primaryColor
|
|
||||||
)
|
|
||||||
),
|
|
||||||
linkInteractionListener = { onClickLink(token.text) }
|
|
||||||
)
|
|
||||||
withLink(linkAnnotation) { append(token.text) }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier.fillMaxWidth().focusable(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
contentAlignment = alignment
|
contentAlignment = if (isUser) Alignment.CenterEnd else Alignment.CenterStart
|
||||||
) {
|
) {
|
||||||
Card(
|
Card(
|
||||||
colors = CardDefaults.cardColors(
|
colors = CardDefaults.cardColors(
|
||||||
containerColor = containerColor,
|
containerColor = if (isUser) {
|
||||||
contentColor = contentColor
|
MaterialTheme.colorScheme.primaryContainer
|
||||||
|
} else {
|
||||||
|
MaterialTheme.colorScheme.surfaceVariant
|
||||||
|
},
|
||||||
|
contentColor = if (isUser) {
|
||||||
|
MaterialTheme.colorScheme.onPrimaryContainer
|
||||||
|
} else {
|
||||||
|
MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
}
|
||||||
),
|
),
|
||||||
modifier = Modifier.widthIn(max = 280.dp)
|
modifier = boxModifier
|
||||||
) {
|
) { ChatText(annotatedString) }
|
||||||
Column(modifier = Modifier.padding(12.dp)) {
|
}
|
||||||
SelectionContainer {
|
if (dialog && isTv) {
|
||||||
Text(
|
TextDialog(message.content, linkTokens, onClickLink) { dialog = false }
|
||||||
text = annotatedString,
|
}
|
||||||
style = MaterialTheme.typography.bodyLarge
|
}
|
||||||
)
|
|
||||||
|
@Composable
|
||||||
|
private fun ChatText(annotatedString: AnnotatedString) {
|
||||||
|
SelectionContainer {
|
||||||
|
Column(modifier = Modifier.padding(8.dp)) {
|
||||||
|
Text(
|
||||||
|
text = annotatedString,
|
||||||
|
style = MaterialTheme.typography.bodyLarge
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun TextDialog(
|
||||||
|
text: String,
|
||||||
|
links: List<TextToken.Link>,
|
||||||
|
onClickLink: (String) -> Unit,
|
||||||
|
dismiss: () -> Unit
|
||||||
|
) {
|
||||||
|
var showLinks by remember { mutableStateOf(false) }
|
||||||
|
AlertContent(
|
||||||
|
onDismissRequest = dismiss,
|
||||||
|
content = {
|
||||||
|
Column {
|
||||||
|
val focusRequester = remember { FocusRequester() }
|
||||||
|
if (!showLinks) {
|
||||||
|
ScrollableMessage(focusRequester, text) {
|
||||||
|
if (links.isNotEmpty()) {
|
||||||
|
showLinks = true
|
||||||
|
} else {
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LazyColumn {
|
||||||
|
itemsIndexed(links) { index, item ->
|
||||||
|
val mod = Modifier.focusBorder(false)
|
||||||
|
TextPreference(
|
||||||
|
text = item.text,
|
||||||
|
onClick = { onClickLink(item.text) },
|
||||||
|
modifier = if (index == 0) {
|
||||||
|
mod.focusRequester(focusRequester)
|
||||||
|
} else {
|
||||||
|
mod
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LaunchedEffect(showLinks) {
|
||||||
|
withFrameNanos {}
|
||||||
|
focusRequester.requestFocus()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ScrollableMessage(
|
||||||
|
focusRequester: FocusRequester,
|
||||||
|
text: String,
|
||||||
|
onEnter: () -> Unit
|
||||||
|
) {
|
||||||
|
val scrollStep = 250f
|
||||||
|
val scrollState = rememberScrollState()
|
||||||
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.focusRequester(focusRequester)
|
||||||
|
.focusable()
|
||||||
|
.onKeyEvent { keyEvent ->
|
||||||
|
if (keyEvent.type == KeyEventType.KeyDown) {
|
||||||
|
when (keyEvent.key) {
|
||||||
|
Key.DirectionDown -> {
|
||||||
|
coroutineScope.launch { scrollState.animateScrollBy(scrollStep) }
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
Key.DirectionUp -> {
|
||||||
|
coroutineScope.launch { scrollState.animateScrollBy(-scrollStep) }
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
Key.Enter -> {
|
||||||
|
onEnter()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = text,
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
.verticalScroll(scrollState)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Modifier.resetFocusOnUpEvent(focusRequester: FocusRequester): Modifier {
|
private fun buildText(
|
||||||
return onKeyEvent {
|
tokens: List<TextToken>,
|
||||||
if (it.type == KeyEventType.KeyUp) {
|
primaryColor: Color,
|
||||||
if (it.key == Key.DirectionUp) {
|
focusedColor: Color,
|
||||||
focusRequester.requestFocus()
|
onClickLink: (String) -> Unit
|
||||||
|
): AnnotatedString = buildAnnotatedString {
|
||||||
|
tokens.forEach { token ->
|
||||||
|
when (token) {
|
||||||
|
is TextToken.Plain -> append(token.text)
|
||||||
|
is TextToken.Link -> {
|
||||||
|
val linkAnnotation = LinkAnnotation.Clickable(
|
||||||
|
tag = "LINK",
|
||||||
|
styles = TextLinkStyles(
|
||||||
|
style = SpanStyle(
|
||||||
|
color = primaryColor,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
textDecoration = TextDecoration.Underline
|
||||||
|
),
|
||||||
|
focusedStyle = SpanStyle(
|
||||||
|
color = focusedColor,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
textDecoration = TextDecoration.Underline
|
||||||
|
)
|
||||||
|
),
|
||||||
|
linkInteractionListener = { onClickLink(token.text) }
|
||||||
|
)
|
||||||
|
withLink(linkAnnotation) { append(token.text) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
|||||||
import androidx.compose.foundation.layout.navigationBars
|
import androidx.compose.foundation.layout.navigationBars
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material3.ColorScheme
|
import androidx.compose.material3.ColorScheme
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||||
@@ -18,6 +19,7 @@ import androidx.compose.material3.Icon
|
|||||||
import androidx.compose.material3.MaterialExpressiveTheme
|
import androidx.compose.material3.MaterialExpressiveTheme
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.NavigationBar
|
import androidx.compose.material3.NavigationBar
|
||||||
|
import androidx.compose.material3.NavigationBarDefaults
|
||||||
import androidx.compose.material3.NavigationBarItem
|
import androidx.compose.material3.NavigationBarItem
|
||||||
import androidx.compose.material3.NavigationRail
|
import androidx.compose.material3.NavigationRail
|
||||||
import androidx.compose.material3.NavigationRailItem
|
import androidx.compose.material3.NavigationRailItem
|
||||||
@@ -30,6 +32,7 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.graphics.Brush
|
import androidx.compose.ui.graphics.Brush
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.painter.Painter
|
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.LayoutDirection
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import ru.shadowsparky.ui.nav.Route
|
import ru.shadowsparky.ui.nav.Route
|
||||||
import ru.shadowsparky.ui.theme.ComponentDefaults
|
|
||||||
import ru.shadowsparky.ui.theme.getColorScheme
|
import ru.shadowsparky.ui.theme.getColorScheme
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@@ -138,8 +140,8 @@ private fun <T : Route> BottomBar(
|
|||||||
.windowInsetsPadding(WindowInsets.navigationBars)
|
.windowInsetsPadding(WindowInsets.navigationBars)
|
||||||
) {
|
) {
|
||||||
NavigationBar(
|
NavigationBar(
|
||||||
containerColor = ComponentDefaults.navBarContentColor(),
|
containerColor = navBarContentColor(),
|
||||||
modifier = ComponentDefaults.roundedClip().blurEffect()
|
modifier = Modifier.clip(RoundedCornerShape(24.dp)).blurEffect()
|
||||||
) {
|
) {
|
||||||
entries.forEach { entry ->
|
entries.forEach { entry ->
|
||||||
NavigationBarItem(
|
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)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
|
|||||||
@@ -11,10 +11,11 @@ import androidx.compose.material3.SearchBarState
|
|||||||
import androidx.compose.material3.rememberSearchBarState
|
import androidx.compose.material3.rememberSearchBarState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.Shape
|
import androidx.compose.ui.graphics.Shape
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
import ru.shadowsparky.ui.blurEffect
|
import ru.shadowsparky.ui.blurEffect
|
||||||
import ru.shadowsparky.ui.theme.ComponentDefaults
|
import ru.shadowsparky.ui.isBlurEnabled
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
@@ -25,7 +26,7 @@ fun AppBarWithSearch(
|
|||||||
navigationIcon: @Composable (() -> Unit)? = null,
|
navigationIcon: @Composable (() -> Unit)? = null,
|
||||||
actions: @Composable (RowScope.() -> Unit)? = null,
|
actions: @Composable (RowScope.() -> Unit)? = null,
|
||||||
shape: Shape = SearchBarDefaults.inputFieldShape,
|
shape: Shape = SearchBarDefaults.inputFieldShape,
|
||||||
colors: AppBarWithSearchColors = ComponentDefaults.appBarWithSearchDefaults(),
|
colors: AppBarWithSearchColors = appBarWithSearchDefaults(),
|
||||||
tonalElevation: Dp = SearchBarDefaults.TonalElevation,
|
tonalElevation: Dp = SearchBarDefaults.TonalElevation,
|
||||||
shadowElevation: Dp = SearchBarDefaults.ShadowElevation,
|
shadowElevation: Dp = SearchBarDefaults.ShadowElevation,
|
||||||
contentPadding: PaddingValues = SearchBarDefaults.AppBarContentPadding,
|
contentPadding: PaddingValues = SearchBarDefaults.AppBarContentPadding,
|
||||||
@@ -47,3 +48,15 @@ fun AppBarWithSearch(
|
|||||||
scrollBehavior
|
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.material3.TopAppBarScrollBehavior
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
import ru.shadowsparky.ui.blurEffect
|
import ru.shadowsparky.ui.blurEffect
|
||||||
import ru.shadowsparky.ui.theme.ComponentDefaults
|
import ru.shadowsparky.ui.isBlurEnabled
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
@@ -23,7 +24,7 @@ fun TopAppBar(
|
|||||||
actions: @Composable RowScope.() -> Unit = {},
|
actions: @Composable RowScope.() -> Unit = {},
|
||||||
expandedHeight: Dp = TopAppBarDefaults.TopAppBarExpandedHeight,
|
expandedHeight: Dp = TopAppBarDefaults.TopAppBarExpandedHeight,
|
||||||
windowInsets: WindowInsets = TopAppBarDefaults.windowInsets,
|
windowInsets: WindowInsets = TopAppBarDefaults.windowInsets,
|
||||||
colors: TopAppBarColors = ComponentDefaults.topAppBarColors(),
|
colors: TopAppBarColors = topAppBarColors(),
|
||||||
scrollBehavior: TopAppBarScrollBehavior? = null,
|
scrollBehavior: TopAppBarScrollBehavior? = null,
|
||||||
contentPadding: PaddingValues = TopAppBarDefaults.ContentPadding
|
contentPadding: PaddingValues = TopAppBarDefaults.ContentPadding
|
||||||
) {
|
) {
|
||||||
@@ -39,3 +40,16 @@ fun TopAppBar(
|
|||||||
contentPadding = contentPadding
|
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
|
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.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material3.AppBarWithSearchColors
|
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
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.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.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.focus.onFocusChanged
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.graphicsLayer
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import ru.shadowsparky.ui.isBlurEnabled
|
|
||||||
|
|
||||||
object ComponentDefaults {
|
object ComponentDefaults {
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun topAppBarColors(): TopAppBarColors {
|
internal fun focusBorder(
|
||||||
return if (isBlurEnabled()) {
|
modifier: Modifier,
|
||||||
TopAppBarDefaults.topAppBarColors(
|
needScale: Boolean
|
||||||
Color.Transparent,
|
): Modifier {
|
||||||
Color.Transparent
|
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) {
|
fun onPageHideShow(hidden: Boolean) {
|
||||||
if (hidden) {
|
if (hidden) {
|
||||||
// уходим глубже: STARTED → CREATED
|
|
||||||
when (lifecycle.state) {
|
when (lifecycle.state) {
|
||||||
Lifecycle.State.RESUMED -> {
|
Lifecycle.State.RESUMED -> {
|
||||||
lifecycle.pause(); lifecycle.stop()
|
lifecycle.pause();
|
||||||
|
lifecycle.stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
Lifecycle.State.STARTED -> {
|
Lifecycle.State.STARTED -> {
|
||||||
@@ -51,14 +51,16 @@ fun hookPageVisibility(lifecycle: LifecycleRegistry) {
|
|||||||
else -> Unit
|
else -> Unit
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// pageshow: поднимемся обратно
|
|
||||||
when (lifecycle.state) {
|
when (lifecycle.state) {
|
||||||
Lifecycle.State.INITIALIZED -> {
|
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.State.CREATED -> {
|
||||||
lifecycle.start(); if (pageVisibilityState() == "visible") lifecycle.resume()
|
lifecycle.start()
|
||||||
|
if (pageVisibilityState() == "visible") lifecycle.resume()
|
||||||
}
|
}
|
||||||
|
|
||||||
Lifecycle.State.STARTED -> {
|
Lifecycle.State.STARTED -> {
|
||||||
|
|||||||
Reference in New Issue
Block a user