Compare commits
3 Commits
de20183b73
...
6c92a323c0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6c92a323c0 | ||
|
|
8efd3e875c | ||
|
|
01e26bb5f5 |
@@ -1792,7 +1792,7 @@ String get warningDeleteSave {
|
|||||||
|
|
||||||
String get copyrightText {
|
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" },
|
"@endingSkip": { "description": "Skip button" },
|
||||||
|
|
||||||
"endingTapToSkip": "TAP TO SKIP",
|
"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": "殿堂に記録されます",
|
"endingHallOfFameLine2": "殿堂に記録されます",
|
||||||
"endingHallOfFameButton": "殿堂入り",
|
"endingHallOfFameButton": "殿堂入り",
|
||||||
"endingSkip": "スキップ",
|
"endingSkip": "スキップ",
|
||||||
"endingTapToSkip": "タップでスキップ"
|
"endingTapToSkip": "タップでスキップ",
|
||||||
|
"endingHoldToSpeedUp": "長押しで高速スクロール"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,5 +92,6 @@
|
|||||||
"endingHallOfFameLine2": "명예의 전당에 기록됩니다",
|
"endingHallOfFameLine2": "명예의 전당에 기록됩니다",
|
||||||
"endingHallOfFameButton": "명예의 전당",
|
"endingHallOfFameButton": "명예의 전당",
|
||||||
"endingSkip": "건너뛰기",
|
"endingSkip": "건너뛰기",
|
||||||
"endingTapToSkip": "탭하여 건너뛰기"
|
"endingTapToSkip": "탭하여 건너뛰기",
|
||||||
|
"endingHoldToSpeedUp": "길게 누르면 빨리 스크롤"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -646,6 +646,12 @@ abstract class L10n {
|
|||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'TAP TO SKIP'**
|
/// **'TAP TO SKIP'**
|
||||||
String get endingTapToSkip;
|
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> {
|
class _L10nDelegate extends LocalizationsDelegate<L10n> {
|
||||||
|
|||||||
@@ -294,4 +294,7 @@ class L10nEn extends L10n {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get endingTapToSkip => 'TAP TO SKIP';
|
String get endingTapToSkip => 'TAP TO SKIP';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get endingHoldToSpeedUp => 'HOLD TO SPEED UP';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -294,4 +294,7 @@ class L10nJa extends L10n {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get endingTapToSkip => 'タップでスキップ';
|
String get endingTapToSkip => 'タップでスキップ';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get endingHoldToSpeedUp => '長押しで高速スクロール';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -294,4 +294,7 @@ class L10nKo extends L10n {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get endingTapToSkip => '탭하여 건너뛰기';
|
String get endingTapToSkip => '탭하여 건너뛰기';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get endingHoldToSpeedUp => '길게 누르면 빨리 스크롤';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -294,4 +294,7 @@ class L10nZh extends L10n {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get endingTapToSkip => '点击跳过';
|
String get endingTapToSkip => '点击跳过';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get endingHoldToSpeedUp => '长按加速滚动';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,5 +92,6 @@
|
|||||||
"endingHallOfFameLine2": "将被铭记于荣誉殿堂",
|
"endingHallOfFameLine2": "将被铭记于荣誉殿堂",
|
||||||
"endingHallOfFameButton": "荣誉殿堂",
|
"endingHallOfFameButton": "荣誉殿堂",
|
||||||
"endingSkip": "跳过",
|
"endingSkip": "跳过",
|
||||||
"endingTapToSkip": "点击跳过"
|
"endingTapToSkip": "点击跳过",
|
||||||
|
"endingHoldToSpeedUp": "长按加速滚动"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,109 +34,186 @@ class VictoryOverlay extends StatefulWidget {
|
|||||||
|
|
||||||
class _VictoryOverlayState extends State<VictoryOverlay>
|
class _VictoryOverlayState extends State<VictoryOverlay>
|
||||||
with SingleTickerProviderStateMixin {
|
with SingleTickerProviderStateMixin {
|
||||||
late AnimationController _scrollController;
|
late AnimationController _animationController;
|
||||||
late Animation<double> _scrollAnimation;
|
late Animation<double> _scrollAnimation;
|
||||||
|
|
||||||
// 스크롤이 완료(최하단 도달) 되었는지 여부
|
// 스크롤이 완료(최하단 도달) 되었는지 여부
|
||||||
bool _isScrollComplete = false;
|
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
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
|
||||||
_scrollController = AnimationController(
|
_manualScrollController = ScrollController();
|
||||||
|
|
||||||
|
_animationController = AnimationController(
|
||||||
duration: const Duration(milliseconds: _scrollDurationMs),
|
duration: const Duration(milliseconds: _scrollDurationMs),
|
||||||
vsync: this,
|
vsync: this,
|
||||||
);
|
);
|
||||||
|
|
||||||
_scrollAnimation = Tween<double>(
|
_scrollAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
|
||||||
begin: 0.0,
|
CurvedAnimation(parent: _animationController, curve: Curves.linear),
|
||||||
end: 1.0,
|
);
|
||||||
).animate(CurvedAnimation(parent: _scrollController, curve: Curves.linear));
|
|
||||||
|
|
||||||
// 스크롤 완료 시 버튼 표시 (자동 종료하지 않음)
|
// 스크롤 완료 시 수동 스크롤 모드로 전환
|
||||||
_scrollController.addStatusListener((status) {
|
_animationController.addStatusListener((status) {
|
||||||
if (status == AnimationStatus.completed) {
|
if (status == AnimationStatus.completed) {
|
||||||
setState(() {
|
_onScrollComplete();
|
||||||
_isScrollComplete = true;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
_scrollController.forward();
|
_animationController.forward();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_scrollController.dispose();
|
_animationController.dispose();
|
||||||
|
_manualScrollController.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 탭 시 스크롤 최하단으로 즉시 이동
|
/// 스크롤 완료 시 호출
|
||||||
void _skipToEnd() {
|
void _onScrollComplete() {
|
||||||
_scrollController.stop();
|
|
||||||
_scrollController.value = 1.0;
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_isScrollComplete = true;
|
_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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final gold = RetroColors.goldOf(context);
|
final gold = RetroColors.goldOf(context);
|
||||||
|
final l10n = L10n.of(context);
|
||||||
|
|
||||||
return GestureDetector(
|
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(
|
child: Material(
|
||||||
color: Colors.black,
|
color: Colors.black,
|
||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
// 스크롤되는 크레딧
|
// 스크롤 중: 애니메이션 기반 스크롤
|
||||||
|
// 스크롤 완료: 수동 스크롤 가능
|
||||||
|
if (!_isScrollComplete)
|
||||||
AnimatedBuilder(
|
AnimatedBuilder(
|
||||||
animation: _scrollAnimation,
|
animation: _scrollAnimation,
|
||||||
builder: (context, child) {
|
builder: (context, child) {
|
||||||
return _buildScrollingCredits(context);
|
return _buildScrollingCredits(context);
|
||||||
},
|
},
|
||||||
),
|
)
|
||||||
|
else
|
||||||
|
_buildManualScrollableCredits(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),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
// 하단 탭 힌트 (스크롤 중에만 표시)
|
|
||||||
if (!_isScrollComplete)
|
if (!_isScrollComplete)
|
||||||
Positioned(
|
Positioned(
|
||||||
bottom: 16,
|
bottom: 16,
|
||||||
left: 0,
|
left: 0,
|
||||||
right: 0,
|
right: 0,
|
||||||
|
child: AnimatedOpacity(
|
||||||
|
opacity: _isTouching ? 0.0 : 1.0,
|
||||||
|
duration: const Duration(milliseconds: 200),
|
||||||
child: Text(
|
child: Text(
|
||||||
L10n.of(context).endingTapToSkip,
|
l10n.endingHoldToSpeedUp,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontFamily: 'PressStart2P',
|
fontFamily: 'PressStart2P',
|
||||||
fontSize: 8,
|
fontSize: 8,
|
||||||
color: Colors.white.withValues(alpha: 0.2),
|
color: Colors.white.withValues(alpha: 0.3),
|
||||||
),
|
),
|
||||||
textAlign: TextAlign.center,
|
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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -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() {
|
double _estimateContentHeight() {
|
||||||
// 대략적인 콘텐츠 높이 추정 (스크롤 계산용)
|
// 대략적인 콘텐츠 높이 추정 (스크롤 계산용)
|
||||||
// 명예의 전당 버튼 추가로 인해 높이 증가
|
// 명예의 전당 버튼 추가로 인해 높이 증가
|
||||||
|
|||||||
Reference in New Issue
Block a user