feat: adopt material 3 theme and billing adjustments
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../../theme/app_colors.dart';
|
||||
|
||||
/// 주요 액션에 사용되는 Primary 버튼
|
||||
/// 저장, 추가, 확인 등의 주요 액션에 사용됩니다.
|
||||
@@ -44,26 +43,30 @@ class _PrimaryButtonState extends State<PrimaryButton> {
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final effectiveBackgroundColor =
|
||||
widget.backgroundColor ?? theme.primaryColor;
|
||||
widget.backgroundColor ?? theme.colorScheme.primary;
|
||||
final effectiveForegroundColor =
|
||||
widget.foregroundColor ?? AppColors.pureWhite;
|
||||
widget.foregroundColor ?? theme.colorScheme.onPrimary;
|
||||
|
||||
Widget button = AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
width: widget.width ?? double.infinity,
|
||||
height: widget.height,
|
||||
transform: widget.enableHoverEffect && _isHovered
|
||||
? (Matrix4.identity()..scale(1.02))
|
||||
? Matrix4.diagonal3Values(1.02, 1.02, 1.0)
|
||||
: Matrix4.identity(),
|
||||
child: ElevatedButton(
|
||||
onPressed: widget.isLoading ? null : widget.onPressed,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: effectiveBackgroundColor,
|
||||
foregroundColor: effectiveForegroundColor,
|
||||
// 고정 높이와 텍스트 잘림 방지를 위해 최소 사이즈 지정
|
||||
minimumSize: Size.fromHeight(widget.height),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(widget.borderRadius),
|
||||
),
|
||||
padding: widget.padding ?? const EdgeInsets.symmetric(vertical: 16),
|
||||
// 컨테이너에서 높이를 관리하므로 수직 패딩은 0으로 두고
|
||||
// 수평 여백만 부여하여 작은 높이(예: 48)에서 글자 잘림 방지
|
||||
padding: widget.padding ?? const EdgeInsets.symmetric(horizontal: 16),
|
||||
elevation: widget.enableHoverEffect && _isHovered ? 2 : 0,
|
||||
shadowColor: Colors.black.withValues(alpha: 0.08),
|
||||
disabledBackgroundColor:
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../../theme/app_colors.dart';
|
||||
|
||||
/// 부차적인 액션에 사용되는 Secondary 버튼
|
||||
/// 취소, 되돌아가기, 부가 옵션 등에 사용됩니다.
|
||||
@@ -42,15 +41,17 @@ class _SecondaryButtonState extends State<SecondaryButton> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final effectiveBorderColor = widget.borderColor ?? AppColors.secondaryColor;
|
||||
final effectiveTextColor = widget.textColor ?? AppColors.primaryColor;
|
||||
final theme = Theme.of(context);
|
||||
final effectiveBorderColor =
|
||||
widget.borderColor ?? theme.colorScheme.outline;
|
||||
final effectiveTextColor = widget.textColor ?? theme.colorScheme.primary;
|
||||
|
||||
Widget button = AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
width: widget.width,
|
||||
height: widget.height,
|
||||
transform: widget.enableHoverEffect && _isHovered
|
||||
? (Matrix4.identity()..scale(1.02))
|
||||
? Matrix4.diagonal3Values(1.02, 1.02, 1.0)
|
||||
: Matrix4.identity(),
|
||||
child: OutlinedButton(
|
||||
onPressed: widget.onPressed,
|
||||
@@ -70,8 +71,9 @@ class _SecondaryButtonState extends State<SecondaryButton> {
|
||||
vertical: 12,
|
||||
horizontal: 24,
|
||||
),
|
||||
backgroundColor:
|
||||
_isHovered ? AppColors.glassBackground : Colors.transparent,
|
||||
backgroundColor: _isHovered
|
||||
? theme.colorScheme.onSurface.withValues(alpha: 0.06)
|
||||
: Colors.transparent,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
@@ -140,7 +142,7 @@ class _TextLinkButtonState extends State<TextLinkButton> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final effectiveColor = widget.color ?? AppColors.primaryColor;
|
||||
final effectiveColor = widget.color ?? theme.colorScheme.primary;
|
||||
|
||||
Widget button = AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../../theme/color_scheme_ext.dart';
|
||||
|
||||
/// 확인 다이얼로그 위젯
|
||||
/// 사용자에게 중요한 작업을 확인받을 때 사용합니다.
|
||||
@@ -99,7 +100,9 @@ class ConfirmationDialog extends StatelessWidget {
|
||||
),
|
||||
child: Text(
|
||||
confirmText,
|
||||
style: const TextStyle(color: Colors.white),
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -164,12 +167,13 @@ class SuccessDialog extends StatelessWidget {
|
||||
Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.green.withValues(alpha: 0.1),
|
||||
color:
|
||||
Theme.of(context).colorScheme.success.withValues(alpha: 0.1),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Icon(
|
||||
child: Icon(
|
||||
Icons.check_circle,
|
||||
color: Colors.green,
|
||||
color: Theme.of(context).colorScheme.success,
|
||||
size: 64,
|
||||
),
|
||||
),
|
||||
@@ -188,7 +192,7 @@ class SuccessDialog extends StatelessWidget {
|
||||
message!,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: Colors.grey[600],
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
@@ -203,7 +207,7 @@ class SuccessDialog extends StatelessWidget {
|
||||
onPressed?.call();
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.green,
|
||||
backgroundColor: Theme.of(context).colorScheme.success,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
@@ -214,8 +218,8 @@ class SuccessDialog extends StatelessWidget {
|
||||
),
|
||||
child: Text(
|
||||
buttonText,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
@@ -272,12 +276,12 @@ class ErrorDialog extends StatelessWidget {
|
||||
Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.red.withValues(alpha: 0.1),
|
||||
color: Theme.of(context).colorScheme.error.withValues(alpha: 0.1),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Icon(
|
||||
child: Icon(
|
||||
Icons.error_outline,
|
||||
color: Colors.red,
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
size: 64,
|
||||
),
|
||||
),
|
||||
@@ -296,7 +300,7 @@ class ErrorDialog extends StatelessWidget {
|
||||
message!,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: Colors.grey[600],
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
@@ -311,7 +315,7 @@ class ErrorDialog extends StatelessWidget {
|
||||
onPressed?.call();
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.red,
|
||||
backgroundColor: Theme.of(context).colorScheme.error,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
@@ -322,8 +326,8 @@ class ErrorDialog extends StatelessWidget {
|
||||
),
|
||||
child: Text(
|
||||
buttonText,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import '../../../theme/app_colors.dart';
|
||||
// import '../../../theme/app_colors.dart';
|
||||
|
||||
/// 공통 텍스트 필드 위젯
|
||||
/// 프로젝트 전체에서 일관된 스타일의 텍스트 입력 필드를 제공합니다.
|
||||
@@ -66,10 +66,10 @@ class BaseTextField extends StatelessWidget {
|
||||
if (label != null) ...[
|
||||
Text(
|
||||
label!,
|
||||
style: const TextStyle(
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.textSecondary,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
@@ -89,22 +89,22 @@ class BaseTextField extends StatelessWidget {
|
||||
maxLines: maxLines,
|
||||
minLines: minLines,
|
||||
readOnly: readOnly,
|
||||
cursorColor: cursorColor ?? theme.primaryColor,
|
||||
cursorColor: cursorColor ?? theme.colorScheme.primary,
|
||||
style: style ??
|
||||
const TextStyle(
|
||||
TextStyle(
|
||||
fontSize: 16,
|
||||
color: AppColors.textPrimary,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
decoration: InputDecoration(
|
||||
hintText: hintText,
|
||||
hintStyle: const TextStyle(
|
||||
color: AppColors.textMuted,
|
||||
hintStyle: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
prefixIcon: prefixIcon,
|
||||
prefixText: prefixText,
|
||||
suffixIcon: suffixIcon,
|
||||
filled: true,
|
||||
fillColor: fillColor ?? AppColors.surfaceColorAlt,
|
||||
fillColor: fillColor ?? Theme.of(context).colorScheme.surface,
|
||||
contentPadding: contentPadding ?? const EdgeInsets.all(16),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
@@ -113,15 +113,15 @@ class BaseTextField extends StatelessWidget {
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
borderSide: BorderSide(
|
||||
color: theme.primaryColor,
|
||||
color: theme.colorScheme.primary,
|
||||
width: 2,
|
||||
),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
borderSide: BorderSide(
|
||||
color: AppColors.borderColor.withValues(alpha: 0.7),
|
||||
width: 1.5,
|
||||
color: theme.colorScheme.outline.withValues(alpha: 0.6),
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
disabledBorder: OutlineInputBorder(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../../theme/app_colors.dart';
|
||||
// import '../../../theme/app_colors.dart';
|
||||
import '../../../l10n/app_localizations.dart';
|
||||
|
||||
/// 결제 주기 선택 위젯
|
||||
@@ -8,8 +8,8 @@ class BillingCycleSelector extends StatelessWidget {
|
||||
final String billingCycle;
|
||||
final ValueChanged<String> onChanged;
|
||||
final Color? baseColor;
|
||||
final List<Color>? gradientColors;
|
||||
final bool isGlassmorphism;
|
||||
final List<Color>? gradientColors; // deprecated: ignored
|
||||
final bool isGlassmorphism; // deprecated: ignored
|
||||
|
||||
const BillingCycleSelector({
|
||||
super.key,
|
||||
@@ -24,19 +24,12 @@ class BillingCycleSelector extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
final localization = AppLocalizations.of(context);
|
||||
// 상세 화면에서는 '매월', 추가 화면에서는 '월간'으로 표시
|
||||
final cycles = isGlassmorphism
|
||||
? [
|
||||
localization.billingCycleMonthly,
|
||||
localization.billingCycleQuarterly,
|
||||
localization.billingCycleHalfYearly,
|
||||
localization.billingCycleYearly,
|
||||
]
|
||||
: [
|
||||
localization.monthly,
|
||||
localization.billingCycleQuarterly,
|
||||
localization.billingCycleHalfYearly,
|
||||
localization.yearly,
|
||||
];
|
||||
final cycles = [
|
||||
localization.monthly,
|
||||
localization.billingCycleQuarterly,
|
||||
localization.billingCycleHalfYearly,
|
||||
localization.yearly,
|
||||
];
|
||||
|
||||
return SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
@@ -54,16 +47,16 @@ class BillingCycleSelector extends StatelessWidget {
|
||||
vertical: 12,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: _getBackgroundColor(isSelected),
|
||||
color: _getBackgroundColor(context, isSelected),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: _getBorder(isSelected),
|
||||
border: _getBorder(context, isSelected),
|
||||
),
|
||||
child: Text(
|
||||
cycle,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: _getTextColor(isSelected),
|
||||
color: _getTextColor(context, isSelected),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -74,38 +67,22 @@ class BillingCycleSelector extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Color _getBackgroundColor(bool isSelected) {
|
||||
if (!isSelected) {
|
||||
return isGlassmorphism
|
||||
? AppColors.backgroundColor
|
||||
: Colors.grey.withValues(alpha: 0.1);
|
||||
}
|
||||
|
||||
if (baseColor != null) {
|
||||
return baseColor!;
|
||||
}
|
||||
|
||||
if (gradientColors != null && gradientColors!.isNotEmpty) {
|
||||
return gradientColors![0];
|
||||
}
|
||||
|
||||
return const Color(0xFF3B82F6);
|
||||
}
|
||||
|
||||
Border? _getBorder(bool isSelected) {
|
||||
if (isSelected || !isGlassmorphism) {
|
||||
return null;
|
||||
}
|
||||
return Border.all(
|
||||
color: AppColors.borderColor.withValues(alpha: 0.5),
|
||||
width: 1.5,
|
||||
);
|
||||
}
|
||||
|
||||
Color _getTextColor(bool isSelected) {
|
||||
Color _getBackgroundColor(BuildContext context, bool isSelected) {
|
||||
final scheme = Theme.of(context).colorScheme;
|
||||
if (isSelected) {
|
||||
return Colors.white;
|
||||
return baseColor ?? scheme.primary;
|
||||
}
|
||||
return isGlassmorphism ? AppColors.darkNavy : Colors.grey[700]!;
|
||||
return scheme.surface;
|
||||
}
|
||||
|
||||
Border? _getBorder(BuildContext context, bool isSelected) {
|
||||
final scheme = Theme.of(context).colorScheme;
|
||||
if (isSelected) return null;
|
||||
return Border.all(color: scheme.outline.withValues(alpha: 0.6), width: 1);
|
||||
}
|
||||
|
||||
Color _getTextColor(BuildContext context, bool isSelected) {
|
||||
final scheme = Theme.of(context).colorScheme;
|
||||
return isSelected ? scheme.onPrimary : scheme.onSurface;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../../../theme/app_colors.dart';
|
||||
// import '../../../theme/app_colors.dart';
|
||||
import '../../../providers/category_provider.dart';
|
||||
|
||||
/// 카테고리 선택 위젯
|
||||
@@ -10,8 +10,8 @@ class CategorySelector extends StatelessWidget {
|
||||
final String? selectedCategoryId;
|
||||
final ValueChanged<String?> onChanged;
|
||||
final Color? baseColor;
|
||||
final List<Color>? gradientColors;
|
||||
final bool isGlassmorphism;
|
||||
final List<Color>? gradientColors; // deprecated: ignored
|
||||
final bool isGlassmorphism; // deprecated: ignored
|
||||
|
||||
const CategorySelector({
|
||||
super.key,
|
||||
@@ -39,9 +39,9 @@ class CategorySelector extends StatelessWidget {
|
||||
vertical: 10,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: _getBackgroundColor(isSelected),
|
||||
color: _getBackgroundColor(context, isSelected),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: _getBorder(isSelected),
|
||||
border: _getBorder(context, isSelected),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
@@ -49,7 +49,7 @@ class CategorySelector extends StatelessWidget {
|
||||
Icon(
|
||||
_getCategoryIcon(category),
|
||||
size: 18,
|
||||
color: _getTextColor(isSelected),
|
||||
color: _getTextColor(context, isSelected),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Consumer<CategoryProvider>(
|
||||
@@ -60,7 +60,7 @@ class CategorySelector extends StatelessWidget {
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: _getTextColor(isSelected),
|
||||
color: _getTextColor(context, isSelected),
|
||||
),
|
||||
);
|
||||
},
|
||||
@@ -100,38 +100,22 @@ class CategorySelector extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
Color _getBackgroundColor(bool isSelected) {
|
||||
if (!isSelected) {
|
||||
return isGlassmorphism
|
||||
? AppColors.backgroundColor
|
||||
: Colors.grey.withValues(alpha: 0.1);
|
||||
}
|
||||
|
||||
if (baseColor != null) {
|
||||
return baseColor!;
|
||||
}
|
||||
|
||||
if (gradientColors != null && gradientColors!.isNotEmpty) {
|
||||
return gradientColors![0];
|
||||
}
|
||||
|
||||
return const Color(0xFF3B82F6);
|
||||
}
|
||||
|
||||
Border? _getBorder(bool isSelected) {
|
||||
if (isSelected || !isGlassmorphism) {
|
||||
return null;
|
||||
}
|
||||
return Border.all(
|
||||
color: AppColors.borderColor.withValues(alpha: 0.5),
|
||||
width: 1.5,
|
||||
);
|
||||
}
|
||||
|
||||
Color _getTextColor(bool isSelected) {
|
||||
Color _getBackgroundColor(BuildContext context, bool isSelected) {
|
||||
final scheme = Theme.of(context).colorScheme;
|
||||
if (isSelected) {
|
||||
return Colors.white;
|
||||
return baseColor ?? scheme.primary;
|
||||
}
|
||||
return isGlassmorphism ? AppColors.darkNavy : Colors.grey[700]!;
|
||||
return scheme.surface;
|
||||
}
|
||||
|
||||
Border? _getBorder(BuildContext context, bool isSelected) {
|
||||
final scheme = Theme.of(context).colorScheme;
|
||||
if (isSelected) return null;
|
||||
return Border.all(color: scheme.outline.withValues(alpha: 0.6), width: 1);
|
||||
}
|
||||
|
||||
Color _getTextColor(BuildContext context, bool isSelected) {
|
||||
final scheme = Theme.of(context).colorScheme;
|
||||
return isSelected ? scheme.onPrimary : scheme.onSurface;
|
||||
}
|
||||
}
|
||||
|
||||
112
lib/widgets/common/form_fields/currency_dropdown_field.dart
Normal file
112
lib/widgets/common/form_fields/currency_dropdown_field.dart
Normal file
@@ -0,0 +1,112 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class CurrencyDropdownField extends StatelessWidget {
|
||||
final String currency;
|
||||
final ValueChanged<String> onChanged;
|
||||
|
||||
const CurrencyDropdownField({
|
||||
super.key,
|
||||
required this.currency,
|
||||
required this.onChanged,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
|
||||
return DropdownButtonFormField<String>(
|
||||
initialValue: currency,
|
||||
isExpanded: true,
|
||||
icon: const Icon(Icons.keyboard_arrow_down_rounded),
|
||||
// 선택된 아이템은 코드만 간결하게 표시하여 오버플로우 방지
|
||||
selectedItemBuilder: (context) {
|
||||
final color = theme.colorScheme.onSurface;
|
||||
return const [
|
||||
'KRW',
|
||||
'USD',
|
||||
'JPY',
|
||||
'CNY',
|
||||
].map((code) {
|
||||
return Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
code,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(fontSize: 14, color: color),
|
||||
),
|
||||
);
|
||||
}).toList();
|
||||
},
|
||||
decoration: InputDecoration(
|
||||
filled: true,
|
||||
fillColor: theme.colorScheme.surface,
|
||||
contentPadding:
|
||||
const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
borderSide: BorderSide.none,
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
borderSide: BorderSide(
|
||||
color: theme.colorScheme.outline.withValues(alpha: 0.6),
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
borderSide: BorderSide(
|
||||
color: theme.colorScheme.primary,
|
||||
width: 2,
|
||||
),
|
||||
),
|
||||
),
|
||||
items: const [
|
||||
DropdownMenuItem(
|
||||
value: 'KRW', child: _CurrencyItem(symbol: '₩', code: 'KRW')),
|
||||
DropdownMenuItem(
|
||||
value: 'USD', child: _CurrencyItem(symbol: '\$', code: 'USD')),
|
||||
DropdownMenuItem(
|
||||
value: 'JPY', child: _CurrencyItem(symbol: '¥', code: 'JPY')),
|
||||
DropdownMenuItem(
|
||||
value: 'CNY', child: _CurrencyItem(symbol: '¥', code: 'CNY')),
|
||||
],
|
||||
onChanged: (val) {
|
||||
if (val != null) onChanged(val);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _CurrencyItem extends StatelessWidget {
|
||||
final String symbol;
|
||||
final String code;
|
||||
|
||||
const _CurrencyItem({required this.symbol, required this.code});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final color = Theme.of(context).colorScheme.onSurface;
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
symbol,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: color,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
code,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: color,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -5,10 +5,10 @@ import 'base_text_field.dart';
|
||||
import '../../../l10n/app_localizations.dart';
|
||||
|
||||
/// 통화 입력 필드 위젯
|
||||
/// 원화(KRW)와 달러(USD)를 지원하며 자동 포맷팅을 제공합니다.
|
||||
/// KRW/JPY(정수), USD/CNY(소수점 2자리)를 지원하며 자동 포맷팅을 제공합니다.
|
||||
class CurrencyInputField extends StatefulWidget {
|
||||
final TextEditingController controller;
|
||||
final String currency; // 'KRW' or 'USD'
|
||||
final String currency; // 'KRW' | 'USD' | 'JPY' | 'CNY'
|
||||
final String? label;
|
||||
final String? hintText;
|
||||
final Function(double?)? onChanged;
|
||||
@@ -39,6 +39,7 @@ class CurrencyInputField extends StatefulWidget {
|
||||
class _CurrencyInputFieldState extends State<CurrencyInputField> {
|
||||
late FocusNode _focusNode;
|
||||
bool _isFormatted = false;
|
||||
bool _isPostFrameUpdating = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -66,6 +67,29 @@ class _CurrencyInputFieldState extends State<CurrencyInputField> {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant CurrencyInputField oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (oldWidget.currency != widget.currency) {
|
||||
// 통화 변경 시 빌드 이후에 안전하게 재포맷 적용
|
||||
if (_focusNode.hasFocus) return;
|
||||
final value = _parseValue(widget.controller.text);
|
||||
if (value == null) return;
|
||||
final formatted = _formatCurrency(value);
|
||||
if (widget.controller.text == formatted || _isPostFrameUpdating) return;
|
||||
_isPostFrameUpdating = true;
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (!mounted) return;
|
||||
widget.controller.value = TextEditingValue(
|
||||
text: formatted,
|
||||
selection: TextSelection.collapsed(offset: formatted.length),
|
||||
);
|
||||
_isFormatted = true;
|
||||
_isPostFrameUpdating = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void _onFocusChanged() {
|
||||
if (!_focusNode.hasFocus && widget.controller.text.isNotEmpty) {
|
||||
// 포커스를 잃었을 때 포맷팅 적용
|
||||
@@ -81,7 +105,7 @@ class _CurrencyInputFieldState extends State<CurrencyInputField> {
|
||||
final value = _parseValue(widget.controller.text);
|
||||
if (value != null) {
|
||||
setState(() {
|
||||
if (widget.currency == 'KRW') {
|
||||
if (_isIntegerCurrency(widget.currency)) {
|
||||
widget.controller.text = value.toInt().toString();
|
||||
} else {
|
||||
widget.controller.text = value.toString();
|
||||
@@ -97,7 +121,7 @@ class _CurrencyInputFieldState extends State<CurrencyInputField> {
|
||||
}
|
||||
|
||||
String _formatCurrency(double value) {
|
||||
if (widget.currency == 'KRW') {
|
||||
if (_isIntegerCurrency(widget.currency)) {
|
||||
return NumberFormat.decimalPattern().format(value.toInt());
|
||||
} else {
|
||||
return NumberFormat('#,##0.00').format(value);
|
||||
@@ -108,13 +132,26 @@ class _CurrencyInputFieldState extends State<CurrencyInputField> {
|
||||
final cleanText = text
|
||||
.replaceAll(',', '')
|
||||
.replaceAll('₩', '')
|
||||
.replaceAll('¥', '')
|
||||
.replaceAll('¥', '')
|
||||
.replaceAll('\$', '')
|
||||
.trim();
|
||||
return double.tryParse(cleanText);
|
||||
}
|
||||
|
||||
// ignore: unused_element
|
||||
String get _prefixText {
|
||||
return widget.currency == 'KRW' ? '₩ ' : '\$ ';
|
||||
switch (widget.currency) {
|
||||
case 'KRW':
|
||||
return '₩ ';
|
||||
case 'JPY':
|
||||
return '¥ ';
|
||||
case 'CNY':
|
||||
return '¥ ';
|
||||
case 'USD':
|
||||
default:
|
||||
return '4 ';
|
||||
}
|
||||
}
|
||||
|
||||
String _getDefaultHintText(BuildContext context) {
|
||||
@@ -132,26 +169,27 @@ class _CurrencyInputFieldState extends State<CurrencyInputField> {
|
||||
keyboardType: const TextInputType.numberWithOptions(decimal: true),
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.allow(
|
||||
widget.currency == 'KRW' ? RegExp(r'[0-9]') : RegExp(r'[0-9.]')),
|
||||
if (widget.currency == 'USD')
|
||||
// USD의 경우 소수점 이하 2자리까지만 허용
|
||||
_isIntegerCurrency(widget.currency)
|
||||
? RegExp(r'[0-9]')
|
||||
: RegExp(r'[0-9.]'),
|
||||
),
|
||||
if (!_isIntegerCurrency(widget.currency))
|
||||
// 소수 통화(USD/CNY): 소수점 이하 2자리 제한
|
||||
TextInputFormatter.withFunction((oldValue, newValue) {
|
||||
final text = newValue.text;
|
||||
if (text.isEmpty) return newValue;
|
||||
|
||||
final parts = text.split('.');
|
||||
if (parts.length > 2) {
|
||||
// 소수점이 2개 이상인 경우 거부
|
||||
return oldValue;
|
||||
return oldValue; // 소수점이 2개 이상인 경우 거부
|
||||
}
|
||||
if (parts.length == 2 && parts[1].length > 2) {
|
||||
// 소수점 이하가 2자리를 초과하는 경우 거부
|
||||
return oldValue;
|
||||
return oldValue; // 소수점 이하 2자 초과 거부
|
||||
}
|
||||
return newValue;
|
||||
}),
|
||||
],
|
||||
prefixText: _prefixText,
|
||||
prefixText: _getPrefixText(),
|
||||
onEditingComplete: widget.onEditingComplete,
|
||||
enabled: widget.enabled,
|
||||
onChanged: (value) {
|
||||
@@ -172,3 +210,23 @@ class _CurrencyInputFieldState extends State<CurrencyInputField> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
bool _isIntegerCurrency(String code) => code == 'KRW' || code == 'JPY';
|
||||
|
||||
// 안전한 프리픽스 계산 함수(모든 통화 지원)
|
||||
String _currencySymbol(String code) {
|
||||
switch (code) {
|
||||
case 'KRW':
|
||||
return '₩';
|
||||
case 'JPY':
|
||||
case 'CNY':
|
||||
return '¥';
|
||||
case 'USD':
|
||||
default:
|
||||
return '\$';
|
||||
}
|
||||
}
|
||||
|
||||
extension on _CurrencyInputFieldState {
|
||||
String _getPrefixText() => '${_currencySymbol(widget.currency)} ';
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../../theme/app_colors.dart';
|
||||
// import '../../../theme/app_colors.dart';
|
||||
|
||||
/// 통화 선택 위젯
|
||||
/// KRW(원화), USD(달러), JPY(엔화), CNY(위안화) 중 선택할 수 있습니다.
|
||||
class CurrencySelector extends StatelessWidget {
|
||||
final String currency;
|
||||
final ValueChanged<String> onChanged;
|
||||
final bool isGlassmorphism;
|
||||
final bool isGlassmorphism; // deprecated: ignored
|
||||
|
||||
const CurrencySelector({
|
||||
super.key,
|
||||
@@ -72,7 +72,7 @@ class _CurrencyOption extends StatelessWidget {
|
||||
final String? subtitle;
|
||||
final bool isSelected;
|
||||
final VoidCallback onTap;
|
||||
final bool isGlassmorphism;
|
||||
final bool isGlassmorphism; // deprecated: ignored
|
||||
|
||||
const _CurrencyOption({
|
||||
required this.label,
|
||||
@@ -96,7 +96,7 @@ class _CurrencyOption extends StatelessWidget {
|
||||
decoration: BoxDecoration(
|
||||
color: _getBackgroundColor(theme),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: _getBorder(),
|
||||
border: _getBorder(theme),
|
||||
),
|
||||
child: Center(
|
||||
child: Column(
|
||||
@@ -107,7 +107,7 @@ class _CurrencyOption extends StatelessWidget {
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: _getTextColor(),
|
||||
color: _getTextColor(theme),
|
||||
),
|
||||
),
|
||||
if (subtitle != null) ...[
|
||||
@@ -117,7 +117,7 @@ class _CurrencyOption extends StatelessWidget {
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: _getTextColor().withValues(alpha: 0.8),
|
||||
color: _getTextColor(theme).withValues(alpha: 0.8),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -130,28 +130,20 @@ class _CurrencyOption extends StatelessWidget {
|
||||
}
|
||||
|
||||
Color _getBackgroundColor(ThemeData theme) {
|
||||
if (isSelected) {
|
||||
return isGlassmorphism ? theme.primaryColor : const Color(0xFF3B82F6);
|
||||
}
|
||||
return isGlassmorphism
|
||||
? AppColors.surfaceColorAlt
|
||||
: Colors.grey.withValues(alpha: 0.1);
|
||||
final scheme = theme.colorScheme;
|
||||
return isSelected ? scheme.primary : scheme.surface;
|
||||
}
|
||||
|
||||
Border? _getBorder() {
|
||||
if (isSelected || !isGlassmorphism) {
|
||||
return null;
|
||||
}
|
||||
Border? _getBorder(ThemeData theme) {
|
||||
if (isSelected) return null;
|
||||
return Border.all(
|
||||
color: AppColors.borderColor,
|
||||
width: 1.5,
|
||||
color: theme.colorScheme.outline.withValues(alpha: 0.6),
|
||||
width: 1,
|
||||
);
|
||||
}
|
||||
|
||||
Color _getTextColor() {
|
||||
if (isSelected) {
|
||||
return Colors.white;
|
||||
}
|
||||
return isGlassmorphism ? AppColors.navyGray : Colors.grey[600]!;
|
||||
Color _getTextColor(ThemeData theme) {
|
||||
final scheme = theme.colorScheme;
|
||||
return isSelected ? scheme.onPrimary : scheme.onSurface;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import '../../../theme/app_colors.dart';
|
||||
// import '../../../theme/app_colors.dart';
|
||||
import '../../../l10n/app_localizations.dart';
|
||||
|
||||
/// 날짜 선택 필드 위젯
|
||||
@@ -48,10 +48,10 @@ class DatePickerField extends StatelessWidget {
|
||||
children: [
|
||||
Text(
|
||||
label,
|
||||
style: const TextStyle(
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.darkNavy,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
@@ -67,13 +67,14 @@ class DatePickerField extends StatelessWidget {
|
||||
lastDate: lastDate ??
|
||||
DateTime.now().add(const Duration(days: 365 * 10)),
|
||||
builder: (BuildContext context, Widget? child) {
|
||||
final cs = Theme.of(context).colorScheme;
|
||||
return Theme(
|
||||
data: ThemeData.light().copyWith(
|
||||
colorScheme: ColorScheme.light(
|
||||
data: Theme.of(context).copyWith(
|
||||
colorScheme: cs.copyWith(
|
||||
primary: effectivePrimaryColor,
|
||||
onPrimary: Colors.white,
|
||||
surface: Colors.white,
|
||||
onSurface: Colors.black,
|
||||
onPrimary: cs.onPrimary,
|
||||
surface: cs.surface,
|
||||
onSurface: cs.onSurface,
|
||||
),
|
||||
),
|
||||
child: child!,
|
||||
@@ -90,10 +91,13 @@ class DatePickerField extends StatelessWidget {
|
||||
child: Container(
|
||||
padding: contentPadding ?? const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: backgroundColor ?? AppColors.surfaceColorAlt,
|
||||
color: backgroundColor ?? Theme.of(context).colorScheme.surface,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(
|
||||
color: AppColors.borderColor.withValues(alpha: 0.7),
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.outline
|
||||
.withValues(alpha: 0.6),
|
||||
width: 1.5,
|
||||
),
|
||||
),
|
||||
@@ -105,15 +109,18 @@ class DatePickerField extends StatelessWidget {
|
||||
.format(selectedDate),
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color:
|
||||
enabled ? AppColors.textPrimary : AppColors.textMuted,
|
||||
color: enabled
|
||||
? Theme.of(context).colorScheme.onSurface
|
||||
: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
Icon(
|
||||
Icons.calendar_today,
|
||||
size: 20,
|
||||
color: enabled ? AppColors.navyGray : AppColors.textMuted,
|
||||
color: enabled
|
||||
? Theme.of(context).colorScheme.onSurfaceVariant
|
||||
: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -214,13 +221,14 @@ class _DateRangeItem extends StatelessWidget {
|
||||
firstDate: firstDate,
|
||||
lastDate: lastDate,
|
||||
builder: (BuildContext context, Widget? child) {
|
||||
final cs = Theme.of(context).colorScheme;
|
||||
return Theme(
|
||||
data: ThemeData.light().copyWith(
|
||||
colorScheme: ColorScheme.light(
|
||||
data: Theme.of(context).copyWith(
|
||||
colorScheme: cs.copyWith(
|
||||
primary: effectivePrimaryColor,
|
||||
onPrimary: Colors.white,
|
||||
surface: Colors.white,
|
||||
onSurface: Colors.black,
|
||||
onPrimary: cs.onPrimary,
|
||||
surface: cs.surface,
|
||||
onSurface: cs.onSurface,
|
||||
),
|
||||
),
|
||||
child: child!,
|
||||
@@ -237,10 +245,10 @@ class _DateRangeItem extends StatelessWidget {
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.surfaceColorAlt,
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(
|
||||
color: AppColors.borderColor.withValues(alpha: 0.7),
|
||||
color: Theme.of(context).colorScheme.outline.withValues(alpha: 0.6),
|
||||
width: 1.5,
|
||||
),
|
||||
),
|
||||
@@ -249,9 +257,9 @@ class _DateRangeItem extends StatelessWidget {
|
||||
children: [
|
||||
Text(
|
||||
label,
|
||||
style: const TextStyle(
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: AppColors.textSecondary,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
@@ -263,8 +271,9 @@ class _DateRangeItem extends StatelessWidget {
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color:
|
||||
date != null ? AppColors.textPrimary : AppColors.textMuted,
|
||||
color: date != null
|
||||
? Theme.of(context).colorScheme.onSurface
|
||||
: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
33
lib/widgets/common/layout/page_container.dart
Normal file
33
lib/widgets/common/layout/page_container.dart
Normal file
@@ -0,0 +1,33 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../../theme/ui_constants.dart';
|
||||
|
||||
/// 페이지 공통 좌우 패딩과 최대 폭을 보장하는 래퍼
|
||||
class PageContainer extends StatelessWidget {
|
||||
final Widget child;
|
||||
final EdgeInsetsGeometry? padding;
|
||||
final double maxWidth;
|
||||
|
||||
const PageContainer({
|
||||
super.key,
|
||||
required this.child,
|
||||
this.padding,
|
||||
this.maxWidth = 720,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Align(
|
||||
alignment: Alignment.topCenter,
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(maxWidth: maxWidth),
|
||||
child: Padding(
|
||||
padding: padding ??
|
||||
const EdgeInsets.symmetric(
|
||||
horizontal: UIConstants.pageHorizontalPadding,
|
||||
),
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../../theme/app_colors.dart';
|
||||
import '../../../theme/color_scheme_ext.dart';
|
||||
|
||||
/// 앱 전체에서 사용되는 통합 스낵바
|
||||
/// 성공, 에러, 정보 등 다양한 타입의 메시지를 표시합니다.
|
||||
@@ -16,9 +16,9 @@ class AppSnackBar {
|
||||
context: context,
|
||||
message: message,
|
||||
icon: icon,
|
||||
backgroundColor: AppColors.successColor,
|
||||
iconColor: AppColors.pureWhite,
|
||||
textColor: AppColors.pureWhite,
|
||||
backgroundColor: Theme.of(context).colorScheme.success,
|
||||
iconColor: Theme.of(context).colorScheme.onPrimary,
|
||||
textColor: Theme.of(context).colorScheme.onPrimary,
|
||||
duration: duration,
|
||||
showAtTop: showAtTop,
|
||||
);
|
||||
@@ -32,13 +32,14 @@ class AppSnackBar {
|
||||
Duration duration = const Duration(seconds: 4),
|
||||
bool showAtTop = true,
|
||||
}) {
|
||||
final cs = Theme.of(context).colorScheme;
|
||||
_show(
|
||||
context: context,
|
||||
message: message,
|
||||
icon: icon,
|
||||
backgroundColor: AppColors.dangerColor,
|
||||
iconColor: AppColors.pureWhite,
|
||||
textColor: AppColors.pureWhite,
|
||||
backgroundColor: cs.error,
|
||||
iconColor: Theme.of(context).colorScheme.onPrimary,
|
||||
textColor: Theme.of(context).colorScheme.onPrimary,
|
||||
duration: duration,
|
||||
showAtTop: showAtTop,
|
||||
);
|
||||
@@ -56,9 +57,9 @@ class AppSnackBar {
|
||||
context: context,
|
||||
message: message,
|
||||
icon: icon,
|
||||
backgroundColor: AppColors.primaryColor,
|
||||
iconColor: AppColors.pureWhite,
|
||||
textColor: AppColors.pureWhite,
|
||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
iconColor: Theme.of(context).colorScheme.onPrimary,
|
||||
textColor: Theme.of(context).colorScheme.onPrimary,
|
||||
duration: duration,
|
||||
showAtTop: showAtTop,
|
||||
);
|
||||
@@ -76,9 +77,9 @@ class AppSnackBar {
|
||||
context: context,
|
||||
message: message,
|
||||
icon: icon,
|
||||
backgroundColor: AppColors.warningColor,
|
||||
iconColor: AppColors.pureWhite,
|
||||
textColor: AppColors.pureWhite,
|
||||
backgroundColor: Theme.of(context).colorScheme.warning,
|
||||
iconColor: Theme.of(context).colorScheme.onPrimary,
|
||||
textColor: Theme.of(context).colorScheme.onPrimary,
|
||||
duration: duration,
|
||||
showAtTop: showAtTop,
|
||||
);
|
||||
@@ -90,8 +91,8 @@ class AppSnackBar {
|
||||
required String message,
|
||||
required IconData icon,
|
||||
required Color backgroundColor,
|
||||
Color iconColor = AppColors.pureWhite,
|
||||
Color textColor = AppColors.pureWhite,
|
||||
Color iconColor = Colors.white,
|
||||
Color textColor = Colors.white,
|
||||
Duration duration = const Duration(seconds: 3),
|
||||
bool showAtTop = true,
|
||||
SnackBarAction? action,
|
||||
@@ -200,25 +201,25 @@ class AppSnackBar {
|
||||
width: 24,
|
||||
height: 24,
|
||||
margin: const EdgeInsets.only(right: 12),
|
||||
child: const CircularProgressIndicator(
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2.5,
|
||||
color: AppColors.pureWhite,
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
),
|
||||
// 메시지
|
||||
Expanded(
|
||||
child: Text(
|
||||
message,
|
||||
style: const TextStyle(
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColors.pureWhite,
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
backgroundColor: AppColors.primaryColor,
|
||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
behavior: SnackBarBehavior.floating,
|
||||
margin: showAtTop
|
||||
? EdgeInsets.only(
|
||||
@@ -249,7 +250,7 @@ class AppSnackBar {
|
||||
required String actionLabel,
|
||||
required VoidCallback onActionPressed,
|
||||
IconData icon = Icons.info_rounded,
|
||||
Color backgroundColor = AppColors.primaryColor,
|
||||
Color? backgroundColor,
|
||||
Duration duration = const Duration(seconds: 4),
|
||||
bool showAtTop = true,
|
||||
}) {
|
||||
@@ -257,14 +258,14 @@ class AppSnackBar {
|
||||
context: context,
|
||||
message: message,
|
||||
icon: icon,
|
||||
backgroundColor: backgroundColor,
|
||||
iconColor: AppColors.pureWhite,
|
||||
textColor: AppColors.pureWhite,
|
||||
backgroundColor: backgroundColor ?? Theme.of(context).colorScheme.primary,
|
||||
iconColor: Theme.of(context).colorScheme.onPrimary,
|
||||
textColor: Theme.of(context).colorScheme.onPrimary,
|
||||
duration: duration,
|
||||
showAtTop: showAtTop,
|
||||
action: SnackBarAction(
|
||||
label: actionLabel,
|
||||
textColor: AppColors.pureWhite,
|
||||
textColor: Theme.of(context).colorScheme.onPrimary,
|
||||
onPressed: onActionPressed,
|
||||
),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user