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:
272
lib/widgets/common/snackbar/app_snackbar.dart
Normal file
272
lib/widgets/common/snackbar/app_snackbar.dart
Normal file
@@ -0,0 +1,272 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../../theme/app_colors.dart';
|
||||
|
||||
/// 앱 전체에서 사용되는 통합 스낵바
|
||||
/// 성공, 에러, 정보 등 다양한 타입의 메시지를 표시합니다.
|
||||
class AppSnackBar {
|
||||
/// 성공 메시지를 표시합니다.
|
||||
static void showSuccess({
|
||||
required BuildContext context,
|
||||
required String message,
|
||||
IconData icon = Icons.check_circle_rounded,
|
||||
Duration duration = const Duration(seconds: 3),
|
||||
bool showAtTop = true,
|
||||
}) {
|
||||
_show(
|
||||
context: context,
|
||||
message: message,
|
||||
icon: icon,
|
||||
backgroundColor: AppColors.successColor,
|
||||
iconColor: AppColors.pureWhite,
|
||||
textColor: AppColors.pureWhite,
|
||||
duration: duration,
|
||||
showAtTop: showAtTop,
|
||||
);
|
||||
}
|
||||
|
||||
/// 에러 메시지를 표시합니다.
|
||||
static void showError({
|
||||
required BuildContext context,
|
||||
required String message,
|
||||
IconData icon = Icons.error_rounded,
|
||||
Duration duration = const Duration(seconds: 4),
|
||||
bool showAtTop = true,
|
||||
}) {
|
||||
_show(
|
||||
context: context,
|
||||
message: message,
|
||||
icon: icon,
|
||||
backgroundColor: AppColors.dangerColor,
|
||||
iconColor: AppColors.pureWhite,
|
||||
textColor: AppColors.pureWhite,
|
||||
duration: duration,
|
||||
showAtTop: showAtTop,
|
||||
);
|
||||
}
|
||||
|
||||
/// 정보 메시지를 표시합니다.
|
||||
static void showInfo({
|
||||
required BuildContext context,
|
||||
required String message,
|
||||
IconData icon = Icons.info_rounded,
|
||||
Duration duration = const Duration(seconds: 3),
|
||||
bool showAtTop = true,
|
||||
}) {
|
||||
_show(
|
||||
context: context,
|
||||
message: message,
|
||||
icon: icon,
|
||||
backgroundColor: AppColors.primaryColor,
|
||||
iconColor: AppColors.pureWhite,
|
||||
textColor: AppColors.pureWhite,
|
||||
duration: duration,
|
||||
showAtTop: showAtTop,
|
||||
);
|
||||
}
|
||||
|
||||
/// 경고 메시지를 표시합니다.
|
||||
static void showWarning({
|
||||
required BuildContext context,
|
||||
required String message,
|
||||
IconData icon = Icons.warning_amber_rounded,
|
||||
Duration duration = const Duration(seconds: 3),
|
||||
bool showAtTop = true,
|
||||
}) {
|
||||
_show(
|
||||
context: context,
|
||||
message: message,
|
||||
icon: icon,
|
||||
backgroundColor: AppColors.warningColor,
|
||||
iconColor: AppColors.pureWhite,
|
||||
textColor: AppColors.pureWhite,
|
||||
duration: duration,
|
||||
showAtTop: showAtTop,
|
||||
);
|
||||
}
|
||||
|
||||
/// 커스텀 스낵바를 표시합니다.
|
||||
static void showCustom({
|
||||
required BuildContext context,
|
||||
required String message,
|
||||
required IconData icon,
|
||||
required Color backgroundColor,
|
||||
Color iconColor = AppColors.pureWhite,
|
||||
Color textColor = AppColors.pureWhite,
|
||||
Duration duration = const Duration(seconds: 3),
|
||||
bool showAtTop = true,
|
||||
SnackBarAction? action,
|
||||
}) {
|
||||
_show(
|
||||
context: context,
|
||||
message: message,
|
||||
icon: icon,
|
||||
backgroundColor: backgroundColor,
|
||||
iconColor: iconColor,
|
||||
textColor: textColor,
|
||||
duration: duration,
|
||||
showAtTop: showAtTop,
|
||||
action: action,
|
||||
);
|
||||
}
|
||||
|
||||
/// 내부적으로 스낵바를 표시하는 메서드
|
||||
static void _show({
|
||||
required BuildContext context,
|
||||
required String message,
|
||||
required IconData icon,
|
||||
required Color backgroundColor,
|
||||
required Color iconColor,
|
||||
required Color textColor,
|
||||
required Duration duration,
|
||||
required bool showAtTop,
|
||||
SnackBarAction? action,
|
||||
}) {
|
||||
// 기존 스낵바 제거
|
||||
ScaffoldMessenger.of(context).hideCurrentSnackBar();
|
||||
|
||||
// 새 스낵바 표시
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Row(
|
||||
children: [
|
||||
// 아이콘
|
||||
Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: iconColor.withValues(alpha: 0.2),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(
|
||||
icon,
|
||||
color: iconColor,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
// 메시지
|
||||
Expanded(
|
||||
child: Text(
|
||||
message,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: textColor,
|
||||
height: 1.3,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
backgroundColor: backgroundColor,
|
||||
behavior: SnackBarBehavior.floating,
|
||||
margin: showAtTop
|
||||
? EdgeInsets.only(
|
||||
top: MediaQuery.of(context).padding.top + 16,
|
||||
left: 16,
|
||||
right: 16,
|
||||
bottom: MediaQuery.of(context).size.height - 120,
|
||||
)
|
||||
: const EdgeInsets.all(16),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 14,
|
||||
),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
elevation: 4,
|
||||
duration: duration,
|
||||
dismissDirection: DismissDirection.horizontal,
|
||||
action: action,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 로딩 스낵바를 표시합니다. (자동으로 사라지지 않음)
|
||||
static ScaffoldFeatureController<SnackBar, SnackBarClosedReason> showLoading({
|
||||
required BuildContext context,
|
||||
required String message,
|
||||
bool showAtTop = true,
|
||||
}) {
|
||||
// 기존 스낵바 제거
|
||||
ScaffoldMessenger.of(context).hideCurrentSnackBar();
|
||||
|
||||
return ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Row(
|
||||
children: [
|
||||
// 로딩 인디케이터
|
||||
Container(
|
||||
width: 24,
|
||||
height: 24,
|
||||
margin: const EdgeInsets.only(right: 12),
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2.5,
|
||||
color: AppColors.pureWhite,
|
||||
),
|
||||
),
|
||||
// 메시지
|
||||
Expanded(
|
||||
child: Text(
|
||||
message,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColors.pureWhite,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
backgroundColor: AppColors.primaryColor,
|
||||
behavior: SnackBarBehavior.floating,
|
||||
margin: showAtTop
|
||||
? EdgeInsets.only(
|
||||
top: MediaQuery.of(context).padding.top + 16,
|
||||
left: 16,
|
||||
right: 16,
|
||||
bottom: MediaQuery.of(context).size.height - 120,
|
||||
)
|
||||
: const EdgeInsets.all(16),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 14,
|
||||
),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
elevation: 4,
|
||||
duration: const Duration(days: 365), // 자동으로 사라지지 않음
|
||||
dismissDirection: DismissDirection.none, // 스와이프로 닫을 수 없음
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 액션 버튼이 있는 스낵바를 표시합니다.
|
||||
static void showWithAction({
|
||||
required BuildContext context,
|
||||
required String message,
|
||||
required String actionLabel,
|
||||
required VoidCallback onActionPressed,
|
||||
IconData icon = Icons.info_rounded,
|
||||
Color backgroundColor = AppColors.primaryColor,
|
||||
Duration duration = const Duration(seconds: 4),
|
||||
bool showAtTop = true,
|
||||
}) {
|
||||
_show(
|
||||
context: context,
|
||||
message: message,
|
||||
icon: icon,
|
||||
backgroundColor: backgroundColor,
|
||||
iconColor: AppColors.pureWhite,
|
||||
textColor: AppColors.pureWhite,
|
||||
duration: duration,
|
||||
showAtTop: showAtTop,
|
||||
action: SnackBarAction(
|
||||
label: actionLabel,
|
||||
textColor: AppColors.pureWhite,
|
||||
onPressed: onActionPressed,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user