feat(ui): 승리 오버레이 개선
This commit is contained in:
@@ -8,6 +8,8 @@ import 'package:asciineverdie/src/shared/retro_colors.dart';
|
||||
/// 게임 클리어 엔딩 오버레이 (Act V 완료 시)
|
||||
///
|
||||
/// 영화 엔딩 크레딧 스타일로 텍스트가 아래에서 위로 스크롤됨
|
||||
/// - 탭/클릭 시 스크롤 최하단으로 즉시 이동
|
||||
/// - 최하단에 명예의 전당 버튼 표시
|
||||
class VictoryOverlay extends StatefulWidget {
|
||||
const VictoryOverlay({
|
||||
super.key,
|
||||
@@ -35,6 +37,9 @@ class _VictoryOverlayState extends State<VictoryOverlay>
|
||||
late AnimationController _scrollController;
|
||||
late Animation<double> _scrollAnimation;
|
||||
|
||||
// 스크롤이 완료(최하단 도달) 되었는지 여부
|
||||
bool _isScrollComplete = false;
|
||||
|
||||
// 스크롤 지속 시간 (밀리초)
|
||||
static const _scrollDurationMs = 25000; // 25초
|
||||
|
||||
@@ -51,10 +56,12 @@ class _VictoryOverlayState extends State<VictoryOverlay>
|
||||
CurvedAnimation(parent: _scrollController, curve: Curves.linear),
|
||||
);
|
||||
|
||||
// 스크롤 완료 시 자동 종료
|
||||
// 스크롤 완료 시 버튼 표시 (자동 종료하지 않음)
|
||||
_scrollController.addStatusListener((status) {
|
||||
if (status == AnimationStatus.completed) {
|
||||
widget.onComplete();
|
||||
setState(() {
|
||||
_isScrollComplete = true;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -67,12 +74,21 @@ class _VictoryOverlayState extends State<VictoryOverlay>
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
/// 탭 시 스크롤 최하단으로 즉시 이동
|
||||
void _skipToEnd() {
|
||||
_scrollController.stop();
|
||||
_scrollController.value = 1.0;
|
||||
setState(() {
|
||||
_isScrollComplete = true;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final gold = RetroColors.goldOf(context);
|
||||
|
||||
return GestureDetector(
|
||||
onTap: widget.onComplete, // 탭으로 스킵
|
||||
onTap: _isScrollComplete ? null : _skipToEnd, // 스크롤 중에만 탭으로 스킵
|
||||
child: Material(
|
||||
color: Colors.black,
|
||||
child: SafeArea(
|
||||
@@ -86,12 +102,13 @@ class _VictoryOverlayState extends State<VictoryOverlay>
|
||||
},
|
||||
),
|
||||
|
||||
// 스킵 버튼
|
||||
// 스킵 버튼 (스크롤 중에만 표시)
|
||||
if (!_isScrollComplete)
|
||||
Positioned(
|
||||
top: 16,
|
||||
right: 16,
|
||||
child: TextButton(
|
||||
onPressed: widget.onComplete,
|
||||
onPressed: _skipToEnd,
|
||||
child: Text(
|
||||
L10n.of(context).endingSkip,
|
||||
style: TextStyle(
|
||||
@@ -103,7 +120,8 @@ class _VictoryOverlayState extends State<VictoryOverlay>
|
||||
),
|
||||
),
|
||||
|
||||
// 하단 탭 힌트
|
||||
// 하단 탭 힌트 (스크롤 중에만 표시)
|
||||
if (!_isScrollComplete)
|
||||
Positioned(
|
||||
bottom: 16,
|
||||
left: 0,
|
||||
@@ -145,7 +163,8 @@ class _VictoryOverlayState extends State<VictoryOverlay>
|
||||
|
||||
double _estimateContentHeight() {
|
||||
// 대략적인 콘텐츠 높이 추정 (스크롤 계산용)
|
||||
return 1500.0;
|
||||
// 명예의 전당 버튼 추가로 인해 높이 증가
|
||||
return 1600.0;
|
||||
}
|
||||
|
||||
Widget _buildCreditContent(BuildContext context) {
|
||||
@@ -231,7 +250,13 @@ class _VictoryOverlayState extends State<VictoryOverlay>
|
||||
// THE END
|
||||
// ═══════════════════════════════════
|
||||
_buildTheEnd(context, gold),
|
||||
const SizedBox(height: 200), // 여백 (스크롤 끝)
|
||||
const SizedBox(height: 60),
|
||||
|
||||
// ═══════════════════════════════════
|
||||
// HALL OF FAME BUTTON
|
||||
// ═══════════════════════════════════
|
||||
_buildHallOfFameButton(context, gold),
|
||||
const SizedBox(height: 100), // 여백 (스크롤 끝)
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -518,7 +543,6 @@ class _VictoryOverlayState extends State<VictoryOverlay>
|
||||
}
|
||||
|
||||
Widget _buildTheEnd(BuildContext context, Color gold) {
|
||||
final l10n = L10n.of(context);
|
||||
const theEnd = '''
|
||||
████████╗██╗ ██╗███████╗ ███████╗███╗ ██╗██████╗
|
||||
╚══██╔══╝██║ ██║██╔════╝ ██╔════╝████╗ ██║██╔══██╗
|
||||
@@ -539,7 +563,17 @@ class _VictoryOverlayState extends State<VictoryOverlay>
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/// 명예의 전당 버튼 (최하단)
|
||||
Widget _buildHallOfFameButton(BuildContext context, Color gold) {
|
||||
final l10n = L10n.of(context);
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
// 안내 텍스트
|
||||
Text(
|
||||
l10n.endingHallOfFameLine1,
|
||||
style: TextStyle(
|
||||
@@ -557,6 +591,38 @@ class _VictoryOverlayState extends State<VictoryOverlay>
|
||||
color: gold.withValues(alpha: 0.7),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// 명예의 전당 버튼
|
||||
SizedBox(
|
||||
width: 280,
|
||||
height: 56,
|
||||
child: ElevatedButton(
|
||||
onPressed: widget.onComplete,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: gold,
|
||||
foregroundColor: Colors.black,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
elevation: 8,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Icons.emoji_events, size: 24),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
l10n.endingHallOfFameButton,
|
||||
style: const TextStyle(
|
||||
fontFamily: 'PressStart2P',
|
||||
fontSize: 10,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user