Compare commits
2 Commits
94c2ed1ca1
...
d90543dd86
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d90543dd86 | ||
|
|
03ff9c1ce8 |
@@ -289,11 +289,14 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
||||
}
|
||||
|
||||
// 모바일: 앱이 포그라운드로 돌아올 때 전체 재로드
|
||||
// (광고 표시 중에는 reload 건너뛰기 - 배속 부스트 등 상태 유지)
|
||||
if (appState == AppLifecycleState.resumed && isMobile) {
|
||||
_audioController.resumeAll();
|
||||
if (!widget.controller.isShowingAd) {
|
||||
_reloadGameScreen();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 모바일 재진입 시 전체 화면 재로드
|
||||
Future<void> _reloadGameScreen() async {
|
||||
@@ -659,6 +662,7 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
||||
speedBoostEndMs: widget.controller.monetization.speedBoostEndMs,
|
||||
isPaidUser: widget.controller.monetization.isPaidUser,
|
||||
onSpeedBoostActivate: _handleSpeedBoost,
|
||||
isSpeedBoostActive: widget.controller.isSpeedBoostActive,
|
||||
adSpeedMultiplier: widget.controller.adSpeedMultiplier,
|
||||
has2xUnlocked: widget.controller.has2xUnlocked,
|
||||
),
|
||||
|
||||
@@ -66,6 +66,9 @@ class GameSessionController extends ChangeNotifier {
|
||||
int _speedBoostRemainingSeconds = 0;
|
||||
static const int _speedBoostDuration = 300; // 5분
|
||||
|
||||
// 광고 표시 중 플래그 (lifecycle reload 방지용)
|
||||
bool _isShowingAd = false;
|
||||
|
||||
/// 광고 배속 배율 (릴리즈: 5x, 디버그빌드+디버그모드: 20x)
|
||||
int get _speedBoostMultiplier => (kDebugMode && _cheatsEnabled) ? 20 : 5;
|
||||
|
||||
@@ -153,8 +156,16 @@ class GameSessionController extends ChangeNotifier {
|
||||
// 명예의 전당 체크 → 가용 배속 결정
|
||||
final availableSpeeds = await _getAvailableSpeeds();
|
||||
|
||||
// 새 게임이면 1배속, 재개/부활이면 기존 배속 유지
|
||||
final initialSpeed = isNewGame ? 1 : previousSpeed;
|
||||
// 명예의 전당 해금 시 기본 2배속, 아니면 1배속
|
||||
final hasHallOfFame = availableSpeeds.contains(2);
|
||||
// 새 게임이면 기본 배속, 세이브 로드 시 명예의 전당 해금 시 최소 2배속 보장
|
||||
final int initialSpeed;
|
||||
if (isNewGame) {
|
||||
initialSpeed = hasHallOfFame ? 2 : 1;
|
||||
} else {
|
||||
// 세이브 로드: 명예의 전당 해금 시 최소 2배속
|
||||
initialSpeed = (hasHallOfFame && previousSpeed < 2) ? 2 : previousSpeed;
|
||||
}
|
||||
|
||||
_loop = ProgressLoop(
|
||||
initialState: state,
|
||||
@@ -593,6 +604,9 @@ class GameSessionController extends ChangeNotifier {
|
||||
/// 속도 부스트 활성화 여부
|
||||
bool get isSpeedBoostActive => _isSpeedBoostActive;
|
||||
|
||||
/// 광고 표시 중 여부 (lifecycle reload 방지용)
|
||||
bool get isShowingAd => _isShowingAd;
|
||||
|
||||
/// 속도 부스트 남은 시간 (초)
|
||||
int get speedBoostRemainingSeconds => _speedBoostRemainingSeconds;
|
||||
|
||||
@@ -626,6 +640,8 @@ class GameSessionController extends ChangeNotifier {
|
||||
|
||||
// 무료 유저는 인터스티셜 광고 필요
|
||||
bool activated = false;
|
||||
_isShowingAd = true; // 광고 표시 시작 (lifecycle reload 방지)
|
||||
|
||||
final adResult = await AdService.instance.showInterstitialAd(
|
||||
adType: AdType.interstitialSpeed,
|
||||
onComplete: () {
|
||||
@@ -634,6 +650,8 @@ class GameSessionController extends ChangeNotifier {
|
||||
},
|
||||
);
|
||||
|
||||
_isShowingAd = false; // 광고 표시 종료
|
||||
|
||||
if (adResult == AdResult.completed || adResult == AdResult.debugSkipped) {
|
||||
debugPrint('[GameSession] Speed boost activated (free user with ad)');
|
||||
return activated;
|
||||
@@ -690,6 +708,7 @@ class GameSessionController extends ChangeNotifier {
|
||||
if (_loop != null) {
|
||||
_getAvailableSpeeds().then((speeds) {
|
||||
_loop!.updateAvailableSpeeds(speeds);
|
||||
_loop!.setSpeed(_savedSpeedMultiplier);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -825,6 +844,7 @@ class GameSessionController extends ChangeNotifier {
|
||||
}
|
||||
|
||||
_state = updatedState;
|
||||
_loop?.replaceState(updatedState); // ProgressLoop 상태도 업데이트
|
||||
|
||||
// 저장
|
||||
unawaited(saveManager.saveState(
|
||||
|
||||
@@ -55,6 +55,7 @@ class MobileCarouselLayout extends StatefulWidget {
|
||||
this.speedBoostEndMs,
|
||||
this.isPaidUser = false,
|
||||
this.onSpeedBoostActivate,
|
||||
this.isSpeedBoostActive = false,
|
||||
this.onSetSpeed,
|
||||
this.adSpeedMultiplier = 5,
|
||||
this.has2xUnlocked = false,
|
||||
@@ -121,6 +122,9 @@ class MobileCarouselLayout extends StatefulWidget {
|
||||
/// 광고 배속 활성화 콜백 (광고 시청)
|
||||
final VoidCallback? onSpeedBoostActivate;
|
||||
|
||||
/// 배속 부스트 활성화 여부
|
||||
final bool isSpeedBoostActive;
|
||||
|
||||
/// 광고 배속 배율 (릴리즈: 5x, 디버그빌드+디버그모드: 20x)
|
||||
final int adSpeedMultiplier;
|
||||
|
||||
@@ -541,59 +545,26 @@ class _MobileCarouselLayoutState extends State<MobileCarouselLayout> {
|
||||
}
|
||||
|
||||
/// 레트로 스타일 속도 선택기
|
||||
///
|
||||
/// - 5x/20x 토글 버튼 하나만 표시
|
||||
/// - 부스트 활성화 중: 반투명, 비활성 (누를 수 없음)
|
||||
/// - 부스트 비활성화: 불투명, 활성 (누를 수 있음)
|
||||
Widget _buildRetroSpeedSelector(BuildContext context) {
|
||||
final currentElapsedMs = widget.state.skillSystem.elapsedMs;
|
||||
final speedBoostEndMs = widget.speedBoostEndMs ?? 0;
|
||||
final isSpeedBoostActive =
|
||||
speedBoostEndMs > currentElapsedMs || widget.isPaidUser;
|
||||
final isSpeedBoostActive = widget.isSpeedBoostActive;
|
||||
final adSpeed = widget.adSpeedMultiplier;
|
||||
|
||||
void setSpeed(int speed) {
|
||||
if (widget.onSetSpeed != null) {
|
||||
widget.onSetSpeed!(speed);
|
||||
} else {
|
||||
widget.onSpeedCycle();
|
||||
}
|
||||
}
|
||||
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// 1x 버튼
|
||||
_RetroSpeedChip(
|
||||
speed: 1,
|
||||
isSelected: widget.speedMultiplier == 1 && !isSpeedBoostActive,
|
||||
onTap: () {
|
||||
setSpeed(1);
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
// 2x 버튼 (명예의 전당 해금 시)
|
||||
if (widget.has2xUnlocked) ...[
|
||||
const SizedBox(width: 4),
|
||||
_RetroSpeedChip(
|
||||
speed: 2,
|
||||
isSelected: widget.speedMultiplier == 2 && !isSpeedBoostActive,
|
||||
onTap: () {
|
||||
setSpeed(2);
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
],
|
||||
const SizedBox(width: 4),
|
||||
// 광고배속 버튼
|
||||
_RetroSpeedChip(
|
||||
return _RetroSpeedChip(
|
||||
speed: adSpeed,
|
||||
isSelected: isSpeedBoostActive,
|
||||
isAdBased: !isSpeedBoostActive && !widget.isPaidUser,
|
||||
// 부스트 활성화 중이면 비활성 (반투명)
|
||||
isDisabled: isSpeedBoostActive,
|
||||
onTap: () {
|
||||
if (!isSpeedBoostActive) {
|
||||
widget.onSpeedBoostActivate?.call();
|
||||
}
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -653,6 +624,7 @@ class _MobileCarouselLayoutState extends State<MobileCarouselLayout> {
|
||||
speedBoostEndMs: widget.speedBoostEndMs,
|
||||
isPaidUser: widget.isPaidUser,
|
||||
onSpeedBoostActivate: widget.onSpeedBoostActivate,
|
||||
isSpeedBoostActive: widget.isSpeedBoostActive,
|
||||
adSpeedMultiplier: widget.adSpeedMultiplier,
|
||||
),
|
||||
|
||||
@@ -835,6 +807,7 @@ class _RetroSpeedChip extends StatelessWidget {
|
||||
required this.isSelected,
|
||||
required this.onTap,
|
||||
this.isAdBased = false,
|
||||
this.isDisabled = false,
|
||||
});
|
||||
|
||||
final int speed;
|
||||
@@ -842,32 +815,41 @@ class _RetroSpeedChip extends StatelessWidget {
|
||||
final VoidCallback onTap;
|
||||
final bool isAdBased;
|
||||
|
||||
/// 비활성 상태 (반투명, 탭 무시)
|
||||
final bool isDisabled;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final gold = RetroColors.goldOf(context);
|
||||
final warning = RetroColors.warningOf(context);
|
||||
final border = RetroColors.borderOf(context);
|
||||
|
||||
// 비활성 상태면 반투명 처리
|
||||
final opacity = isDisabled ? 0.4 : 1.0;
|
||||
|
||||
final Color bgColor;
|
||||
final Color textColor;
|
||||
final Color borderColor;
|
||||
|
||||
if (isSelected) {
|
||||
bgColor = isAdBased ? warning.withValues(alpha: 0.3) : gold.withValues(alpha: 0.3);
|
||||
textColor = isAdBased ? warning : gold;
|
||||
borderColor = isAdBased ? warning : gold;
|
||||
bgColor = isAdBased
|
||||
? warning.withValues(alpha: 0.3 * opacity)
|
||||
: gold.withValues(alpha: 0.3 * opacity);
|
||||
textColor = (isAdBased ? warning : gold).withValues(alpha: opacity);
|
||||
borderColor = (isAdBased ? warning : gold).withValues(alpha: opacity);
|
||||
} else if (isAdBased) {
|
||||
bgColor = Colors.transparent;
|
||||
textColor = warning;
|
||||
borderColor = warning;
|
||||
textColor = warning.withValues(alpha: opacity);
|
||||
borderColor = warning.withValues(alpha: opacity);
|
||||
} else {
|
||||
bgColor = Colors.transparent;
|
||||
textColor = RetroColors.textMutedOf(context);
|
||||
borderColor = border;
|
||||
textColor = RetroColors.textMutedOf(context).withValues(alpha: opacity);
|
||||
borderColor = border.withValues(alpha: opacity);
|
||||
}
|
||||
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
// 비활성 상태면 탭 무시
|
||||
onTap: isDisabled ? null : onTap,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
@@ -877,7 +859,7 @@ class _RetroSpeedChip extends StatelessWidget {
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (isAdBased && !isSelected)
|
||||
if (isAdBased && !isSelected && !isDisabled)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 2),
|
||||
child: Text(
|
||||
|
||||
@@ -41,6 +41,7 @@ class EnhancedAnimationPanel extends StatefulWidget {
|
||||
this.speedBoostEndMs,
|
||||
this.isPaidUser = false,
|
||||
this.onSpeedBoostActivate,
|
||||
this.isSpeedBoostActive = false,
|
||||
this.adSpeedMultiplier = 5,
|
||||
});
|
||||
|
||||
@@ -82,6 +83,9 @@ class EnhancedAnimationPanel extends StatefulWidget {
|
||||
/// 광고 배속 활성화 콜백 (광고 시청)
|
||||
final VoidCallback? onSpeedBoostActivate;
|
||||
|
||||
/// 배속 부스트 활성화 여부
|
||||
final bool isSpeedBoostActive;
|
||||
|
||||
/// 광고 배속 배율 (릴리즈: 5x, 디버그빌드+디버그모드: 20x)
|
||||
final int adSpeedMultiplier;
|
||||
|
||||
@@ -676,83 +680,53 @@ class _EnhancedAnimationPanelState extends State<EnhancedAnimationPanel>
|
||||
|
||||
/// 속도 컨트롤 버튼 (태스크 프로그레스 바 우측)
|
||||
///
|
||||
/// - 일반배속: 1x (기본) ↔ 2x (명예의 전당 해금)
|
||||
/// - 광고배속: 릴리즈 5x, 디버그빌드+디버그모드 20x
|
||||
/// - 5x/20x 토글 버튼 하나만 표시
|
||||
/// - 부스트 활성화 중: 반투명, 비활성 (누를 수 없음)
|
||||
/// - 부스트 비활성화: 불투명, 활성 (누를 수 있음)
|
||||
Widget _buildSpeedControls() {
|
||||
final isSpeedBoostActive = _speedBoostRemainingMs > 0 || widget.isPaidUser;
|
||||
final isSpeedBoostActive = widget.isSpeedBoostActive;
|
||||
final adSpeed = widget.adSpeedMultiplier;
|
||||
// 2x일 때 광고 버튼 표시 (버프 비활성이고 무료유저)
|
||||
final showAdButton =
|
||||
widget.speedMultiplier == 2 && !isSpeedBoostActive && !widget.isPaidUser;
|
||||
final isPaidUser = widget.isPaidUser;
|
||||
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// 속도 사이클 버튼 (1x ↔ 2x, 버프 활성시 광고배속)
|
||||
SizedBox(
|
||||
width: 44,
|
||||
height: 32,
|
||||
child: OutlinedButton(
|
||||
onPressed: widget.onSpeedCycle,
|
||||
style: OutlinedButton.styleFrom(
|
||||
padding: EdgeInsets.zero,
|
||||
side: BorderSide(
|
||||
color: isSpeedBoostActive
|
||||
? Colors.orange
|
||||
: widget.speedMultiplier > 1
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Theme.of(context).colorScheme.outline,
|
||||
width: isSpeedBoostActive ? 2 : 1,
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
isSpeedBoostActive ? '${adSpeed}x' : '${widget.speedMultiplier}x',
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: isSpeedBoostActive
|
||||
? Colors.orange
|
||||
: widget.speedMultiplier > 1
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: null,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
// 광고 배속 버튼 (2x일 때만 표시)
|
||||
if (showAdButton) ...[
|
||||
const SizedBox(width: 4),
|
||||
SizedBox(
|
||||
// 부스트 활성화 중이면 반투명
|
||||
final opacity = isSpeedBoostActive ? 0.4 : 1.0;
|
||||
|
||||
return SizedBox(
|
||||
width: 52,
|
||||
height: 32,
|
||||
child: OutlinedButton(
|
||||
onPressed: widget.onSpeedBoostActivate,
|
||||
// 부스트 활성화 중이면 누를 수 없음
|
||||
onPressed: isSpeedBoostActive ? null : widget.onSpeedBoostActivate,
|
||||
style: OutlinedButton.styleFrom(
|
||||
padding: EdgeInsets.zero,
|
||||
side: const BorderSide(color: Colors.orange, width: 1.5),
|
||||
side: BorderSide(
|
||||
color: Colors.orange.withValues(alpha: opacity),
|
||||
width: isSpeedBoostActive ? 2 : 1.5,
|
||||
),
|
||||
disabledForegroundColor: Colors.orange.withValues(alpha: 0.4),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
// 무료유저 + 비활성 상태면 AD 아이콘 표시
|
||||
if (!isPaidUser && !isSpeedBoostActive) ...[
|
||||
const Text(
|
||||
'▶',
|
||||
style: TextStyle(fontSize: 9, color: Colors.orange),
|
||||
),
|
||||
const SizedBox(width: 2),
|
||||
],
|
||||
Text(
|
||||
'${adSpeed}x',
|
||||
style: const TextStyle(
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.orange,
|
||||
color: Colors.orange.withValues(alpha: opacity),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user