diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/activities/MainActivity.kt b/app/src/main/kotlin/cn/super12138/todo/ui/activities/MainActivity.kt index 6910948..ea60c08 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/activities/MainActivity.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/activities/MainActivity.kt @@ -6,8 +6,11 @@ import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface +import androidx.lifecycle.viewmodel.compose.viewModel +import cn.super12138.todo.ui.components.Konfetti import cn.super12138.todo.ui.navigation.ToDoNavigation import cn.super12138.todo.ui.theme.ToDoTheme +import cn.super12138.todo.ui.viewmodels.MainViewModel class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { @@ -16,7 +19,10 @@ class MainActivity : ComponentActivity() { setContent { ToDoTheme { Surface(color = MaterialTheme.colorScheme.background) { - ToDoNavigation() + val mainViewModel: MainViewModel = viewModel() + val showConfetti = mainViewModel.showConfetti + ToDoNavigation(viewModel = mainViewModel) + Konfetti(state = showConfetti) } } } diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/components/Konfetti.kt b/app/src/main/kotlin/cn/super12138/todo/ui/components/Konfetti.kt new file mode 100644 index 0000000..107a518 --- /dev/null +++ b/app/src/main/kotlin/cn/super12138/todo/ui/components/Konfetti.kt @@ -0,0 +1,106 @@ +package cn.super12138.todo.ui.components + +import androidx.annotation.FloatRange +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.toArgb +import androidx.core.graphics.ColorUtils +import nl.dionsegijn.konfetti.compose.KonfettiView +import nl.dionsegijn.konfetti.compose.OnParticleSystemUpdateListener +import nl.dionsegijn.konfetti.core.Angle +import nl.dionsegijn.konfetti.core.Party +import nl.dionsegijn.konfetti.core.PartySystem +import nl.dionsegijn.konfetti.core.Position +import nl.dionsegijn.konfetti.core.Spread +import nl.dionsegijn.konfetti.core.emitter.Emitter +import java.util.concurrent.TimeUnit + +/** + * 参照于:https://github.com/hushenghao/AndroidEasterEggs/blob/main/app/src/main/java/com/dede/android_eggs/views/main/compose/Konfetti.kt + */ + +@Composable +fun Konfetti( + state: MutableState, + primary: Color = MaterialTheme.colorScheme.primary, + modifier: Modifier = Modifier +) { + var visible by state + if (!visible) { + return + } + val listener = remember(state) { + object : OnParticleSystemUpdateListener { + override fun onParticleSystemEnded(system: PartySystem, activeSystems: Int) { + if (activeSystems == 0) + visible = false + } + } + } + KonfettiView( + modifier = Modifier + .fillMaxSize() + .then(modifier), + parties = remember { particles(primary.toArgb()) }, + updateListener = listener + ) +} + +private val defaultColors = listOf( + 0xFCE18A, + 0x009688, + 0xFF726D, + 0xF4306D, + 0xB48DEF, + 0x95FF82, + 0x82ECFF, + 0xFF9800, + 0x0E008A, +) + +private const val colorBlendFraction = 0.3f + +private fun particles(primary: Int) = listOf( + Party( + speed = 0f, + maxSpeed = 12f, + damping = 0.9f, + angle = Angle.BOTTOM, + spread = Spread.ROUND, + colors = defaultColors.map { it.blend(primary, colorBlendFraction) }, + emitter = Emitter(duration = 2, TimeUnit.SECONDS).perSecond(100), + position = Position.Relative(0.0, 0.0).between(Position.Relative(1.0, 0.0)), + ), + Party( + speed = 10f, + maxSpeed = 30f, + damping = 0.9f, + angle = Angle.RIGHT - 55, + spread = 60, + colors = defaultColors.map { it.blend(primary, colorBlendFraction) }, + emitter = Emitter(duration = 2, TimeUnit.SECONDS).perSecond(100), + position = Position.Relative(0.0, 1.0) + ), + Party( + speed = 10f, + maxSpeed = 30f, + damping = 0.9f, + angle = Angle.RIGHT - 125, + spread = 60, + colors = defaultColors.map { it.blend(primary, colorBlendFraction) }, + emitter = Emitter(duration = 2, TimeUnit.SECONDS).perSecond(100), + position = Position.Relative(1.0, 1.0) + ) +) + +fun Int.blend( + color: Int, + @FloatRange(from = 0.0, to = 1.0) fraction: Float = 0.5f, +): Int = ColorUtils.blendARGB(this, color, fraction) 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 2c3ad76..154a023 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 @@ -3,17 +3,18 @@ package cn.super12138.todo.ui.navigation import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController import cn.super12138.todo.ui.pages.main.MainPage +import cn.super12138.todo.ui.viewmodels.MainViewModel @Composable fun ToDoNavigation( navController: NavHostController = rememberNavController(), startDestination: String = ToDoScreen.Main.name, + viewModel: MainViewModel, modifier: Modifier = Modifier ) { NavHost( @@ -23,7 +24,7 @@ fun ToDoNavigation( ) { composable(ToDoScreen.Main.name) { MainPage( - viewModel = viewModel(), + viewModel = viewModel, modifier = Modifier.fillMaxSize() ) } diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/MainPage.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/MainPage.kt index 2462bf6..76507c8 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/MainPage.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/MainPage.kt @@ -54,7 +54,6 @@ fun MainPage(viewModel: MainViewModel, modifier: Modifier = Modifier) { val bottomSheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) val scope = rememberCoroutineScope() - // TODO: 寻找更好的适配方式 val configuration = LocalConfiguration.current val orientation = when (configuration.orientation) { @@ -134,6 +133,7 @@ fun MainPage(viewModel: MainViewModel, modifier: Modifier = Modifier) { id = id ) ) + viewModel.playConfetti() } }, modifier = Modifier @@ -170,6 +170,7 @@ fun MainPage(viewModel: MainViewModel, modifier: Modifier = Modifier) { id = id ) ) + viewModel.playConfetti() } }, modifier = Modifier diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/viewmodels/MainViewModel.kt b/app/src/main/kotlin/cn/super12138/todo/ui/viewmodels/MainViewModel.kt index efd65d1..5bdd54e 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/viewmodels/MainViewModel.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/viewmodels/MainViewModel.kt @@ -1,5 +1,6 @@ package cn.super12138.todo.ui.viewmodels +import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import cn.super12138.todo.logic.Repository @@ -9,6 +10,7 @@ import kotlinx.coroutines.launch class MainViewModel : ViewModel() { val toDos: Flow> = Repository.getAllTodos() + val showConfetti = mutableStateOf(false) fun addTodo(toDo: TodoEntity) { viewModelScope.launch { @@ -21,4 +23,8 @@ class MainViewModel : ViewModel() { Repository.updateTodo(toDo) } } + + fun playConfetti() { + showConfetti.value = true + } } \ No newline at end of file