feat: 标记待办为已完成后的彩带庆祝

This commit is contained in:
Super12138 2025-01-13 18:02:45 +08:00
parent 70a1febce3
commit 0e54c94572
5 changed files with 124 additions and 4 deletions

View file

@ -6,8 +6,11 @@ import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface 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.navigation.ToDoNavigation
import cn.super12138.todo.ui.theme.ToDoTheme import cn.super12138.todo.ui.theme.ToDoTheme
import cn.super12138.todo.ui.viewmodels.MainViewModel
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@ -16,7 +19,10 @@ class MainActivity : ComponentActivity() {
setContent { setContent {
ToDoTheme { ToDoTheme {
Surface(color = MaterialTheme.colorScheme.background) { Surface(color = MaterialTheme.colorScheme.background) {
ToDoNavigation() val mainViewModel: MainViewModel = viewModel()
val showConfetti = mainViewModel.showConfetti
ToDoNavigation(viewModel = mainViewModel)
Konfetti(state = showConfetti)
} }
} }
} }

View file

@ -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<Boolean>,
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)

View file

@ -3,17 +3,18 @@ package cn.super12138.todo.ui.navigation
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import cn.super12138.todo.ui.pages.main.MainPage import cn.super12138.todo.ui.pages.main.MainPage
import cn.super12138.todo.ui.viewmodels.MainViewModel
@Composable @Composable
fun ToDoNavigation( fun ToDoNavigation(
navController: NavHostController = rememberNavController(), navController: NavHostController = rememberNavController(),
startDestination: String = ToDoScreen.Main.name, startDestination: String = ToDoScreen.Main.name,
viewModel: MainViewModel,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
NavHost( NavHost(
@ -23,7 +24,7 @@ fun ToDoNavigation(
) { ) {
composable(ToDoScreen.Main.name) { composable(ToDoScreen.Main.name) {
MainPage( MainPage(
viewModel = viewModel(), viewModel = viewModel,
modifier = Modifier.fillMaxSize() modifier = Modifier.fillMaxSize()
) )
} }

View file

@ -54,7 +54,6 @@ fun MainPage(viewModel: MainViewModel, modifier: Modifier = Modifier) {
val bottomSheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) val bottomSheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
// TODO: 寻找更好的适配方式 // TODO: 寻找更好的适配方式
val configuration = LocalConfiguration.current val configuration = LocalConfiguration.current
val orientation = when (configuration.orientation) { val orientation = when (configuration.orientation) {
@ -134,6 +133,7 @@ fun MainPage(viewModel: MainViewModel, modifier: Modifier = Modifier) {
id = id id = id
) )
) )
viewModel.playConfetti()
} }
}, },
modifier = Modifier modifier = Modifier
@ -170,6 +170,7 @@ fun MainPage(viewModel: MainViewModel, modifier: Modifier = Modifier) {
id = id id = id
) )
) )
viewModel.playConfetti()
} }
}, },
modifier = Modifier modifier = Modifier

View file

@ -1,5 +1,6 @@
package cn.super12138.todo.ui.viewmodels package cn.super12138.todo.ui.viewmodels
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import cn.super12138.todo.logic.Repository import cn.super12138.todo.logic.Repository
@ -9,6 +10,7 @@ import kotlinx.coroutines.launch
class MainViewModel : ViewModel() { class MainViewModel : ViewModel() {
val toDos: Flow<List<TodoEntity>> = Repository.getAllTodos() val toDos: Flow<List<TodoEntity>> = Repository.getAllTodos()
val showConfetti = mutableStateOf(false)
fun addTodo(toDo: TodoEntity) { fun addTodo(toDo: TodoEntity) {
viewModelScope.launch { viewModelScope.launch {
@ -21,4 +23,8 @@ class MainViewModel : ViewModel() {
Repository.updateTodo(toDo) Repository.updateTodo(toDo)
} }
} }
fun playConfetti() {
showConfetti.value = true
}
} }