diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/navigation/TodoNavigation.kt b/app/src/main/kotlin/cn/super12138/todo/ui/navigation/TodoNavigation.kt index b388107..aa2bbe0 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/navigation/TodoNavigation.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/navigation/TodoNavigation.kt @@ -12,6 +12,7 @@ import cn.super12138.todo.ui.pages.editor.TodoEditorPage import cn.super12138.todo.ui.pages.main.MainPage import cn.super12138.todo.ui.pages.settings.SettingsAbout import cn.super12138.todo.ui.pages.settings.SettingsAboutLicence +import cn.super12138.todo.ui.pages.settings.SettingsAboutSpecial import cn.super12138.todo.ui.pages.settings.SettingsAppearance import cn.super12138.todo.ui.pages.settings.SettingsData import cn.super12138.todo.ui.pages.settings.SettingsInterface @@ -123,8 +124,16 @@ fun TodoNavigation( composable(TodoScreen.SettingsAbout.name) { SettingsAbout( + toSpecialPage = { navController.navigate(TodoScreen.SettingsAboutSpecial.name) }, + toLicencePage = { navController.navigate(TodoScreen.SettingsAboutLicence.name) }, onNavigateUp = { navController.navigateUp() }, - toLicencePage = { navController.navigate(TodoScreen.SettingsAboutLicence.name) } + ) + } + + composable(TodoScreen.SettingsAboutSpecial.name) { + SettingsAboutSpecial( + viewModel = viewModel, + onNavigateUp = { navController.navigateUp() } ) } diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/navigation/TodoScreen.kt b/app/src/main/kotlin/cn/super12138/todo/ui/navigation/TodoScreen.kt index fe5b0ee..a1e365a 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/navigation/TodoScreen.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/navigation/TodoScreen.kt @@ -8,5 +8,6 @@ enum class TodoScreen { SettingsInterface, SettingsData, SettingsAbout, + SettingsAboutSpecial, SettingsAboutLicence } \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAbout.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAbout.kt index 7cc6ed9..f68d510 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAbout.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAbout.kt @@ -2,6 +2,7 @@ package cn.super12138.todo.ui.pages.settings import android.content.Intent import android.net.Uri +import android.widget.Toast import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding @@ -14,6 +15,10 @@ import androidx.compose.material.icons.outlined.Person4 import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalContext @@ -28,8 +33,9 @@ import cn.super12138.todo.utils.SystemUtils @OptIn(ExperimentalMaterial3Api::class) @Composable fun SettingsAbout( - onNavigateUp: () -> Unit, + toSpecialPage: () -> Unit, toLicencePage: () -> Unit, + onNavigateUp: () -> Unit, modifier: Modifier = Modifier ) { val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() @@ -46,10 +52,22 @@ fun SettingsAbout( .padding(innerPadding) .verticalScroll(rememberScrollState()) ) { + var clickCount by remember { mutableIntStateOf(0) } SettingsItem( leadingIcon = Icons.Outlined.Numbers, title = stringResource(R.string.pref_app_version), - description = SystemUtils.getAppVersion(context) + description = SystemUtils.getAppVersion(context), + onClick = { + clickCount++ + if (clickCount == 5) { + if ((System.currentTimeMillis() % 2) == 0.toLong()) { + toSpecialPage() + } else { + Toast.makeText(context, "✨", Toast.LENGTH_SHORT).show() + } + clickCount = 0 + } + } ) SettingsItem( leadingIcon = Icons.Outlined.Person4, diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAboutSpecial.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAboutSpecial.kt new file mode 100644 index 0000000..99ed198 --- /dev/null +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAboutSpecial.kt @@ -0,0 +1,234 @@ +package cn.super12138.todo.ui.pages.settings + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.expandVertically +import androidx.compose.animation.scaleIn +import androidx.compose.animation.scaleOut +import androidx.compose.animation.shrinkVertically +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.asPaddingValues +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.statusBars +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.CornerSize +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +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.platform.LocalConfiguration +import androidx.compose.ui.platform.LocalView +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import cn.super12138.todo.R +import cn.super12138.todo.ui.viewmodels.MainViewModel +import cn.super12138.todo.utils.VibrationUtils + +@Composable +fun SettingsAboutSpecial( + viewModel: MainViewModel, + onNavigateUp: () -> Unit, + modifier: Modifier = Modifier +) { + val configuration = LocalConfiguration.current + val screenHeight = configuration.screenHeightDp.dp + var showBanner by rememberSaveable { mutableStateOf(false) } + Surface(color = SpecialDefaults.WallColor) { + Box(Modifier.fillMaxSize()) { + AnimatedVisibility( + visible = showBanner, + enter = expandVertically(), + exit = shrinkVertically() + ) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, + modifier = Modifier + .fillMaxWidth() + .height(screenHeight / 4) + .clip(MaterialTheme.shapes.large) + .background(SpecialDefaults.BannerColor) + .padding(WindowInsets.statusBars.asPaddingValues()) + + ) { + Text( + text = stringResource(R.string.happy_birthday), + style = MaterialTheme.typography.headlineMedium + ) + } + } + // 蛋糕 + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier + .align(Alignment.BottomCenter) + ) { + Cake( + onCandleClick = { + showBanner = it + if (it) viewModel.playConfetti() + } + ) + // 桌子 + Box( + Modifier + .fillMaxWidth() + .height(SpecialDefaults.TableHeight) + .background(SpecialDefaults.TableColor) + ) + } + } + } +} + + +@Composable +private fun Cake( + onCandleClick: (Boolean) -> Unit, + modifier: Modifier = Modifier +) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = modifier + ) { + val configuration = LocalConfiguration.current + val screenWidth = configuration.screenWidthDp.dp + Row { + Candle(onClick = onCandleClick) + } + CakePiece( + width = screenWidth / 4, + height = SpecialDefaults.Cake2Height, + color = SpecialDefaults.Cake2Color + ) + CakePiece( + width = screenWidth / 3, + height = SpecialDefaults.Cake1Height, + color = SpecialDefaults.Cake1Color + ) + } +} + +@Composable +private fun CakePiece( + width: Dp, + height: Dp, + color: Color, + modifier: Modifier = Modifier +) { + Box( + modifier + .width(width) + .height(height) + .clip( + MaterialTheme.shapes.small.copy( + bottomStart = CornerSize(0.dp), + bottomEnd = CornerSize(0.dp) + ) + ) + .background(color) + ) +} + +@Composable +fun Candle( + onClick: (Boolean) -> Unit = {}, + modifier: Modifier = Modifier +) { + var showFlame by rememberSaveable { mutableStateOf(false) } + val interactionSource = remember { MutableInteractionSource() } + val view = LocalView.current + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = modifier.clickable( + interactionSource = interactionSource, + indication = null, + onClick = { + showFlame = !showFlame + VibrationUtils.performHapticFeedback(view) + onClick(showFlame) + } + ) + ) { + // 火苗 + Box { + androidx.compose.animation.AnimatedVisibility( + visible = showFlame, + enter = scaleIn(), + exit = scaleOut(), + modifier = Modifier + .align(Alignment.Center) + .padding(bottom = 2.5.dp) + ) { + Box( + Modifier + .size(10.dp) + .background( + brush = Brush.radialGradient( + listOf( + SpecialDefaults.FlameCenterColor, + SpecialDefaults.FlameAroundColor, + Color.Transparent + ) + ) + ) + ) + } + Box( + Modifier + .width(SpecialDefaults.WickWidth) + .height(SpecialDefaults.WickHeight) + .background(SpecialDefaults.WickColor) + .align(Alignment.BottomCenter) + ) + } + Box( + Modifier + .width(SpecialDefaults.CandleBodyWidth) + .height(SpecialDefaults.CandleBodyHeight) + .background(Color(0xFFECCE48)) + ) + } +} + +private object SpecialDefaults { + // Color + val WallColor = Color(0xFFFFFBEA) + val TableColor = Color(0xFF412200) + val Cake1Color = Color(0xFFFFE9A0) + val Cake2Color = Color(0xFFFFECBF) + val FlameCenterColor = Color(0xFFFF5100) + val FlameAroundColor = Color(0xFFF82323) + val WickColor = Color(0xFF1E1E1E) + val BannerColor = Color(0xFFFFF1BC) + + // Size + val TableHeight = 30.dp + val Cake1Height = 60.dp + val Cake2Height = 45.dp + val CandleBodyWidth = 10.dp + val CandleBodyHeight = 20.dp + val WickWidth = 1.5.dp + val WickHeight = 8.dp +} \ No newline at end of file diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index cfcaf6d..38d99a4 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -107,4 +107,5 @@ 安全模式 阻止截屏并保护后台预览图 更多 + 待办1岁生日快乐 \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 038d86d..967c6c6 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -108,4 +108,5 @@ Secure Mode Prevent screenshots and protect the background preview image More + Happy 1st birthday to ToDo \ No newline at end of file