From 84f16c25f6815a73ee0b9ea8829231f9a4d6da50 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Mon, 18 May 2026 20:18:22 -0400 Subject: [PATCH] =?UTF-8?q?feat(ui):=20Lucide=20migration=20unit=201=20?= =?UTF-8?q?=E2=80=94=20dependency=20+=20heart=20(#60)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- .../main/res/drawable/ic_stat_favorite.xml | 3 +- .../res/drawable/ic_stat_favorite_border.xml | 9 +++- flutter_client/lib/likes/like_button.dart | 5 ++- .../lib/shared/widgets/lucide_heart.dart | 43 +++++++++++++++++++ flutter_client/pubspec.yaml | 3 ++ 5 files changed, 58 insertions(+), 5 deletions(-) create mode 100644 flutter_client/lib/shared/widgets/lucide_heart.dart diff --git a/flutter_client/android/app/src/main/res/drawable/ic_stat_favorite.xml b/flutter_client/android/app/src/main/res/drawable/ic_stat_favorite.xml index 51cd65ac..9c9aca56 100644 --- a/flutter_client/android/app/src/main/res/drawable/ic_stat_favorite.xml +++ b/flutter_client/android/app/src/main/res/drawable/ic_stat_favorite.xml @@ -4,7 +4,8 @@ android:viewportWidth="24" android:viewportHeight="24" android:tint="#FFFFFFFF"> + + 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"/> diff --git a/flutter_client/android/app/src/main/res/drawable/ic_stat_favorite_border.xml b/flutter_client/android/app/src/main/res/drawable/ic_stat_favorite_border.xml index 390502df..96ecf90f 100644 --- a/flutter_client/android/app/src/main/res/drawable/ic_stat_favorite_border.xml +++ b/flutter_client/android/app/src/main/res/drawable/ic_stat_favorite_border.xml @@ -4,7 +4,12 @@ android:viewportWidth="24" android:viewportHeight="24" android:tint="#FFFFFFFF"> + + 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"/> diff --git a/flutter_client/lib/likes/like_button.dart b/flutter_client/lib/likes/like_button.dart index 8a43af3c..729d15da 100644 --- a/flutter_client/lib/likes/like_button.dart +++ b/flutter_client/lib/likes/like_button.dart @@ -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, ), diff --git a/flutter_client/lib/shared/widgets/lucide_heart.dart b/flutter_client/lib/shared/widgets/lucide_heart.dart new file mode 100644 index 00000000..9e103435 --- /dev/null +++ b/flutter_client/lib/shared/widgets/lucide_heart.dart @@ -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 + ? '' + '' + : '' + ''; + return SvgPicture.string( + svg, + width: size, + height: size, + colorFilter: ColorFilter.mode(color, BlendMode.srcIn), + ); + } +} diff --git a/flutter_client/pubspec.yaml b/flutter_client/pubspec.yaml index 6bda1980..9121b4c0 100644 --- a/flutter_client/pubspec.yaml +++ b/flutter_client/pubspec.yaml @@ -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. 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.