diff --git a/app/src/main/java/top/yogiczy/mytv/activities/LeanbackActivity.kt b/app/src/main/java/top/yogiczy/mytv/activities/LeanbackActivity.kt index d1e3f330..4d213bd0 100644 --- a/app/src/main/java/top/yogiczy/mytv/activities/LeanbackActivity.kt +++ b/app/src/main/java/top/yogiczy/mytv/activities/LeanbackActivity.kt @@ -66,6 +66,8 @@ class LeanbackActivity : ComponentActivity() { } } - HttpServer.start(applicationContext, showToast = { LeanbackToastState.I.showToast(it) }) + HttpServer.start(applicationContext, showToast = { + LeanbackToastState.I.showToast(it, id = "httpServer") + }) } } diff --git a/app/src/main/java/top/yogiczy/mytv/ui/LeanbackApp.kt b/app/src/main/java/top/yogiczy/mytv/ui/LeanbackApp.kt index 2aab53d8..542b62d5 100644 --- a/app/src/main/java/top/yogiczy/mytv/ui/LeanbackApp.kt +++ b/app/src/main/java/top/yogiczy/mytv/ui/LeanbackApp.kt @@ -1,6 +1,5 @@ package top.yogiczy.mytv.ui -import android.widget.Toast import androidx.annotation.IntRange import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.calculateEndPadding @@ -23,6 +22,7 @@ import kotlinx.coroutines.flow.debounce import top.yogiczy.mytv.ui.screens.leanback.components.LeanbackPadding import top.yogiczy.mytv.ui.screens.leanback.main.LeanbackMainScreen import top.yogiczy.mytv.ui.screens.leanback.toast.LeanbackToastScreen +import top.yogiczy.mytv.ui.screens.leanback.toast.LeanbackToastState @Composable fun LeanbackApp( @@ -40,7 +40,7 @@ fun LeanbackApp( onBackPressed() } else { doubleBackPressedExitState.backPress() - Toast.makeText(context, "再按一次退出", Toast.LENGTH_SHORT).show() + LeanbackToastState.I.showToast("再按一次退出") } }, ) diff --git a/app/src/main/java/top/yogiczy/mytv/ui/screens/leanback/toast/ToastScreen.kt b/app/src/main/java/top/yogiczy/mytv/ui/screens/leanback/toast/ToastScreen.kt index 231fa0e1..3879a3ae 100644 --- a/app/src/main/java/top/yogiczy/mytv/ui/screens/leanback/toast/ToastScreen.kt +++ b/app/src/main/java/top/yogiczy/mytv/ui/screens/leanback/toast/ToastScreen.kt @@ -1,22 +1,33 @@ package top.yogiczy.mytv.ui.screens.leanback.toast import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.scaleIn +import androidx.compose.animation.scaleOut import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.sizeIn +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Info import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Popup +import androidx.tv.material3.Icon +import androidx.tv.material3.MaterialTheme import kotlinx.coroutines.delay -import top.yogiczy.mytv.ui.rememberLeanbackChildPadding import top.yogiczy.mytv.ui.theme.LeanbackTheme @Composable @@ -24,17 +35,19 @@ fun LeanbackToastScreen( modifier: Modifier = Modifier, state: LeanbackToastState = rememberLeanbackToastState(), ) { - val childPadding = rememberLeanbackChildPadding() - Box(modifier = modifier.fillMaxSize()) { - Popup( - offset = IntOffset( - x = with(LocalDensity.current) { childPadding.start.toPx().toInt() }, - y = with(LocalDensity.current) { childPadding.top.toPx().toInt() }, - ), - ) { - AnimatedVisibility(visible = state.visible) { - LeanbackToastItem(property = state.current) + Popup { + Box(modifier = Modifier.fillMaxSize()) { + AnimatedVisibility( + visible = state.visible, + enter = fadeIn() + scaleIn(), + exit = fadeOut() + scaleOut(), + modifier = Modifier + .align(Alignment.BottomCenter) + .padding(bottom = 28.dp), + ) { + LeanbackToastItem(property = state.current) + } } } } @@ -47,13 +60,50 @@ fun LeanbackToastItem( ) { Box( modifier = modifier - .background( - color = MaterialTheme.colorScheme.background.copy(alpha = 0.9f), - shape = MaterialTheme.shapes.small, - ) - .padding(horizontal = 20.dp, vertical = 10.dp) + .sizeIn(maxWidth = 556.dp) + .background(MaterialTheme.colorScheme.inverseSurface, MaterialTheme.shapes.medium) + .padding(horizontal = 16.dp, vertical = 12.dp), ) { - Text(text = property.message) + Row( + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + LeanbackToastContentIcon( + showIcon = true, + icon = Icons.Outlined.Info, + iconColor = MaterialTheme.colorScheme.inverseOnSurface, + iconContainerColors = MaterialTheme.colorScheme.onSurfaceVariant, + ) + + androidx.tv.material3.Text( + property.message, + color = MaterialTheme.colorScheme.inverseOnSurface + ) + } + } +} + +@Composable +private fun LeanbackToastContentIcon( + modifier: Modifier = Modifier, + showIcon: Boolean, + icon: ImageVector, + iconColor: Color, + iconContainerColors: Color, +) { + if (showIcon) { + Box( + modifier = modifier + .background(iconContainerColors, CircleShape) + .padding(8.dp), + ) { + Icon( + imageVector = icon, + contentDescription = null, + modifier = Modifier.size(16.dp), + tint = iconColor, + ) + } } } diff --git a/app/src/main/java/top/yogiczy/mytv/ui/screens/leanback/toast/ToastState.kt b/app/src/main/java/top/yogiczy/mytv/ui/screens/leanback/toast/ToastState.kt index a4dbe2b6..cd73efb8 100644 --- a/app/src/main/java/top/yogiczy/mytv/ui/screens/leanback/toast/ToastState.kt +++ b/app/src/main/java/top/yogiczy/mytv/ui/screens/leanback/toast/ToastState.kt @@ -6,15 +6,20 @@ import androidx.compose.runtime.Stable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.consumeAsFlow import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.launch import top.yogiczy.mytv.ui.screens.leanback.toast.LeanbackToastProperty.Companion.toMs +import java.util.UUID @Stable -class LeanbackToastState { +class LeanbackToastState(private val coroutineScope: CoroutineScope) { private var _visible by mutableStateOf(false) val visible get() = _visible @@ -22,17 +27,24 @@ class LeanbackToastState { val current get() = _current private fun showToast(toast: LeanbackToastProperty) { - // TODO 消息变化较生硬 - _current = toast - _visible = true - channel.trySend(toast.duration.toMs()) + coroutineScope.launch { + if (_current.id != toast.id) { + _visible = false + delay(300) + } + + _current = toast + _visible = true + channel.trySend(toast.duration.toMs()) + } } fun showToast( message: String, duration: LeanbackToastProperty.Duration = LeanbackToastProperty.Duration.Default, + id: String = UUID.randomUUID().toString(), ) { - showToast(LeanbackToastProperty(message = message, duration = duration)) + showToast(LeanbackToastProperty(message = message, duration = duration, id = id)) } private val channel = Channel(Channel.CONFLATED) @@ -49,14 +61,21 @@ class LeanbackToastState { } @Composable -fun rememberLeanbackToastState() = remember { LeanbackToastState() }.also { - LeanbackToastState.I = it - LaunchedEffect(it) { it.observe() } +fun rememberLeanbackToastState(): LeanbackToastState { + val coroutineScope = rememberCoroutineScope() + + return remember { + LeanbackToastState(coroutineScope) + }.also { + LeanbackToastState.I = it + LaunchedEffect(it) { it.observe() } + } } data class LeanbackToastProperty( val message: String = "", val duration: Duration = Duration.Default, + val id: String = UUID.randomUUID().toString(), ) { sealed interface Duration { data object Default : Duration diff --git a/app/src/main/java/top/yogiczy/mytv/ui/screens/leanback/update/UpdateViewModel.kt b/app/src/main/java/top/yogiczy/mytv/ui/screens/leanback/update/UpdateViewModel.kt index 40d69994..daf2dd38 100644 --- a/app/src/main/java/top/yogiczy/mytv/ui/screens/leanback/update/UpdateViewModel.kt +++ b/app/src/main/java/top/yogiczy/mytv/ui/screens/leanback/update/UpdateViewModel.kt @@ -59,6 +59,7 @@ class LeanBackUpdateViewModel : ViewModel() { LeanbackToastState.I.showToast( "正在下载更新: $it%", LeanbackToastProperty.Duration.Custom(10_000), + "downloadProcess" ) }