feat(ui): 게임 화면 및 UI 컴포넌트 개선

- front_screen: 프론트 화면 UI 업데이트
- game_play_screen: 게임 플레이 화면 수정
- game_session_controller: 세션 관리 로직 개선
- mobile_carousel_layout: 모바일 캐러셀 레이아웃 개선
- enhanced_animation_panel: 애니메이션 패널 업데이트
- help_dialog: 도움말 다이얼로그 수정
- return_rewards_dialog: 복귀 보상 다이얼로그 개선
- new_character_screen: 새 캐릭터 화면 수정
- settings_screen: 설정 화면 업데이트
This commit is contained in:
JiWoong Sul
2026-01-19 15:50:35 +09:00
parent ffc19c7ca6
commit 19faa9ea39
9 changed files with 2495 additions and 1355 deletions

View File

@@ -14,6 +14,7 @@ import 'package:asciineverdie/src/core/model/game_state.dart';
import 'package:asciineverdie/src/core/model/game_statistics.dart';
import 'package:asciineverdie/src/core/model/hall_of_fame.dart';
import 'package:asciineverdie/src/core/model/monetization_state.dart';
import 'package:asciineverdie/src/core/model/treasure_chest.dart';
import 'package:asciineverdie/src/core/storage/hall_of_fame_storage.dart';
import 'package:asciineverdie/src/core/storage/save_manager.dart';
import 'package:asciineverdie/src/core/storage/statistics_storage.dart';
@@ -64,14 +65,16 @@ class GameSessionController extends ChangeNotifier {
Timer? _speedBoostTimer;
int _speedBoostRemainingSeconds = 0;
static const int _speedBoostDuration = 300; // 5분
static const int _speedBoostMultiplier = 5; // 5x 속도
/// 광고 배속 배율 (릴리즈: 5x, 디버그빌드+디버그모드: 20x)
int get _speedBoostMultiplier => (kDebugMode && _cheatsEnabled) ? 20 : 5;
// 복귀 보상 상태 (Phase 7)
MonetizationState _monetization = MonetizationState.initial();
ReturnReward? _pendingReturnReward;
ReturnChestReward? _pendingReturnReward;
/// 복귀 보상 콜백 (UI에서 다이얼로그 표시용)
void Function(ReturnReward reward)? onReturnRewardAvailable;
void Function(ReturnChestReward reward)? onReturnRewardAvailable;
// 통계 관련 필드
SessionStatistics _sessionStats = SessionStatistics.empty();
@@ -105,6 +108,12 @@ class GameSessionController extends ChangeNotifier {
/// 현재 ProgressLoop 인스턴스 (치트 기능용)
ProgressLoop? get loop => _loop;
/// 광고 배속 배율 (릴리즈: 5x, 디버그빌드+디버그모드: 20x)
int get adSpeedMultiplier => _speedBoostMultiplier;
/// 2x 배속 해금 여부 (명예의 전당에 캐릭터가 있으면 true)
bool get has2xUnlocked => _loop?.availableSpeeds.contains(2) ?? false;
Future<void> startNew(
GameState initialState, {
bool cheatsEnabled = false,
@@ -172,13 +181,16 @@ class GameSessionController extends ChangeNotifier {
}
/// 가용 배속 목록 반환
/// - 디버그 모드(치트 활성화): [1, 2, 20] (터보 모드 포함)
/// - 일반 모드: [1, 2] (5x는 광고 버프로만 활성화)
///
/// - 기본: [1] (1x만)
/// - 명예의 전당에 캐릭터 있으면: [1, 2] (2x 해금)
/// - 광고 배속(5x/20x)은 별도 버프로만 활성화
Future<List<int>> _getAvailableSpeeds() async {
if (_cheatsEnabled) {
return [1, 2, 20];
final hallOfFame = await _hallOfFameStorage.load();
if (hallOfFame.entries.isNotEmpty) {
return [1, 2]; // 명예의 전당 캐릭터 있으면 2x 해금
}
return [1, 2];
return [1]; // 기본: 1x만
}
/// 이전 값 초기화 (통계 변화 추적용)
@@ -700,7 +712,7 @@ class GameSessionController extends ChangeNotifier {
MonetizationState get monetization => _monetization;
/// 대기 중인 복귀 보상
ReturnReward? get pendingReturnReward => _pendingReturnReward;
ReturnChestReward? get pendingReturnReward => _pendingReturnReward;
/// 복귀 보상 체크 (로드 시 호출)
void _checkReturnRewards(GameState loaded) {
@@ -715,17 +727,17 @@ class GameSessionController extends ChangeNotifier {
final reward = rewardsService.calculateReward(
lastPlayTime: lastPlayTime,
currentTime: DateTime.now(),
playerLevel: loaded.traits.level,
isPaidUser: _monetization.isPaidUser,
);
if (reward.hasReward) {
_pendingReturnReward = reward;
debugPrint('[ReturnRewards] Reward available: ${reward.goldReward} gold, '
debugPrint('[ReturnRewards] Reward available: ${reward.chestCount} chests, '
'${reward.hoursAway} hours away');
// UI에서 다이얼로그 표시를 위해 콜백 호출
// startNew 후에 호출하도록 딜레이
Future.delayed(const Duration(milliseconds: 500), () {
Future<void>.delayed(const Duration(milliseconds: 500), () {
if (_pendingReturnReward != null) {
onReturnRewardAvailable?.call(_pendingReturnReward!);
}
@@ -733,23 +745,86 @@ class GameSessionController extends ChangeNotifier {
}
}
/// 복귀 보상 수령 완료 (골드 적용)
/// 복귀 보상 수령 완료 (상자 보상 적용)
///
/// [totalGold] 수령한 총 골드 (기본 + 보너스)
void applyReturnReward(int totalGold) {
/// [rewards] 오픈된 상자 보상 목록
void applyReturnReward(List<ChestReward> rewards) {
if (_state == null) return;
if (totalGold <= 0) {
if (rewards.isEmpty) {
// 보상 없이 건너뛴 경우
_pendingReturnReward = null;
debugPrint('[ReturnRewards] Reward skipped');
return;
}
// 골드 추가
final updatedInventory = _state!.inventory.copyWith(
gold: _state!.inventory.gold + totalGold,
);
_state = _state!.copyWith(inventory: updatedInventory);
var updatedState = _state!;
// 보상 적용
for (final reward in rewards) {
switch (reward.type) {
case ChestRewardType.equipment:
if (reward.equipment != null) {
// 현재 장비와 비교하여 더 좋으면 자동 장착
final slotIndex = reward.equipment!.slot.index;
final currentItem = updatedState.equipment.getItemByIndex(slotIndex);
if (currentItem.isEmpty ||
reward.equipment!.itemWeight > currentItem.itemWeight) {
updatedState = updatedState.copyWith(
equipment: updatedState.equipment.setItemByIndex(
slotIndex,
reward.equipment!,
),
);
debugPrint('[ReturnRewards] Equipped: ${reward.equipment!.name}');
} else {
// 더 좋지 않으면 판매 (골드로 변환)
final sellPrice =
(reward.equipment!.level * 50 * 0.3).round().clamp(1, 99999);
updatedState = updatedState.copyWith(
inventory: updatedState.inventory.copyWith(
gold: updatedState.inventory.gold + sellPrice,
),
);
debugPrint('[ReturnRewards] Sold: ${reward.equipment!.name} '
'for $sellPrice gold');
}
}
case ChestRewardType.potion:
if (reward.potionId != null) {
updatedState = updatedState.copyWith(
potionInventory: updatedState.potionInventory.addPotion(
reward.potionId!,
reward.potionCount ?? 1,
),
);
debugPrint('[ReturnRewards] Added potion: ${reward.potionId} '
'x${reward.potionCount}');
}
case ChestRewardType.gold:
if (reward.gold != null && reward.gold! > 0) {
updatedState = updatedState.copyWith(
inventory: updatedState.inventory.copyWith(
gold: updatedState.inventory.gold + reward.gold!,
),
);
debugPrint('[ReturnRewards] Added gold: ${reward.gold}');
}
case ChestRewardType.experience:
if (reward.experience != null && reward.experience! > 0) {
updatedState = updatedState.copyWith(
progress: updatedState.progress.copyWith(
exp: updatedState.progress.exp.copyWith(
position:
updatedState.progress.exp.position + reward.experience!,
),
),
);
debugPrint('[ReturnRewards] Added experience: ${reward.experience}');
}
}
}
_state = updatedState;
// 저장
unawaited(saveManager.saveState(
@@ -761,7 +836,7 @@ class GameSessionController extends ChangeNotifier {
_pendingReturnReward = null;
notifyListeners();
debugPrint('[ReturnRewards] Reward applied: $totalGold gold');
debugPrint('[ReturnRewards] Rewards applied: ${rewards.length} items');
}
/// 복귀 보상 건너뛰기