feat: 标记待办为已完成后的彩带庆祝
This commit is contained in:
parent
70a1febce3
commit
0e54c94572
5 changed files with 124 additions and 4 deletions
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
106
app/src/main/kotlin/cn/super12138/todo/ui/components/Konfetti.kt
Normal file
106
app/src/main/kotlin/cn/super12138/todo/ui/components/Konfetti.kt
Normal 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)
|
||||||
|
|
@ -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()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in a new issue