feat(ui): 레트로 위젯 확장

- RetroDialog: 레트로 스타일 다이얼로그 위젯 추가
- RetroButton: 다양한 크기/스타일 옵션 추가
- retro_widgets.dart에 export 추가
This commit is contained in:
JiWoong Sul
2025-12-30 19:03:34 +09:00
parent 2486d84d63
commit 4d9042451c
3 changed files with 472 additions and 25 deletions

View File

@@ -0,0 +1,303 @@
import 'package:flutter/material.dart';
import 'package:askiineverdie/src/shared/retro_colors.dart';
/// 레트로 스타일 다이얼로그 베이스 위젯
///
/// 8-bit RPG 스타일의 다이얼로그 프레임
class RetroDialog extends StatelessWidget {
const RetroDialog({
super.key,
required this.title,
required this.child,
this.titleIcon,
this.maxWidth = 500,
this.maxHeight = 600,
this.accentColor = RetroColors.gold,
});
final String title;
final Widget child;
final String? titleIcon;
final double maxWidth;
final double maxHeight;
final Color accentColor;
@override
Widget build(BuildContext context) {
return Dialog(
backgroundColor: Colors.transparent,
child: Container(
constraints: BoxConstraints(maxWidth: maxWidth, maxHeight: maxHeight),
decoration: BoxDecoration(
color: RetroColors.panelBg,
border: Border(
top: BorderSide(color: accentColor, width: 3),
left: BorderSide(color: accentColor, width: 3),
bottom: const BorderSide(color: RetroColors.panelBorderOuter, width: 3),
right: const BorderSide(color: RetroColors.panelBorderOuter, width: 3),
),
boxShadow: [
BoxShadow(
color: accentColor.withValues(alpha: 0.3),
blurRadius: 20,
spreadRadius: 2,
),
],
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// 헤더 바
_buildHeader(context),
// 본문
Flexible(child: child),
],
),
),
);
}
Widget _buildHeader(BuildContext context) {
return Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
decoration: BoxDecoration(
color: accentColor.withValues(alpha: 0.2),
border: Border(
bottom: BorderSide(color: accentColor, width: 2),
),
),
child: Row(
children: [
if (titleIcon != null) ...[
Text(
titleIcon!,
style: TextStyle(fontSize: 14, color: accentColor),
),
const SizedBox(width: 8),
],
Expanded(
child: Text(
title.toUpperCase(),
style: TextStyle(
fontFamily: 'PressStart2P',
fontSize: 10,
color: accentColor,
letterSpacing: 1,
),
),
),
GestureDetector(
onTap: () => Navigator.of(context).pop(),
child: Container(
padding: const EdgeInsets.all(4),
decoration: BoxDecoration(
border: Border.all(color: RetroColors.textDisabled, width: 1),
),
child: const Text(
'X',
style: TextStyle(
fontFamily: 'PressStart2P',
fontSize: 8,
color: RetroColors.textDisabled,
),
),
),
),
],
),
);
}
}
/// 레트로 스타일 탭 바
class RetroTabBar extends StatelessWidget {
const RetroTabBar({
super.key,
required this.controller,
required this.tabs,
this.accentColor = RetroColors.gold,
});
final TabController controller;
final List<String> tabs;
final Color accentColor;
@override
Widget build(BuildContext context) {
return Container(
decoration: const BoxDecoration(
border: Border(
bottom: BorderSide(color: RetroColors.panelBorderOuter, width: 2),
),
),
child: TabBar(
controller: controller,
isScrollable: tabs.length > 3,
indicator: BoxDecoration(
color: accentColor.withValues(alpha: 0.3),
border: Border(
bottom: BorderSide(color: accentColor, width: 2),
),
),
labelColor: accentColor,
unselectedLabelColor: RetroColors.textDisabled,
labelStyle: const TextStyle(
fontFamily: 'PressStart2P',
fontSize: 7,
),
unselectedLabelStyle: const TextStyle(
fontFamily: 'PressStart2P',
fontSize: 7,
),
dividerColor: Colors.transparent,
tabs: tabs.map((t) => Tab(text: t.toUpperCase())).toList(),
),
);
}
}
/// 레트로 스타일 섹션 헤더
class RetroSectionHeader extends StatelessWidget {
const RetroSectionHeader({
super.key,
required this.title,
this.icon,
this.accentColor = RetroColors.gold,
});
final String title;
final String? icon;
final Color accentColor;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(bottom: 8),
child: Row(
children: [
if (icon != null) ...[
Text(
icon!,
style: TextStyle(fontSize: 12, color: accentColor),
),
const SizedBox(width: 6),
],
Text(
title.toUpperCase(),
style: TextStyle(
fontFamily: 'PressStart2P',
fontSize: 8,
color: accentColor,
),
),
const SizedBox(width: 8),
Expanded(
child: Container(
height: 2,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
accentColor,
accentColor.withValues(alpha: 0.3),
Colors.transparent,
],
),
),
),
),
],
),
);
}
}
/// 레트로 스타일 정보 박스
class RetroInfoBox extends StatelessWidget {
const RetroInfoBox({
super.key,
required this.content,
this.backgroundColor,
});
final String content;
final Color? backgroundColor;
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: backgroundColor ?? RetroColors.deepBrown,
border: Border.all(color: RetroColors.panelBorderOuter, width: 1),
),
child: Text(
content,
style: const TextStyle(
fontFamily: 'PressStart2P',
fontSize: 7,
color: RetroColors.textLight,
height: 1.8,
),
),
);
}
}
/// 레트로 스타일 통계 행
class RetroStatRow extends StatelessWidget {
const RetroStatRow({
super.key,
required this.label,
required this.value,
this.highlight = false,
this.highlightColor = RetroColors.gold,
});
final String label;
final String value;
final bool highlight;
final Color highlightColor;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 3),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
label,
style: const TextStyle(
fontFamily: 'PressStart2P',
fontSize: 6,
color: RetroColors.textDisabled,
),
),
Container(
padding: highlight
? const EdgeInsets.symmetric(horizontal: 6, vertical: 2)
: null,
decoration: highlight
? BoxDecoration(
color: highlightColor.withValues(alpha: 0.2),
border: Border.all(color: highlightColor, width: 1),
)
: null,
child: Text(
value,
style: TextStyle(
fontFamily: 'JetBrainsMono',
fontSize: 9,
color: highlight ? highlightColor : RetroColors.textLight,
fontWeight: FontWeight.bold,
),
),
),
],
),
);
}
}