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