feat(arch): GetIt DI + IIAPService/IAdService 인터페이스 도입
- core/di/: service_locator, IIAPService, IAdService 생성 - IAPService/AdService: implements 인터페이스 + GetIt 위임 - main.dart: setupServiceLocator() 호출 - 기존 .instance getter 호환성 100% 유지 - test/helpers/test_setup.dart: 테스트용 서비스 로케이터 초기화
This commit is contained in:
@@ -1,6 +1,9 @@
|
||||
import 'package:asciineverdie/src/app.dart';
|
||||
import 'package:asciineverdie/src/core/di/service_locator.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
void main() {
|
||||
// 서비스 로케이터(service locator) 초기화
|
||||
setupServiceLocator();
|
||||
runApp(const AskiiNeverDieApp());
|
||||
}
|
||||
|
||||
31
lib/src/core/di/i_ad_service.dart
Normal file
31
lib/src/core/di/i_ad_service.dart
Normal file
@@ -0,0 +1,31 @@
|
||||
import 'package:asciineverdie/src/core/engine/ad_service.dart';
|
||||
|
||||
/// 광고 서비스 인터페이스 (interface)
|
||||
///
|
||||
/// 광고 표시 기능의 추상화 계층.
|
||||
/// 테스트 시 목(mock) 구현체로 교체 가능.
|
||||
abstract class IAdService {
|
||||
/// AdMob SDK 초기화
|
||||
Future<void> initialize();
|
||||
|
||||
/// 리워드 광고(rewarded ad) 준비 여부
|
||||
bool get isRewardedAdReady;
|
||||
|
||||
/// 인터스티셜 광고(interstitial ad) 준비 여부
|
||||
bool get isInterstitialAdReady;
|
||||
|
||||
/// 리워드 광고 표시
|
||||
Future<AdResult> showRewardedAd({
|
||||
required AdType adType,
|
||||
required void Function() onRewarded,
|
||||
});
|
||||
|
||||
/// 인터스티셜 광고 표시
|
||||
Future<AdResult> showInterstitialAd({
|
||||
required AdType adType,
|
||||
required void Function() onComplete,
|
||||
});
|
||||
|
||||
/// 리소스 해제
|
||||
void dispose();
|
||||
}
|
||||
34
lib/src/core/di/i_iap_service.dart
Normal file
34
lib/src/core/di/i_iap_service.dart
Normal file
@@ -0,0 +1,34 @@
|
||||
import 'package:asciineverdie/src/core/engine/iap_service.dart';
|
||||
|
||||
/// IAP 서비스 인터페이스 (interface)
|
||||
///
|
||||
/// 인앱 구매 기능의 추상화 계층.
|
||||
/// 테스트 시 목(mock) 구현체로 교체 가능.
|
||||
abstract class IIAPService {
|
||||
/// IAP 서비스 초기화
|
||||
Future<void> initialize();
|
||||
|
||||
/// 광고 제거 구매 여부
|
||||
bool get isAdRemovalPurchased;
|
||||
|
||||
/// 스토어 가용성(store availability)
|
||||
bool get isStoreAvailable;
|
||||
|
||||
/// 광고 제거 상품 가격 문자열
|
||||
String get removeAdsPrice;
|
||||
|
||||
/// 광고 제거 구매
|
||||
Future<IAPResult> purchaseRemoveAds();
|
||||
|
||||
/// 구매 복원(restore purchases)
|
||||
Future<IAPResult> restorePurchases();
|
||||
|
||||
/// 디버그 모드 IAP 시뮬레이션(debug simulation) 활성화 여부
|
||||
bool get debugIAPSimulated;
|
||||
|
||||
/// 디버그 모드 IAP 시뮬레이션 토글
|
||||
set debugIAPSimulated(bool value);
|
||||
|
||||
/// 리소스 해제
|
||||
void dispose();
|
||||
}
|
||||
21
lib/src/core/di/service_locator.dart
Normal file
21
lib/src/core/di/service_locator.dart
Normal file
@@ -0,0 +1,21 @@
|
||||
import 'package:get_it/get_it.dart';
|
||||
|
||||
import 'package:asciineverdie/src/core/di/i_ad_service.dart';
|
||||
import 'package:asciineverdie/src/core/di/i_iap_service.dart';
|
||||
import 'package:asciineverdie/src/core/engine/ad_service.dart';
|
||||
import 'package:asciineverdie/src/core/engine/iap_service.dart';
|
||||
|
||||
/// 전역 서비스 로케이터(service locator) 인스턴스
|
||||
final GetIt sl = GetIt.instance;
|
||||
|
||||
/// 서비스 로케이터 초기화
|
||||
///
|
||||
/// 앱 시작 시 한 번 호출하여 모든 서비스를 등록합니다.
|
||||
/// 점진적 도입: IAPService, AdService만 먼저 등록.
|
||||
void setupServiceLocator() {
|
||||
// IAP 서비스 (싱글톤 등록)
|
||||
sl.registerLazySingleton<IIAPService>(() => IAPService.createInstance());
|
||||
|
||||
// 광고 서비스 (싱글톤 등록)
|
||||
sl.registerLazySingleton<IAdService>(() => AdService.createInstance());
|
||||
}
|
||||
@@ -5,6 +5,9 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:google_mobile_ads/google_mobile_ads.dart';
|
||||
|
||||
import 'package:get_it/get_it.dart';
|
||||
|
||||
import 'package:asciineverdie/src/core/di/i_ad_service.dart';
|
||||
import 'package:asciineverdie/src/core/engine/iap_service.dart';
|
||||
|
||||
/// 광고 타입
|
||||
@@ -44,16 +47,14 @@ enum AdResult {
|
||||
///
|
||||
/// AdMob 리워드/인터스티셜 광고 로드 및 표시를 관리합니다.
|
||||
/// 디버그 모드에서는 광고 ON/OFF 토글이 가능합니다.
|
||||
class AdService {
|
||||
class AdService implements IAdService {
|
||||
AdService._();
|
||||
|
||||
static AdService? _instance;
|
||||
/// GetIt 등록용 팩토리 메서드
|
||||
factory AdService.createInstance() => AdService._();
|
||||
|
||||
/// 싱글톤 인스턴스
|
||||
static AdService get instance {
|
||||
_instance ??= AdService._();
|
||||
return _instance!;
|
||||
}
|
||||
/// 싱글톤 인스턴스 (GetIt 서비스 로케이터에 위임)
|
||||
static IAdService get instance => GetIt.instance<IAdService>();
|
||||
|
||||
// ===========================================================================
|
||||
// 광고 단위 ID
|
||||
@@ -126,6 +127,7 @@ class AdService {
|
||||
// ===========================================================================
|
||||
|
||||
/// AdMob SDK 초기화
|
||||
@override
|
||||
Future<void> initialize() async {
|
||||
if (_isInitialized) return;
|
||||
|
||||
@@ -197,6 +199,7 @@ class AdService {
|
||||
}
|
||||
|
||||
/// 리워드 광고 준비 여부
|
||||
@override
|
||||
bool get isRewardedAdReady => _rewardedAd != null || _shouldSkipAd;
|
||||
|
||||
/// 리워드 광고 표시
|
||||
@@ -204,6 +207,7 @@ class AdService {
|
||||
/// [adType] 광고 타입 (로깅용)
|
||||
/// [onRewarded] 보상 지급 콜백
|
||||
/// Returns: 광고 결과
|
||||
@override
|
||||
Future<AdResult> showRewardedAd({
|
||||
required AdType adType,
|
||||
required void Function() onRewarded,
|
||||
@@ -308,6 +312,7 @@ class AdService {
|
||||
}
|
||||
|
||||
/// 인터스티셜 광고 준비 여부
|
||||
@override
|
||||
bool get isInterstitialAdReady => _interstitialAd != null || _shouldSkipAd;
|
||||
|
||||
/// 인터스티셜 광고 표시
|
||||
@@ -315,6 +320,7 @@ class AdService {
|
||||
/// [adType] 광고 타입 (로깅용)
|
||||
/// [onComplete] 광고 완료 콜백 (보상 지급)
|
||||
/// Returns: 광고 결과
|
||||
@override
|
||||
Future<AdResult> showInterstitialAd({
|
||||
required AdType adType,
|
||||
required void Function() onComplete,
|
||||
@@ -386,6 +392,7 @@ class AdService {
|
||||
// ===========================================================================
|
||||
|
||||
/// 리소스 해제
|
||||
@override
|
||||
void dispose() {
|
||||
_rewardedAd?.dispose();
|
||||
_rewardedAd = null;
|
||||
|
||||
@@ -12,7 +12,10 @@ import 'package:pointycastle/asn1/primitives/asn1_bit_string.dart';
|
||||
import 'package:pointycastle/asn1/primitives/asn1_integer.dart';
|
||||
import 'package:pointycastle/asn1/primitives/asn1_sequence.dart';
|
||||
|
||||
import 'package:get_it/get_it.dart';
|
||||
|
||||
import 'package:asciineverdie/data/game_text_l10n.dart' as game_l10n;
|
||||
import 'package:asciineverdie/src/core/di/i_iap_service.dart';
|
||||
|
||||
/// IAP 상품 ID
|
||||
class IAPProductIds {
|
||||
@@ -62,16 +65,14 @@ String get _googlePlayPublicKey => _gpKeyPart1 + _gpKeyPart2;
|
||||
///
|
||||
/// 인앱 구매 (광고 제거) 처리를 담당합니다.
|
||||
/// flutter_secure_storage를 사용하여 구매 상태를 보안 저장합니다.
|
||||
class IAPService {
|
||||
class IAPService implements IIAPService {
|
||||
IAPService._();
|
||||
|
||||
static IAPService? _instance;
|
||||
/// GetIt 등록용 팩토리 메서드
|
||||
factory IAPService.createInstance() => IAPService._();
|
||||
|
||||
/// 싱글톤 인스턴스
|
||||
static IAPService get instance {
|
||||
_instance ??= IAPService._();
|
||||
return _instance!;
|
||||
}
|
||||
/// 싱글톤 인스턴스 (GetIt 서비스 로케이터에 위임)
|
||||
static IIAPService get instance => GetIt.instance<IIAPService>();
|
||||
|
||||
// ===========================================================================
|
||||
// 상수
|
||||
@@ -109,6 +110,7 @@ class IAPService {
|
||||
// ===========================================================================
|
||||
|
||||
/// IAP 서비스 초기화
|
||||
@override
|
||||
Future<void> initialize() async {
|
||||
if (_isInitialized) return;
|
||||
|
||||
@@ -201,9 +203,11 @@ class IAPService {
|
||||
// ===========================================================================
|
||||
|
||||
/// 디버그 모드 IAP 시뮬레이션 활성화 여부
|
||||
@override
|
||||
bool get debugIAPSimulated => _debugIAPSimulated;
|
||||
|
||||
/// 디버그 모드 IAP 시뮬레이션 토글
|
||||
@override
|
||||
set debugIAPSimulated(bool value) {
|
||||
_debugIAPSimulated = value;
|
||||
if (kDebugMode) {
|
||||
@@ -217,18 +221,21 @@ class IAPService {
|
||||
// ===========================================================================
|
||||
|
||||
/// 광고 제거 구매 여부
|
||||
@override
|
||||
bool get isAdRemovalPurchased {
|
||||
if (kDebugMode && _debugIAPSimulated) return true;
|
||||
return _adRemovalPurchased;
|
||||
}
|
||||
|
||||
/// 스토어 가용성
|
||||
@override
|
||||
bool get isStoreAvailable => _isAvailable;
|
||||
|
||||
/// 광고 제거 상품 정보
|
||||
ProductDetails? get removeAdsProduct => _removeAdsProduct;
|
||||
|
||||
/// 광고 제거 상품 가격 문자열
|
||||
@override
|
||||
String get removeAdsPrice {
|
||||
if (_removeAdsProduct != null) {
|
||||
return _removeAdsProduct!.price;
|
||||
@@ -246,6 +253,7 @@ class IAPService {
|
||||
// ===========================================================================
|
||||
|
||||
/// 광고 제거 구매
|
||||
@override
|
||||
Future<IAPResult> purchaseRemoveAds() async {
|
||||
// 디버그 모드 시뮬레이션
|
||||
if (kDebugMode && _debugIAPSimulated) {
|
||||
@@ -290,6 +298,7 @@ class IAPService {
|
||||
// ===========================================================================
|
||||
|
||||
/// 구매 복원
|
||||
@override
|
||||
Future<IAPResult> restorePurchases() async {
|
||||
// 디버그 모드 시뮬레이션
|
||||
if (kDebugMode && _debugIAPSimulated) {
|
||||
@@ -489,6 +498,7 @@ class IAPService {
|
||||
// ===========================================================================
|
||||
|
||||
/// 리소스 해제
|
||||
@override
|
||||
void dispose() {
|
||||
_subscription?.cancel();
|
||||
_subscription = null;
|
||||
|
||||
Reference in New Issue
Block a user