feat(ui): 화면들에 레트로 UI 스타일 적용
- front_screen: 레트로 패널 및 버튼 스타일 - game_play_screen: 레트로 색상 및 초기 BGM 로직 개선 - mobile_carousel_layout: 레트로 테마 적용 - carousel_nav_bar: 골드 액센트 색상 적용
This commit is contained in:
@@ -3,6 +3,8 @@ import 'package:flutter/material.dart';
|
||||
import 'package:askiineverdie/data/game_text_l10n.dart' as game_l10n;
|
||||
import 'package:askiineverdie/l10n/app_localizations.dart';
|
||||
import 'package:askiineverdie/src/features/front/widgets/hero_vs_boss_animation.dart';
|
||||
import 'package:askiineverdie/src/shared/retro_colors.dart';
|
||||
import 'package:askiineverdie/src/shared/widgets/retro_widgets.dart';
|
||||
|
||||
class FrontScreen extends StatelessWidget {
|
||||
const FrontScreen({
|
||||
@@ -60,142 +62,120 @@ class FrontScreen extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final colorScheme = theme.colorScheme;
|
||||
|
||||
return Scaffold(
|
||||
body: Container(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [colorScheme.surfaceContainerHighest, colorScheme.surface],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
),
|
||||
child: SafeArea(
|
||||
child: Column(
|
||||
children: [
|
||||
// 스크롤 영역 (헤더, 애니메이션, 버튼)
|
||||
Expanded(
|
||||
child: Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 960),
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
_HeroHeader(theme: theme, colorScheme: colorScheme),
|
||||
const SizedBox(height: 20),
|
||||
const HeroVsBossAnimation(),
|
||||
const SizedBox(height: 24),
|
||||
_ActionButtons(
|
||||
onNewCharacter: onNewCharacter != null
|
||||
? () => _handleNewCharacter(context)
|
||||
: null,
|
||||
onLoadSave: onLoadSave != null
|
||||
? () => onLoadSave!(context)
|
||||
: null,
|
||||
onHallOfFame: onHallOfFame != null
|
||||
? () => onHallOfFame!(context)
|
||||
: null,
|
||||
),
|
||||
],
|
||||
),
|
||||
backgroundColor: RetroColors.deepBrown,
|
||||
body: SafeArea(
|
||||
child: Column(
|
||||
children: [
|
||||
// 스크롤 영역 (헤더, 애니메이션, 버튼)
|
||||
Expanded(
|
||||
child: Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 800),
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
const _RetroHeader(),
|
||||
const SizedBox(height: 16),
|
||||
const _AnimationPanel(),
|
||||
const SizedBox(height: 16),
|
||||
_ActionButtons(
|
||||
onNewCharacter: onNewCharacter != null
|
||||
? () => _handleNewCharacter(context)
|
||||
: null,
|
||||
onLoadSave: onLoadSave != null
|
||||
? () => onLoadSave!(context)
|
||||
: null,
|
||||
onHallOfFame: onHallOfFame != null
|
||||
? () => onHallOfFame!(context)
|
||||
: null,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
// 카피라이트 푸터 (하단 고정)
|
||||
const _CopyrightFooter(),
|
||||
],
|
||||
),
|
||||
),
|
||||
// 카피라이트 푸터 (하단 고정)
|
||||
const _CopyrightFooter(),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 헤더 (타이틀 + 태그) - 중앙 정렬
|
||||
class _HeroHeader extends StatelessWidget {
|
||||
const _HeroHeader({required this.theme, required this.colorScheme});
|
||||
|
||||
final ThemeData theme;
|
||||
final ColorScheme colorScheme;
|
||||
/// 레트로 스타일 헤더 (타이틀 + 태그)
|
||||
class _RetroHeader extends StatelessWidget {
|
||||
const _RetroHeader();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
colorScheme.primary.withValues(alpha: 0.9),
|
||||
colorScheme.primaryContainer,
|
||||
],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: colorScheme.primary.withValues(alpha: 0.18),
|
||||
blurRadius: 18,
|
||||
offset: const Offset(0, 10),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 24),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
// 타이틀 (중앙 정렬)
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(Icons.auto_awesome, color: colorScheme.onPrimary),
|
||||
const SizedBox(width: 10),
|
||||
Text(
|
||||
L10n.of(context).appTitle,
|
||||
style: theme.textTheme.headlineSmall?.copyWith(
|
||||
color: colorScheme.onPrimary,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
// 태그 (중앙 정렬)
|
||||
Builder(
|
||||
builder: (context) {
|
||||
final l10n = L10n.of(context);
|
||||
return Wrap(
|
||||
alignment: WrapAlignment.center,
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: [
|
||||
_Tag(
|
||||
icon: Icons.cloud_off_outlined,
|
||||
label: l10n.tagNoNetwork,
|
||||
),
|
||||
_Tag(icon: Icons.timer_outlined, label: l10n.tagIdleRpg),
|
||||
_Tag(
|
||||
icon: Icons.storage_rounded,
|
||||
label: l10n.tagLocalSaves,
|
||||
final l10n = L10n.of(context);
|
||||
return RetroGoldPanel(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 20),
|
||||
child: Column(
|
||||
children: [
|
||||
// 타이틀 (픽셀 폰트)
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Icons.auto_awesome, color: RetroColors.gold, size: 20),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
l10n.appTitle,
|
||||
style: const TextStyle(
|
||||
fontFamily: 'PressStart2P',
|
||||
fontSize: 14,
|
||||
color: RetroColors.gold,
|
||||
shadows: [
|
||||
Shadow(
|
||||
color: RetroColors.goldDark,
|
||||
offset: Offset(2, 2),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
// 태그 (레트로 스타일)
|
||||
Wrap(
|
||||
alignment: WrapAlignment.center,
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: [
|
||||
_RetroTag(icon: Icons.cloud_off_outlined, label: l10n.tagNoNetwork),
|
||||
_RetroTag(icon: Icons.timer_outlined, label: l10n.tagIdleRpg),
|
||||
_RetroTag(icon: Icons.storage_rounded, label: l10n.tagLocalSaves),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 액션 버튼 (세로 배치)
|
||||
/// 애니메이션 패널
|
||||
class _AnimationPanel extends StatelessWidget {
|
||||
const _AnimationPanel();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return RetroPanel(
|
||||
title: 'BATTLE',
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: const AspectRatio(
|
||||
aspectRatio: 16 / 9,
|
||||
child: HeroVsBossAnimation(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 액션 버튼 (레트로 스타일)
|
||||
class _ActionButtons extends StatelessWidget {
|
||||
const _ActionButtons({
|
||||
this.onNewCharacter,
|
||||
@@ -209,48 +189,39 @@ class _ActionButtons extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final l10n = L10n.of(context);
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
// 새 캐릭터 (Primary)
|
||||
FilledButton.icon(
|
||||
onPressed: onNewCharacter,
|
||||
icon: const Icon(Icons.casino_outlined),
|
||||
label: Text(l10n.newCharacter),
|
||||
style: FilledButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
|
||||
textStyle: theme.textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
return RetroPanel(
|
||||
title: 'MENU',
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
// 새 캐릭터 (Primary)
|
||||
RetroTextButton(
|
||||
text: l10n.newCharacter,
|
||||
icon: Icons.casino_outlined,
|
||||
onPressed: onNewCharacter,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
// 불러오기 (Secondary)
|
||||
RetroTextButton(
|
||||
text: l10n.loadSave,
|
||||
icon: Icons.folder_open,
|
||||
onPressed: onLoadSave,
|
||||
isPrimary: false,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
// 명예의 전당
|
||||
if (onHallOfFame != null)
|
||||
RetroTextButton(
|
||||
text: game_l10n.uiHallOfFame,
|
||||
icon: Icons.emoji_events_outlined,
|
||||
onPressed: onHallOfFame,
|
||||
isPrimary: false,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
// 불러오기 (Secondary)
|
||||
OutlinedButton.icon(
|
||||
onPressed: onLoadSave,
|
||||
icon: const Icon(Icons.folder_open),
|
||||
label: Text(l10n.loadSave),
|
||||
style: OutlinedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
|
||||
textStyle: theme.textTheme.titleMedium,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
// 명예의 전당 (Tertiary)
|
||||
if (onHallOfFame != null)
|
||||
TextButton.icon(
|
||||
onPressed: onHallOfFame,
|
||||
icon: const Icon(Icons.emoji_events_outlined),
|
||||
label: Text(game_l10n.uiHallOfFame),
|
||||
style: TextButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 14),
|
||||
textStyle: theme.textTheme.titleMedium,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -261,43 +232,51 @@ class _CopyrightFooter extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
child: Text(
|
||||
game_l10n.copyrightText,
|
||||
textAlign: TextAlign.center,
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: theme.colorScheme.onSurface.withValues(alpha: 0.5),
|
||||
style: const TextStyle(
|
||||
fontFamily: 'PressStart2P',
|
||||
fontSize: 6,
|
||||
color: RetroColors.textDisabled,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 태그 칩
|
||||
class _Tag extends StatelessWidget {
|
||||
const _Tag({required this.icon, required this.label});
|
||||
/// 레트로 태그 칩
|
||||
class _RetroTag extends StatelessWidget {
|
||||
const _RetroTag({required this.icon, required this.label});
|
||||
|
||||
final IconData icon;
|
||||
final String label;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
// 어두운 배경에 잘 보이도록 대비되는 색상 사용
|
||||
final tagColor = colorScheme.onPrimaryContainer;
|
||||
return Chip(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
|
||||
backgroundColor: colorScheme.primaryContainer.withValues(alpha: 0.8),
|
||||
avatar: Icon(icon, color: tagColor, size: 16),
|
||||
label: Text(
|
||||
label,
|
||||
style: TextStyle(color: tagColor, fontWeight: FontWeight.w600),
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
|
||||
decoration: BoxDecoration(
|
||||
color: RetroColors.panelBgLight,
|
||||
border: Border.all(color: RetroColors.panelBorderInner, width: 1),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(icon, color: RetroColors.gold, size: 12),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
label,
|
||||
style: const TextStyle(
|
||||
fontFamily: 'PressStart2P',
|
||||
fontSize: 7,
|
||||
color: RetroColors.textLight,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
side: BorderSide.none,
|
||||
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
visualDensity: VisualDensity.compact,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user