Compare commits
3 Commits
de20183b73
...
6c92a323c0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6c92a323c0 | ||
|
|
8efd3e875c | ||
|
|
01e26bb5f5 |
@@ -1792,7 +1792,7 @@ String get warningDeleteSave {
|
||||
|
||||
String get copyrightText {
|
||||
// 카피라이트 텍스트는 언어에 따라 변하지 않음
|
||||
return '© 2025 naturebridgeai & cclabs all rights reserved';
|
||||
return '© 2025 NatureBridgeAi & cclabs all rights reserved';
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
@@ -307,5 +307,8 @@
|
||||
"@endingSkip": { "description": "Skip button" },
|
||||
|
||||
"endingTapToSkip": "TAP TO SKIP",
|
||||
"@endingTapToSkip": { "description": "Tap to skip hint" }
|
||||
"@endingTapToSkip": { "description": "Tap to skip hint" },
|
||||
|
||||
"endingHoldToSpeedUp": "HOLD TO SPEED UP",
|
||||
"@endingHoldToSpeedUp": { "description": "Hold to speed up scrolling hint" }
|
||||
}
|
||||
|
||||
@@ -92,5 +92,6 @@
|
||||
"endingHallOfFameLine2": "殿堂に記録されます",
|
||||
"endingHallOfFameButton": "殿堂入り",
|
||||
"endingSkip": "スキップ",
|
||||
"endingTapToSkip": "タップでスキップ"
|
||||
"endingTapToSkip": "タップでスキップ",
|
||||
"endingHoldToSpeedUp": "長押しで高速スクロール"
|
||||
}
|
||||
|
||||
@@ -92,5 +92,6 @@
|
||||
"endingHallOfFameLine2": "명예의 전당에 기록됩니다",
|
||||
"endingHallOfFameButton": "명예의 전당",
|
||||
"endingSkip": "건너뛰기",
|
||||
"endingTapToSkip": "탭하여 건너뛰기"
|
||||
"endingTapToSkip": "탭하여 건너뛰기",
|
||||
"endingHoldToSpeedUp": "길게 누르면 빨리 스크롤"
|
||||
}
|
||||
|
||||
@@ -646,6 +646,12 @@ abstract class L10n {
|
||||
/// In en, this message translates to:
|
||||
/// **'TAP TO SKIP'**
|
||||
String get endingTapToSkip;
|
||||
|
||||
/// Hold to speed up scrolling hint
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'HOLD TO SPEED UP'**
|
||||
String get endingHoldToSpeedUp;
|
||||
}
|
||||
|
||||
class _L10nDelegate extends LocalizationsDelegate<L10n> {
|
||||
|
||||
@@ -294,4 +294,7 @@ class L10nEn extends L10n {
|
||||
|
||||
@override
|
||||
String get endingTapToSkip => 'TAP TO SKIP';
|
||||
|
||||
@override
|
||||
String get endingHoldToSpeedUp => 'HOLD TO SPEED UP';
|
||||
}
|
||||
|
||||
@@ -294,4 +294,7 @@ class L10nJa extends L10n {
|
||||
|
||||
@override
|
||||
String get endingTapToSkip => 'タップでスキップ';
|
||||
|
||||
@override
|
||||
String get endingHoldToSpeedUp => '長押しで高速スクロール';
|
||||
}
|
||||
|
||||
@@ -294,4 +294,7 @@ class L10nKo extends L10n {
|
||||
|
||||
@override
|
||||
String get endingTapToSkip => '탭하여 건너뛰기';
|
||||
|
||||
@override
|
||||
String get endingHoldToSpeedUp => '길게 누르면 빨리 스크롤';
|
||||
}
|
||||
|
||||
@@ -294,4 +294,7 @@ class L10nZh extends L10n {
|
||||
|
||||
@override
|
||||
String get endingTapToSkip => '点击跳过';
|
||||
|
||||
@override
|
||||
String get endingHoldToSpeedUp => '长按加速滚动';
|
||||
}
|
||||
|
||||
@@ -92,5 +92,6 @@
|
||||
"endingHallOfFameLine2": "将被铭记于荣誉殿堂",
|
||||
"endingHallOfFameButton": "荣誉殿堂",
|
||||
"endingSkip": "跳过",
|
||||
"endingTapToSkip": "点击跳过"
|
||||
"endingTapToSkip": "点击跳过",
|
||||
"endingHoldToSpeedUp": "长按加速滚动"
|
||||
}
|
||||
|
||||
@@ -34,107 +34,184 @@ class VictoryOverlay extends StatefulWidget {
|
||||
|
||||
class _VictoryOverlayState extends State<VictoryOverlay>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late AnimationController _scrollController;
|
||||
late AnimationController _animationController;
|
||||
late Animation<double> _scrollAnimation;
|
||||
|
||||
// 스크롤이 완료(최하단 도달) 되었는지 여부
|
||||
bool _isScrollComplete = false;
|
||||
|
||||
// 터치 중인지 여부 (터치 시 속도업)
|
||||
bool _isTouching = false;
|
||||
|
||||
// 스크롤 완료 후 수동 스크롤용 컨트롤러
|
||||
late ScrollController _manualScrollController;
|
||||
|
||||
// 스크롤 지속 시간 (밀리초)
|
||||
static const _scrollDurationMs = 25000; // 25초
|
||||
static const _scrollDurationMs = 25000; // 25초 (기본 속도)
|
||||
static const _fastScrollDurationMs = 5000; // 5초 (빠른 속도, 5배속)
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
_scrollController = AnimationController(
|
||||
_manualScrollController = ScrollController();
|
||||
|
||||
_animationController = AnimationController(
|
||||
duration: const Duration(milliseconds: _scrollDurationMs),
|
||||
vsync: this,
|
||||
);
|
||||
|
||||
_scrollAnimation = Tween<double>(
|
||||
begin: 0.0,
|
||||
end: 1.0,
|
||||
).animate(CurvedAnimation(parent: _scrollController, curve: Curves.linear));
|
||||
_scrollAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
|
||||
CurvedAnimation(parent: _animationController, curve: Curves.linear),
|
||||
);
|
||||
|
||||
// 스크롤 완료 시 버튼 표시 (자동 종료하지 않음)
|
||||
_scrollController.addStatusListener((status) {
|
||||
// 스크롤 완료 시 수동 스크롤 모드로 전환
|
||||
_animationController.addStatusListener((status) {
|
||||
if (status == AnimationStatus.completed) {
|
||||
setState(() {
|
||||
_isScrollComplete = true;
|
||||
});
|
||||
_onScrollComplete();
|
||||
}
|
||||
});
|
||||
|
||||
_scrollController.forward();
|
||||
_animationController.forward();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_scrollController.dispose();
|
||||
_animationController.dispose();
|
||||
_manualScrollController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
/// 탭 시 스크롤 최하단으로 즉시 이동
|
||||
void _skipToEnd() {
|
||||
_scrollController.stop();
|
||||
_scrollController.value = 1.0;
|
||||
/// 스크롤 완료 시 호출
|
||||
void _onScrollComplete() {
|
||||
setState(() {
|
||||
_isScrollComplete = true;
|
||||
});
|
||||
// 수동 스크롤 시 맨 아래에서 시작
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (_manualScrollController.hasClients) {
|
||||
_manualScrollController.jumpTo(
|
||||
_manualScrollController.position.maxScrollExtent,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// 터치 시작 - 스크롤 속도업
|
||||
void _onTouchStart() {
|
||||
if (_isScrollComplete) return;
|
||||
|
||||
setState(() {
|
||||
_isTouching = true;
|
||||
});
|
||||
|
||||
// 현재 진행도 저장
|
||||
final currentProgress = _animationController.value;
|
||||
final remainingProgress = 1.0 - currentProgress;
|
||||
|
||||
// 남은 시간 계산 (빠른 속도 기준)
|
||||
final remainingDuration = Duration(
|
||||
milliseconds: (remainingProgress * _fastScrollDurationMs).toInt(),
|
||||
);
|
||||
|
||||
_animationController.duration = remainingDuration;
|
||||
_animationController.forward(from: currentProgress);
|
||||
}
|
||||
|
||||
/// 터치 종료 - 스크롤 속도 복원
|
||||
void _onTouchEnd() {
|
||||
if (_isScrollComplete) return;
|
||||
|
||||
setState(() {
|
||||
_isTouching = false;
|
||||
});
|
||||
|
||||
// 현재 진행도 저장
|
||||
final currentProgress = _animationController.value;
|
||||
final remainingProgress = 1.0 - currentProgress;
|
||||
|
||||
// 남은 시간 계산 (기본 속도 기준)
|
||||
final remainingDuration = Duration(
|
||||
milliseconds: (remainingProgress * _scrollDurationMs).toInt(),
|
||||
);
|
||||
|
||||
_animationController.duration = remainingDuration;
|
||||
_animationController.forward(from: currentProgress);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final gold = RetroColors.goldOf(context);
|
||||
final l10n = L10n.of(context);
|
||||
|
||||
return GestureDetector(
|
||||
onTap: _isScrollComplete ? null : _skipToEnd, // 스크롤 중에만 탭으로 스킵
|
||||
// 터치 시 스크롤 속도업 (스크롤 중에만)
|
||||
onTapDown: _isScrollComplete ? null : (_) => _onTouchStart(),
|
||||
onTapUp: _isScrollComplete ? null : (_) => _onTouchEnd(),
|
||||
onTapCancel: _isScrollComplete ? null : _onTouchEnd,
|
||||
onLongPressStart: _isScrollComplete ? null : (_) => _onTouchStart(),
|
||||
onLongPressEnd: _isScrollComplete ? null : (_) => _onTouchEnd(),
|
||||
onLongPressCancel: _isScrollComplete ? null : _onTouchEnd,
|
||||
child: Material(
|
||||
color: Colors.black,
|
||||
child: SafeArea(
|
||||
child: Stack(
|
||||
children: [
|
||||
// 스크롤되는 크레딧
|
||||
AnimatedBuilder(
|
||||
animation: _scrollAnimation,
|
||||
builder: (context, child) {
|
||||
return _buildScrollingCredits(context);
|
||||
},
|
||||
),
|
||||
|
||||
// 스킵 버튼 (스크롤 중에만 표시)
|
||||
// 스크롤 중: 애니메이션 기반 스크롤
|
||||
// 스크롤 완료: 수동 스크롤 가능
|
||||
if (!_isScrollComplete)
|
||||
Positioned(
|
||||
top: 16,
|
||||
right: 16,
|
||||
child: TextButton(
|
||||
onPressed: _skipToEnd,
|
||||
child: Text(
|
||||
L10n.of(context).endingSkip,
|
||||
style: TextStyle(
|
||||
fontFamily: 'PressStart2P',
|
||||
fontSize: 10,
|
||||
color: gold.withValues(alpha: 0.5),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
AnimatedBuilder(
|
||||
animation: _scrollAnimation,
|
||||
builder: (context, child) {
|
||||
return _buildScrollingCredits(context);
|
||||
},
|
||||
)
|
||||
else
|
||||
_buildManualScrollableCredits(context),
|
||||
|
||||
// 하단 탭 힌트 (스크롤 중에만 표시)
|
||||
// 하단 터치 힌트 (스크롤 중에만 표시)
|
||||
if (!_isScrollComplete)
|
||||
Positioned(
|
||||
bottom: 16,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: Text(
|
||||
L10n.of(context).endingTapToSkip,
|
||||
style: TextStyle(
|
||||
fontFamily: 'PressStart2P',
|
||||
fontSize: 8,
|
||||
color: Colors.white.withValues(alpha: 0.2),
|
||||
child: AnimatedOpacity(
|
||||
opacity: _isTouching ? 0.0 : 1.0,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
child: Text(
|
||||
l10n.endingHoldToSpeedUp,
|
||||
style: TextStyle(
|
||||
fontFamily: 'PressStart2P',
|
||||
fontSize: 8,
|
||||
color: Colors.white.withValues(alpha: 0.3),
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// 속도업 표시 (터치 중에만 표시)
|
||||
if (!_isScrollComplete && _isTouching)
|
||||
Positioned(
|
||||
top: 16,
|
||||
right: 16,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
vertical: 4,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: gold.withValues(alpha: 0.2),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: Text(
|
||||
'▶▶ x5',
|
||||
style: TextStyle(
|
||||
fontFamily: 'PressStart2P',
|
||||
fontSize: 10,
|
||||
color: gold,
|
||||
),
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -162,6 +239,15 @@ class _VictoryOverlayState extends State<VictoryOverlay>
|
||||
);
|
||||
}
|
||||
|
||||
/// 수동 스크롤 가능한 크레딧 (스크롤 완료 후)
|
||||
Widget _buildManualScrollableCredits(BuildContext context) {
|
||||
return SingleChildScrollView(
|
||||
controller: _manualScrollController,
|
||||
physics: const BouncingScrollPhysics(),
|
||||
child: _buildCreditContent(context),
|
||||
);
|
||||
}
|
||||
|
||||
double _estimateContentHeight() {
|
||||
// 대략적인 콘텐츠 높이 추정 (스크롤 계산용)
|
||||
// 명예의 전당 버튼 추가로 인해 높이 증가
|
||||
|
||||
Reference in New Issue
Block a user