feat: show tilted birthday crown with click bounce animation

This commit is contained in:
xfy 2026-06-15 16:28:37 +08:00
parent f33e68d1db
commit de47509a32

View File

@ -1,9 +1,12 @@
package plus.rua.project.ui
import androidx.compose.animation.animateColor
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.animateFloat
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween
import androidx.compose.animation.core.updateTransition
import androidx.compose.ui.graphics.TransformOrigin
@ -16,14 +19,19 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
@ -31,6 +39,7 @@ import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.font.FontWeight
@ -44,6 +53,7 @@ import kotlinx.datetime.number
import plus.rua.project.DayCellInfo
import plus.rua.project.LunarCache
import plus.rua.project.ShiftKind
import plus.rua.project.shared.R
enum class DayCellState {
NORMAL, OTHER_MONTH, TODAY, SELECTED, SELECTED_TODAY
@ -63,6 +73,9 @@ enum class DayCellState {
* @param holidayEdgeInfo 假日在连续序列中的边缘状态,决定背景圆角null 表示无假日
* @param cellIndex 单元格在月网格中的线性索引(weekIndex*7+dayIndex),用于法定假日波浪动画延迟
* @param onClick 点击回调
* @param interactionSource 点击交互源
* @param lunarCache 农历缓存实例
* @param lunarData 预计算的日期信息null 时内部自动获取
* @param modifier 外部布局修饰符
*/
@Composable
@ -76,10 +89,10 @@ fun DayCell(
holidayEdgeInfo: HolidayEdgeInfo? = null,
cellIndex: Int = 0,
onClick: () -> Unit,
modifier: Modifier = Modifier,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
lunarCache: LunarCache = LunarCache.default,
lunarData: DayCellInfo? = null,
modifier: Modifier = Modifier,
) {
if (lunarData != null) {
DayCellImpl(
@ -136,6 +149,16 @@ private fun DayCellImpl(
interactionSource: MutableInteractionSource,
lunarData: DayCellInfo,
) {
val isBirthday = lunarData.isBirthday
var birthdayClickTick by remember(date) { mutableIntStateOf(0) }
val crownScale = remember(date) { Animatable(1f) }
LaunchedEffect(birthdayClickTick) {
if (birthdayClickTick > 0) {
crownScale.animateTo(1.4f, spring(dampingRatio = Spring.DampingRatioMediumBouncy))
crownScale.animateTo(1f, spring(dampingRatio = Spring.DampingRatioMediumBouncy))
}
}
val annotationText = lunarData.annotationText
val isAnnotationHighlight = lunarData.isAnnotationHighlight
val holidayBadge = lunarData.holidayBadge
@ -296,7 +319,10 @@ private fun DayCellImpl(
.clickable(
interactionSource = interactionSource,
indication = null,
onClick = onClick
onClick = {
if (isBirthday) birthdayClickTick += 1
onClick()
}
),
contentAlignment = Alignment.Center
) {
@ -346,5 +372,23 @@ private fun DayCellImpl(
)
}
}
if (isBirthday) {
Icon(
painter = painterResource(R.drawable.ic_birthday_crown),
contentDescription = "生日",
tint = Color.Unspecified,
modifier = Modifier
.align(Alignment.TopStart)
.zIndex(1f)
.padding(start = 2.dp, top = 2.dp)
.size(14.dp)
.graphicsLayer {
rotationZ = -45f
transformOrigin = TransformOrigin.Center
scaleX = crownScale.value
scaleY = crownScale.value
}
)
}
}
}