feat: 폼 필드 컴포넌트 분리 및 구독 카드 인터랙션 개선

- billing_cycle_selector, category_selector, currency_selector 컴포넌트 분리
- 구독 카드 클릭 이슈 해결을 위한 리팩토링
- SMS 스캔 화면 UI/UX 개선 및 기능 강화
- 상세 화면 컨트롤러 로직 개선
- 알림 서비스 및 구독 URL 매칭 기능 추가
- CLAUDE.md 프로젝트 가이드라인 대폭 확장
- 전반적인 코드 구조 개선 및 타입 안정성 강화

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
JiWoong Sul
2025-07-14 15:47:46 +09:00
parent 2f60ef585a
commit 111c519883
39 changed files with 2376 additions and 1231 deletions

View File

@@ -1,6 +1,8 @@
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:provider/provider.dart';
import '../models/subscription_model.dart';
import '../providers/category_provider.dart';
import 'website_icon.dart';
import 'app_navigator.dart';
import '../theme/app_colors.dart';
@@ -8,10 +10,12 @@ import 'glassmorphism_card.dart';
class SubscriptionCard extends StatefulWidget {
final SubscriptionModel subscription;
final VoidCallback? onTap;
const SubscriptionCard({
super.key,
required this.subscription,
this.onTap,
});
@override
@@ -190,6 +194,34 @@ class _SubscriptionCardState extends State<SubscriptionCard>
return false;
}
// 카테고리별 그라데이션 색상 생성
List<Color> _getCategoryGradientColors(BuildContext context) {
try {
if (widget.subscription.categoryId == null) {
return AppColors.blueGradient;
}
final categoryProvider = context.watch<CategoryProvider>();
final category = categoryProvider.getCategoryById(widget.subscription.categoryId!);
if (category == null) {
return AppColors.blueGradient;
}
final categoryColor = Color(
int.parse(category.color.replaceAll('#', '0xFF'))
);
return [
categoryColor,
categoryColor.withValues(alpha: 0.8),
];
} catch (e) {
// 색상 파싱 실패 시 기본 파란색 그라데이션 반환
return AppColors.blueGradient;
}
}
@override
Widget build(BuildContext context) {
@@ -200,53 +232,39 @@ class _SubscriptionCardState extends State<SubscriptionCard>
child: MouseRegion(
onEnter: (_) => _onHover(true),
onExit: (_) => _onHover(false),
child: AnimatedBuilder(
animation: _hoverController,
builder: (context, child) {
final scale = 1.0 + (0.02 * _hoverController.value);
child: AnimatedGlassmorphismCard(
padding: EdgeInsets.zero,
borderRadius: 16,
blur: _isHovering ? 15 : 10,
width: double.infinity, // 전체 너비를 차지하도록 설정
onTap: widget.onTap ?? () async {
await AppNavigator.toDetail(context, widget.subscription);
},
child: Column(
children: [
// 그라데이션 상단 바 효과
AnimatedContainer(
duration: const Duration(milliseconds: 200),
height: 4,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: widget.subscription.isCurrentlyInEvent
? [
const Color(0xFFFF6B6B),
const Color(0xFFFF8787),
]
: isNearBilling
? AppColors.amberGradient
: _getCategoryGradientColors(context),
begin: Alignment.centerLeft,
end: Alignment.centerRight,
),
),
),
return Transform.scale(
scale: scale,
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: () async {
await AppNavigator.toDetail(context, widget.subscription);
},
splashColor: AppColors.primaryColor.withValues(alpha: 0.1),
highlightColor: AppColors.primaryColor.withValues(alpha: 0.05),
borderRadius: BorderRadius.circular(16),
child: AnimatedGlassmorphismCard(
onTap: () {}, // onTap은 이미 InkWell에서 처리됨
padding: EdgeInsets.zero,
borderRadius: 16,
blur: _isHovering ? 15 : 10,
width: double.infinity, // 전체 너비를 차지하도록 설정
child: Column(
children: [
// 그라데이션 상단 바 효과
AnimatedContainer(
duration: const Duration(milliseconds: 200),
height: 4,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: widget.subscription.isCurrentlyInEvent
? [
const Color(0xFFFF6B6B),
const Color(0xFFFF8787),
]
: isNearBilling
? AppColors.amberGradient
: AppColors.blueGradient,
begin: Alignment.centerLeft,
end: Alignment.centerRight,
),
),
),
Padding(
padding: const EdgeInsets.all(16),
child: Row(
Padding(
padding: const EdgeInsets.all(16),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 서비스 아이콘
@@ -304,7 +322,7 @@ class _SubscriptionCardState extends State<SubscriptionCard>
borderRadius:
BorderRadius.circular(12),
),
child: Row(
child: const Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
@@ -526,15 +544,10 @@ class _SubscriptionCardState extends State<SubscriptionCard>
),
),
],
),
),
],
),
),
),
),
);
},
],
),
),
),
);