fix: 출시 전 검수 이슈 4건 수정
- save_data: JSON 캐스팅 시 null 안전 처리 (손상된 세이브 크래시 방지) - settings_repository: _prefs! 강제 언래핑 제거, _getPrefs() 패턴 적용 - game_session_controller: IAP 구매 상태를 MonetizationState에 동기화 - iap_service: InAppPurchase.instance를 lazy 초기화로 변경
This commit is contained in:
@@ -68,7 +68,7 @@ class IAPService {
|
||||
// 상태
|
||||
// ===========================================================================
|
||||
|
||||
final InAppPurchase _iap = InAppPurchase.instance;
|
||||
late final InAppPurchase _iap = InAppPurchase.instance;
|
||||
|
||||
bool _isInitialized = false;
|
||||
bool _isAvailable = false;
|
||||
|
||||
@@ -148,11 +148,16 @@ class GameSave {
|
||||
}
|
||||
|
||||
static GameSave fromJson(Map<String, dynamic> json) {
|
||||
final traitsJson = json['traits'] as Map<String, dynamic>;
|
||||
final statsJson = json['stats'] as Map<String, dynamic>;
|
||||
final inventoryJson = json['inventory'] as Map<String, dynamic>;
|
||||
final equipmentJson = json['equipment'] as Map<String, dynamic>;
|
||||
final progressJson = json['progress'] as Map<String, dynamic>;
|
||||
final traitsJson =
|
||||
json['traits'] as Map<String, dynamic>? ?? <String, dynamic>{};
|
||||
final statsJson =
|
||||
json['stats'] as Map<String, dynamic>? ?? <String, dynamic>{};
|
||||
final inventoryJson =
|
||||
json['inventory'] as Map<String, dynamic>? ?? <String, dynamic>{};
|
||||
final equipmentJson =
|
||||
json['equipment'] as Map<String, dynamic>? ?? <String, dynamic>{};
|
||||
final progressJson =
|
||||
json['progress'] as Map<String, dynamic>? ?? <String, dynamic>{};
|
||||
final queueJson = (json['queue'] as List<dynamic>? ?? []).cast<dynamic>();
|
||||
final skillsJson = (json['skills'] as List<dynamic>? ?? []).cast<dynamic>();
|
||||
|
||||
|
||||
@@ -12,55 +12,56 @@ class SettingsRepository {
|
||||
SharedPreferences? _prefs;
|
||||
|
||||
/// SharedPreferences 초기화
|
||||
Future<void> init() async {
|
||||
Future<SharedPreferences> _getPrefs() async {
|
||||
_prefs ??= await SharedPreferences.getInstance();
|
||||
return _prefs!;
|
||||
}
|
||||
|
||||
/// 언어 설정 저장
|
||||
Future<void> saveLocale(String locale) async {
|
||||
await init();
|
||||
await _prefs!.setString(_keyLocale, locale);
|
||||
final prefs = await _getPrefs();
|
||||
await prefs.setString(_keyLocale, locale);
|
||||
}
|
||||
|
||||
/// 언어 설정 불러오기
|
||||
Future<String?> loadLocale() async {
|
||||
await init();
|
||||
return _prefs!.getString(_keyLocale);
|
||||
final prefs = await _getPrefs();
|
||||
return prefs.getString(_keyLocale);
|
||||
}
|
||||
|
||||
/// BGM 볼륨 저장 (0.0 ~ 1.0)
|
||||
Future<void> saveBgmVolume(double volume) async {
|
||||
await init();
|
||||
await _prefs!.setDouble(_keyBgmVolume, volume.clamp(0.0, 1.0));
|
||||
final prefs = await _getPrefs();
|
||||
await prefs.setDouble(_keyBgmVolume, volume.clamp(0.0, 1.0));
|
||||
}
|
||||
|
||||
/// BGM 볼륨 불러오기 (기본값: 0.7)
|
||||
Future<double> loadBgmVolume() async {
|
||||
await init();
|
||||
return _prefs!.getDouble(_keyBgmVolume) ?? 0.7;
|
||||
final prefs = await _getPrefs();
|
||||
return prefs.getDouble(_keyBgmVolume) ?? 0.7;
|
||||
}
|
||||
|
||||
/// SFX 볼륨 저장 (0.0 ~ 1.0)
|
||||
Future<void> saveSfxVolume(double volume) async {
|
||||
await init();
|
||||
await _prefs!.setDouble(_keySfxVolume, volume.clamp(0.0, 1.0));
|
||||
final prefs = await _getPrefs();
|
||||
await prefs.setDouble(_keySfxVolume, volume.clamp(0.0, 1.0));
|
||||
}
|
||||
|
||||
/// SFX 볼륨 불러오기 (기본값: 0.8)
|
||||
Future<double> loadSfxVolume() async {
|
||||
await init();
|
||||
return _prefs!.getDouble(_keySfxVolume) ?? 0.8;
|
||||
final prefs = await _getPrefs();
|
||||
return prefs.getDouble(_keySfxVolume) ?? 0.8;
|
||||
}
|
||||
|
||||
/// 애니메이션 속도 저장 (0.5 ~ 2.0, 1.0이 기본)
|
||||
Future<void> saveAnimationSpeed(double speed) async {
|
||||
await init();
|
||||
await _prefs!.setDouble(_keyAnimationSpeed, speed.clamp(0.5, 2.0));
|
||||
final prefs = await _getPrefs();
|
||||
await prefs.setDouble(_keyAnimationSpeed, speed.clamp(0.5, 2.0));
|
||||
}
|
||||
|
||||
/// 애니메이션 속도 불러오기 (기본값: 1.0)
|
||||
Future<double> loadAnimationSpeed() async {
|
||||
await init();
|
||||
return _prefs!.getDouble(_keyAnimationSpeed) ?? 1.0;
|
||||
final prefs = await _getPrefs();
|
||||
return prefs.getDouble(_keyAnimationSpeed) ?? 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:asciineverdie/src/core/engine/iap_service.dart';
|
||||
import 'package:asciineverdie/src/core/engine/progress_loop.dart';
|
||||
import 'package:asciineverdie/src/core/engine/progress_service.dart';
|
||||
import 'package:asciineverdie/src/core/model/game_state.dart';
|
||||
@@ -173,6 +174,17 @@ class GameSessionController extends ChangeNotifier {
|
||||
_status = GameSessionStatus.running;
|
||||
_cheatsEnabled = cheatsEnabled;
|
||||
|
||||
// IAP 구매 상태를 MonetizationState에 동기화
|
||||
// (테스트 환경에서는 InAppPurchase 플랫폼 채널 미등록으로 예외 발생 가능)
|
||||
try {
|
||||
final isPaid = IAPService.instance.isAdRemovalPurchased;
|
||||
if (_monetization.adRemovalPurchased != isPaid) {
|
||||
_monetization = _monetization.copyWith(adRemovalPurchased: isPaid);
|
||||
}
|
||||
} catch (_) {
|
||||
// 비모바일 플랫폼 또는 테스트 환경에서는 무시
|
||||
}
|
||||
|
||||
// 통계 초기화
|
||||
if (isNewGame) {
|
||||
await _statisticsManager.initializeForNewGame();
|
||||
@@ -273,8 +285,16 @@ class GameSessionController extends ChangeNotifier {
|
||||
return;
|
||||
}
|
||||
|
||||
// 저장된 수익화 상태 복원
|
||||
// 저장된 수익화(monetization) 상태 복원, IAP 구매 상태 동기화
|
||||
_monetization = savedMonetization ?? MonetizationState.initial();
|
||||
try {
|
||||
final isPaid = IAPService.instance.isAdRemovalPurchased;
|
||||
if (_monetization.adRemovalPurchased != isPaid) {
|
||||
_monetization = _monetization.copyWith(adRemovalPurchased: isPaid);
|
||||
}
|
||||
} catch (_) {
|
||||
// 비모바일 플랫폼 또는 테스트 환경에서는 무시
|
||||
}
|
||||
|
||||
// 복귀 보상 체크 (Phase 7)
|
||||
_returnRewardsManager.checkReturnRewards(
|
||||
|
||||
Reference in New Issue
Block a user