diff --git a/android/app/src/main/java/com/fabledsword/minstrel/shared/widgets/LikeButton.kt b/android/app/src/main/java/com/fabledsword/minstrel/shared/widgets/LikeButton.kt index 3ec7722f..67067ae4 100644 --- a/android/app/src/main/java/com/fabledsword/minstrel/shared/widgets/LikeButton.kt +++ b/android/app/src/main/java/com/fabledsword/minstrel/shared/widgets/LikeButton.kt @@ -5,6 +5,11 @@ import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.path +import androidx.compose.ui.unit.dp import com.composables.icons.lucide.Heart import com.composables.icons.lucide.Lucide @@ -14,9 +19,13 @@ import com.composables.icons.lucide.Lucide * caller's ViewModel (typically observeIsLiked + toggleLike from * LikesRepository), not here. * - * Flutter equivalent is `flutter_client/lib/likes/like_button.dart`. - * That one tints the heart with the accent color; we do the same via - * the M3 primary slot so light/dark mode tracks the theme. + * Liked state renders a **filled** heart (HeartFilled, defined below); + * unliked state renders Lucide's outlined Heart. Both share the same + * geometric path so the toggle is a fill swap, not an icon swap — + * matches the cross-app pattern (Spotify, Apple Music, etc.). Lucide + * ships outlined-only by design; the filled variant is built locally + * from the same path data so we keep the project's icon system + * consistent (operator 2026-06-01). */ @Composable fun LikeButton( @@ -26,7 +35,7 @@ fun LikeButton( ) { IconButton(onClick = onToggle, modifier = modifier) { Icon( - imageVector = Lucide.Heart, + imageVector = if (liked) HeartFilled else Lucide.Heart, contentDescription = if (liked) "Unlike" else "Like", tint = if (liked) { MaterialTheme.colorScheme.primary @@ -36,3 +45,35 @@ fun LikeButton( ) } } + +/** + * Filled-heart counterpart to [Lucide.Heart], built from the same path + * data so the toggle reads as a fill change rather than an icon swap. + * Cached as a private lateinit-style backing prop following Lucide's + * own pattern (heart.kt in the icons-lucide-cmp jar). + */ +private val HeartFilled: ImageVector by lazy { + ImageVector.Builder( + name = "heart_filled", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 24f, + viewportHeight = 24f, + ).apply { + path( + fill = SolidColor(Color.Black), + stroke = null, + ) { + moveTo(2f, 9.5f) + arcToRelative(5.5f, 5.5f, 0f, false, true, 9.591f, -3.676f) + arcToRelative(0.56f, 0.56f, 0f, false, false, 0.818f, 0f) + arcTo(5.49f, 5.49f, 0f, false, true, 22f, 9.5f) + curveToRelative(0f, 2.29f, -1.5f, 4f, -3f, 5.5f) + lineToRelative(-5.492f, 5.313f) + arcToRelative(2f, 2f, 0f, false, true, -3f, 0.019f) + lineTo(5f, 15f) + curveToRelative(-1.5f, -1.5f, -3f, -3.2f, -3f, -5.5f) + close() + } + }.build() +}