diff --git a/lib/src/features/game/layouts/mobile_carousel_layout.dart b/lib/src/features/game/layouts/mobile_carousel_layout.dart index f24aa5b..12a27b7 100644 --- a/lib/src/features/game/layouts/mobile_carousel_layout.dart +++ b/lib/src/features/game/layouts/mobile_carousel_layout.dart @@ -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 { } /// 레트로 스타일 속도 선택기 + /// + /// - 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( - speed: adSpeed, - isSelected: isSpeedBoostActive, - isAdBased: !isSpeedBoostActive && !widget.isPaidUser, - onTap: () { - if (!isSpeedBoostActive) { - widget.onSpeedBoostActivate?.call(); - } - Navigator.pop(context); - }, - ), - ], + 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 { 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( diff --git a/lib/src/features/game/widgets/enhanced_animation_panel.dart b/lib/src/features/game/widgets/enhanced_animation_panel.dart index 7099c5f..665590b 100644 --- a/lib/src/features/game/widgets/enhanced_animation_panel.dart +++ b/lib/src/features/game/widgets/enhanced_animation_panel.dart @@ -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 /// 속도 컨트롤 버튼 (태스크 프로그레스 바 우측) /// - /// - 일반배속: 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, + // 부스트 활성화 중이면 반투명 + final opacity = isSpeedBoostActive ? 0.4 : 1.0; + + return SizedBox( + width: 52, + height: 32, + child: OutlinedButton( + // 부스트 활성화 중이면 누를 수 없음 + onPressed: isSpeedBoostActive ? null : widget.onSpeedBoostActivate, + style: OutlinedButton.styleFrom( + padding: EdgeInsets.zero, + 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), ), - ), - child: Text( - isSpeedBoostActive ? '${adSpeed}x' : '${widget.speedMultiplier}x', + const SizedBox(width: 2), + ], + Text( + '${adSpeed}x', style: TextStyle( fontSize: 13, fontWeight: FontWeight.bold, - color: isSpeedBoostActive - ? Colors.orange - : widget.speedMultiplier > 1 - ? Theme.of(context).colorScheme.primary - : null, + color: Colors.orange.withValues(alpha: opacity), ), ), - ), + ], ), - // 광고 배속 버튼 (2x일 때만 표시) - if (showAdButton) ...[ - const SizedBox(width: 4), - SizedBox( - width: 52, - height: 32, - child: OutlinedButton( - onPressed: widget.onSpeedBoostActivate, - style: OutlinedButton.styleFrom( - padding: EdgeInsets.zero, - side: const BorderSide(color: Colors.orange, width: 1.5), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text( - '▶', - style: TextStyle(fontSize: 9, color: Colors.orange), - ), - const SizedBox(width: 2), - Text( - '${adSpeed}x', - style: const TextStyle( - fontSize: 13, - fontWeight: FontWeight.bold, - color: Colors.orange, - ), - ), - ], - ), - ), - ), - ], - ], + ), ); }