feat(ui): Lucide migration unit 1 — dependency + heart (#60)

Design system mandates Lucide, not Material. Foundation before the
mechanical Icons.* sweep:

- pubspec: add flutter_lucide ^1.11.0
- shared/widgets/lucide_heart.dart: LucideHeart renders the verified
  lucide-icons/lucide heart path as outline (stroke) or filled, via
  flutter_svg, tinted by color — Lucide ships no filled heart, so the
  liked state fills the same Lucide silhouette (user-chosen approach)
- like_button: use LucideHeart instead of Icons.favorite/_border
- notification drawables re-derived from the verbatim Lucide heart
  path (border = stroke, filled = fill); separators spaced for
  Android pathData

Unit 2 (mechanical Icons.* -> LucideIcons.* sweep) follows once this
is CI-green. pubspec.lock regenerated by CI.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-18 20:18:22 -04:00
parent 25ee54fca0
commit 84f16c25f6
5 changed files with 58 additions and 5 deletions
@@ -4,7 +4,8 @@
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="#FFFFFFFF">
<!-- Lucide "heart" (filled) — same path verbatim from lucide-icons/lucide -->
<path
android:fillColor="#FFFFFFFF"
android:pathData="M12,21.35l-1.45,-1.32C5.4,15.36 2,12.28 2,8.5 2,5.42 4.42,3 7.5,3c1.74,0 3.41,0.81 4.5,2.09C13.09,3.81 14.76,3 16.5,3 19.58,3 22,5.42 22,8.5c0,3.78 -3.4,6.86 -8.55,11.54L12,21.35z"/>
android:pathData="M2 9.5a5.5 5.5 0 0 1 9.591 -3.676 .56 .56 0 0 0 .818 0A5.49 5.49 0 0 1 22 9.5c0 2.29 -1.5 4 -3 5.5l-5.492 5.313a2 2 0 0 1 -3 .019L5 15c-1.5 -1.5 -3 -3.2 -3 -5.5"/>
</vector>
@@ -4,7 +4,12 @@
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="#FFFFFFFF">
<!-- Lucide "heart" (outline) — path verbatim from lucide-icons/lucide -->
<path
android:fillColor="#FFFFFFFF"
android:pathData="M16.5,3c-1.74,0 -3.41,0.81 -4.5,2.09C10.91,3.81 9.24,3 7.5,3 4.42,3 2,5.42 2,8.5c0,3.78 3.4,6.86 8.55,11.54L12,21.35l1.45,-1.32C18.6,15.36 22,12.28 22,8.5 22,5.42 19.58,3 16.5,3zM12.1,18.55l-0.1,0.1 -0.1,-0.1C7.14,14.24 4,11.39 4,8.5 4,6.5 5.5,5 7.5,5c1.54,0 3.04,0.99 3.57,2.36h1.87C13.46,5.99 14.96,5 16.5,5c2,0 3.5,1.5 3.5,3.5 0,2.89 -3.14,5.74 -7.9,10.05z"/>
android:strokeColor="#FFFFFFFF"
android:strokeWidth="2"
android:strokeLineCap="round"
android:strokeLineJoin="round"
android:fillColor="#00000000"
android:pathData="M2 9.5a5.5 5.5 0 0 1 9.591 -3.676 .56 .56 0 0 0 .818 0A5.49 5.49 0 0 1 22 9.5c0 2.29 -1.5 4 -3 5.5l-5.492 5.313a2 2 0 0 1 -3 .019L5 15c-1.5 -1.5 -3 -3.2 -3 -5.5"/>
</vector>
+3 -2
View File
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../api/endpoints/likes.dart';
import '../shared/widgets/lucide_heart.dart';
import '../theme/theme_extension.dart';
import 'likes_provider.dart';
@@ -25,8 +26,8 @@ class LikeButton extends ConsumerWidget {
orElse: () => false,
);
return IconButton(
icon: Icon(
liked ? Icons.favorite : Icons.favorite_border,
icon: LucideHeart(
filled: liked,
color: liked ? fs.accent : fs.ash,
size: size,
),
@@ -0,0 +1,43 @@
import 'package:flutter/widgets.dart';
import 'package:flutter_svg/flutter_svg.dart';
/// The Lucide "heart" silhouette rendered as either an outline (stroke)
/// or a solid fill. Lucide ships only an outline heart, so the liked
/// state fills the same authoritative Lucide path (verified verbatim
/// from lucide-icons/lucide) — keeping both states visually Lucide
/// rather than introducing a Material filled heart. Used by LikeButton
/// (and, re-derived to a VectorDrawable, by the media notification).
class LucideHeart extends StatelessWidget {
const LucideHeart({
required this.filled,
required this.color,
this.size = 22,
super.key,
});
final bool filled;
final Color color;
final double size;
static const _path =
'M2 9.5a5.5 5.5 0 0 1 9.591-3.676.56.56 0 0 0 .818 0A5.49 5.49 0 0 1 '
'22 9.5c0 2.29-1.5 4-3 5.5l-5.492 5.313a2 2 0 0 1-3 .019L5 15c-1.5-1.5'
'-3-3.2-3-5.5';
@override
Widget build(BuildContext context) {
final svg = filled
? '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">'
'<path d="$_path" fill="currentColor"/></svg>'
: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">'
'<path d="$_path" fill="none" stroke="currentColor" '
'stroke-width="2" stroke-linecap="round" '
'stroke-linejoin="round"/></svg>';
return SvgPicture.string(
svg,
width: size,
height: size,
colorFilter: ColorFilter.mode(color, BlendMode.srcIn),
);
}
}
+3
View File
@@ -22,6 +22,9 @@ dependencies:
flutter_secure_storage: ^10.1.0
go_router: ^17.2.3
flutter_svg: ^2.0.16
# Lucide icon set (design system mandates Lucide, not Material).
# Icons exposed as LucideIcons.<snake_case> IconData usable in Icon().
flutter_lucide: ^1.11.0
google_fonts: ^8.1.0
# 10.x conflicts with flutter_secure_storage 10.x on win32. Hold at 8.3.1
# until either lib bumps win32 to 6.x.