Refactor screens to MVC architecture with modular widgets
- Extract business logic from screens into dedicated controllers - Split large screen files into smaller, reusable widget components - Add controllers for AddSubscriptionScreen and DetailScreen - Create modular widgets for subscription and detail features - Improve code organization and maintainability - Remove duplicated code and improve reusability 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
203
lib/widgets/common/buttons/secondary_button.dart
Normal file
203
lib/widgets/common/buttons/secondary_button.dart
Normal file
@@ -0,0 +1,203 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// 부차적인 액션에 사용되는 Secondary 버튼
|
||||
/// 취소, 되돌아가기, 부가 옵션 등에 사용됩니다.
|
||||
class SecondaryButton extends StatefulWidget {
|
||||
final String text;
|
||||
final VoidCallback? onPressed;
|
||||
final IconData? icon;
|
||||
final double? width;
|
||||
final double height;
|
||||
final Color? borderColor;
|
||||
final Color? textColor;
|
||||
final double fontSize;
|
||||
final EdgeInsetsGeometry? padding;
|
||||
final double borderRadius;
|
||||
final double borderWidth;
|
||||
final bool enableHoverEffect;
|
||||
|
||||
const SecondaryButton({
|
||||
super.key,
|
||||
required this.text,
|
||||
this.onPressed,
|
||||
this.icon,
|
||||
this.width,
|
||||
this.height = 56,
|
||||
this.borderColor,
|
||||
this.textColor,
|
||||
this.fontSize = 16,
|
||||
this.padding,
|
||||
this.borderRadius = 16,
|
||||
this.borderWidth = 1.5,
|
||||
this.enableHoverEffect = true,
|
||||
});
|
||||
|
||||
@override
|
||||
State<SecondaryButton> createState() => _SecondaryButtonState();
|
||||
}
|
||||
|
||||
class _SecondaryButtonState extends State<SecondaryButton> {
|
||||
bool _isHovered = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final effectiveBorderColor = widget.borderColor ??
|
||||
theme.colorScheme.onSurface.withOpacity(0.2);
|
||||
final effectiveTextColor = widget.textColor ??
|
||||
theme.colorScheme.onSurface.withOpacity(0.8);
|
||||
|
||||
Widget button = AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
width: widget.width,
|
||||
height: widget.height,
|
||||
transform: widget.enableHoverEffect && _isHovered
|
||||
? (Matrix4.identity()..scale(1.02))
|
||||
: Matrix4.identity(),
|
||||
child: OutlinedButton(
|
||||
onPressed: widget.onPressed,
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: effectiveTextColor,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(widget.borderRadius),
|
||||
),
|
||||
side: BorderSide(
|
||||
color: _isHovered
|
||||
? effectiveBorderColor.withOpacity(0.4)
|
||||
: effectiveBorderColor,
|
||||
width: widget.borderWidth,
|
||||
),
|
||||
padding: widget.padding ?? const EdgeInsets.symmetric(
|
||||
vertical: 12,
|
||||
horizontal: 24,
|
||||
),
|
||||
backgroundColor: _isHovered
|
||||
? theme.colorScheme.onSurface.withOpacity(0.05)
|
||||
: Colors.transparent,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (widget.icon != null) ...[
|
||||
Icon(
|
||||
widget.icon,
|
||||
color: effectiveTextColor,
|
||||
size: 20,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
],
|
||||
Text(
|
||||
widget.text,
|
||||
style: TextStyle(
|
||||
fontSize: widget.fontSize,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: effectiveTextColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
if (widget.enableHoverEffect) {
|
||||
return MouseRegion(
|
||||
onEnter: (_) => setState(() => _isHovered = true),
|
||||
onExit: (_) => setState(() => _isHovered = false),
|
||||
child: button,
|
||||
);
|
||||
}
|
||||
|
||||
return button;
|
||||
}
|
||||
}
|
||||
|
||||
/// 텍스트 링크 스타일의 버튼
|
||||
/// 간단한 액션이나 링크에 사용됩니다.
|
||||
class TextLinkButton extends StatefulWidget {
|
||||
final String text;
|
||||
final VoidCallback? onPressed;
|
||||
final IconData? icon;
|
||||
final Color? color;
|
||||
final double fontSize;
|
||||
final bool enableHoverEffect;
|
||||
|
||||
const TextLinkButton({
|
||||
super.key,
|
||||
required this.text,
|
||||
this.onPressed,
|
||||
this.icon,
|
||||
this.color,
|
||||
this.fontSize = 14,
|
||||
this.enableHoverEffect = true,
|
||||
});
|
||||
|
||||
@override
|
||||
State<TextLinkButton> createState() => _TextLinkButtonState();
|
||||
}
|
||||
|
||||
class _TextLinkButtonState extends State<TextLinkButton> {
|
||||
bool _isHovered = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final effectiveColor = widget.color ?? theme.colorScheme.primary;
|
||||
|
||||
Widget button = AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
decoration: BoxDecoration(
|
||||
color: _isHovered
|
||||
? theme.colorScheme.onSurface.withOpacity(0.05)
|
||||
: Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: TextButton(
|
||||
onPressed: widget.onPressed,
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: effectiveColor,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 10,
|
||||
vertical: 6,
|
||||
),
|
||||
minimumSize: Size.zero,
|
||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (widget.icon != null) ...[
|
||||
Icon(
|
||||
widget.icon,
|
||||
size: 18,
|
||||
color: effectiveColor,
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
],
|
||||
Text(
|
||||
widget.text,
|
||||
style: TextStyle(
|
||||
fontSize: widget.fontSize,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: effectiveColor,
|
||||
decoration: _isHovered
|
||||
? TextDecoration.underline
|
||||
: TextDecoration.none,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
if (widget.enableHoverEffect) {
|
||||
return MouseRegion(
|
||||
onEnter: (_) => setState(() => _isHovered = true),
|
||||
onExit: (_) => setState(() => _isHovered = false),
|
||||
child: button,
|
||||
);
|
||||
}
|
||||
|
||||
return button;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user