feat: 글래스모피즘 디자인 시스템 및 색상 가이드 전면 적용

- @doc/color.md 가이드라인에 따른 색상 시스템 전면 개편
- 딥 블루(#2563EB), 스카이 블루(#60A5FA) 메인 컬러로 변경
- 모든 화면과 위젯에 글래스모피즘 효과 일관성 있게 적용
- darkNavy, navyGray 등 새로운 텍스트 색상 체계 도입
- 공통 스낵바 및 다이얼로그 컴포넌트 추가
- Claude AI 프로젝트 컨텍스트 파일(CLAUDE.md) 추가

영향받은 컴포넌트:
- 10개 스크린 (main, settings, detail, splash 등)
- 30개 이상 위젯 (buttons, cards, forms 등)
- 테마 시스템 (AppColors, AppTheme)

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
JiWoong Sul
2025-07-11 18:41:05 +09:00
parent 83c5e3d64e
commit 2f60ef585a
46 changed files with 1096 additions and 580 deletions

View File

@@ -6,6 +6,7 @@ import '../providers/subscription_provider.dart';
import '../providers/category_provider.dart';
import '../services/sms_service.dart';
import '../services/subscription_url_matcher.dart';
import '../widgets/common/snackbar/app_snackbar.dart';
/// AddSubscriptionScreen의 비즈니스 로직을 관리하는 Controller
class AddSubscriptionController {
@@ -232,21 +233,9 @@ class AddSubscriptionController {
final granted = await SMSService.requestSMSPermission();
if (!granted) {
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: const Row(
children: [
Icon(Icons.error_outline, color: Colors.white),
SizedBox(width: 12),
Expanded(child: Text('SMS 권한이 필요합니다.')),
],
),
behavior: SnackBarBehavior.floating,
backgroundColor: Colors.red,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
AppSnackBar.showError(
context: context,
message: 'SMS 권한이 필요합니다.',
);
}
return;
@@ -256,21 +245,9 @@ class AddSubscriptionController {
final subscriptions = await SMSService.scanSubscriptions();
if (subscriptions.isEmpty) {
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: const Row(
children: [
Icon(Icons.info_outline, color: Colors.white),
SizedBox(width: 12),
Expanded(child: Text('구독 관련 SMS를 찾을 수 없습니다.')),
],
),
behavior: SnackBarBehavior.floating,
backgroundColor: Colors.orange,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
AppSnackBar.showWarning(
context: context,
message: '구독 관련 SMS를 찾을 수 없습니다.',
);
}
return;
@@ -331,21 +308,9 @@ class AddSubscriptionController {
});
} catch (e) {
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Row(
children: [
const Icon(Icons.error_outline, color: Colors.white),
const SizedBox(width: 12),
Expanded(child: Text('SMS 스캔 중 오류 발생: $e')),
],
),
behavior: SnackBarBehavior.floating,
backgroundColor: Colors.red,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
AppSnackBar.showError(
context: context,
message: 'SMS 스캔 중 오류 발생: $e',
);
}
} finally {
@@ -399,11 +364,9 @@ class AddSubscriptionController {
});
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('저장 중 오류가 발생했습니다: $e'),
backgroundColor: Colors.red,
),
AppSnackBar.showError(
context: context,
message: '저장 중 오류가 발생했습니다: $e',
);
}
}

View File

@@ -7,6 +7,8 @@ import '../providers/category_provider.dart';
import '../services/subscription_url_matcher.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:intl/intl.dart';
import '../widgets/dialogs/delete_confirmation_dialog.dart';
import '../widgets/common/snackbar/app_snackbar.dart';
/// DetailScreen의 비즈니스 로직을 관리하는 Controller
class DetailScreenController {
@@ -313,20 +315,9 @@ class DetailScreenController {
await provider.updateSubscription(subscription);
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: const Row(
children: [
Icon(Icons.check_circle_rounded, color: Colors.white),
SizedBox(width: 12),
Text('구독 정보가 업데이트되었습니다.'),
],
),
behavior: SnackBarBehavior.floating,
backgroundColor: const Color(0xFF10B981),
duration: const Duration(seconds: 2),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
),
AppSnackBar.showSuccess(
context: context,
message: '구독 정보가 업데이트되었습니다.',
);
// 변경 사항이 반영될 시간을 주기 위해 짧게 지연 후 결과 반환
@@ -340,26 +331,27 @@ class DetailScreenController {
/// 구독 삭제
Future<void> deleteSubscription() async {
if (context.mounted) {
final provider = Provider.of<SubscriptionProvider>(context, listen: false);
await provider.deleteSubscription(subscription.id);
// 삭제 확인 다이얼로그 표시
final shouldDelete = await DeleteConfirmationDialog.show(
context: context,
serviceName: subscription.serviceName,
);
if (!shouldDelete) return;
// 사용자가 확인한 경우에만 삭제 진행
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: const Row(
children: [
Icon(Icons.delete_forever_rounded, color: Colors.white),
SizedBox(width: 12),
Text('구독이 삭제되었습니다.'),
],
),
behavior: SnackBarBehavior.floating,
backgroundColor: const Color(0xFFDC2626),
duration: const Duration(seconds: 2),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
),
);
Navigator.of(context).pop();
final provider = Provider.of<SubscriptionProvider>(context, listen: false);
await provider.deleteSubscription(subscription.id);
if (context.mounted) {
AppSnackBar.showSuccess(
context: context,
message: '구독이 삭제되었습니다.',
icon: Icons.delete_forever_rounded,
);
Navigator.of(context).pop();
}
}
}
}
@@ -371,21 +363,17 @@ class DetailScreenController {
final Uri url = Uri.parse(subscription.websiteUrl!);
if (!await launchUrl(url, mode: LaunchMode.externalApplication)) {
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('웹사이트를 열 수 없습니다.'),
backgroundColor: Colors.red,
),
AppSnackBar.showError(
context: context,
message: '웹사이트를 열 수 없습니다.',
);
}
}
} else {
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('웹사이트 정보가 없습니다. 해지는 웹사이트에서 진행해주세요.'),
backgroundColor: Colors.orange,
),
AppSnackBar.showWarning(
context: context,
message: '웹사이트 정보가 없습니다. 해지는 웹사이트에서 진행해주세요.',
);
}
}