feat(monetization): 수익화 시스템 기반 모델 추가

- MonetizationState freezed 모델 추가
- google_mobile_ads, in_app_purchase 의존성 추가
- IAP 구매 상태, 버프 종료 시점, 복귀 보상 데이터 관리
This commit is contained in:
JiWoong Sul
2026-01-16 20:08:10 +09:00
parent 306715ca26
commit 724f08f56d
5 changed files with 717 additions and 0 deletions

View File

@@ -0,0 +1,162 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:asciineverdie/src/core/model/game_state.dart';
part 'monetization_state.freezed.dart';
part 'monetization_state.g.dart';
/// 수익화 시스템 상태
///
/// IAP 구매 여부, 광고 관련 버프, 복귀 보상 등을 관리
@freezed
class MonetizationState with _$MonetizationState {
const MonetizationState._();
const factory MonetizationState({
/// IAP 광고 제거 구매 여부
@Default(false) bool adRemovalPurchased,
/// 캐릭터 생성 굴리기 남은 횟수 (0-5)
@Default(5) int rollsRemaining,
/// 되돌리기 남은 횟수
@Default(1) int undoRemaining,
/// 되돌리기용 스탯 히스토리 (JSON 변환 커스텀)
@JsonKey(fromJson: _statsListFromJson, toJson: _statsListToJson)
List<Stats>? rollHistory,
/// 자동부활 버프 종료 시점 (elapsedMs 기준)
int? autoReviveEndMs,
/// 5배속 버프 종료 시점 (elapsedMs 기준)
int? speedBoostEndMs,
/// 마지막 플레이 시각 (복귀 보상 계산용)
@JsonKey(fromJson: _dateTimeFromJson, toJson: _dateTimeToJson)
DateTime? lastPlayTime,
/// 미개봉 보물 상자 개수
@Default(0) int pendingChests,
/// 행운의 부적 버프 종료 시점 (elapsedMs 기준)
int? luckyCharmEndMs,
}) = _MonetizationState;
factory MonetizationState.fromJson(Map<String, dynamic> json) =>
_$MonetizationStateFromJson(json);
/// 기본 상태 생성 (신규 게임)
factory MonetizationState.initial({bool isPaidUser = false}) {
return MonetizationState(
adRemovalPurchased: isPaidUser,
rollsRemaining: 5,
undoRemaining: isPaidUser ? 3 : 1,
rollHistory: null,
pendingChests: 0,
);
}
// ===========================================================================
// 유틸리티 메서드
// ===========================================================================
/// 유료 사용자 여부
bool get isPaidUser => adRemovalPurchased;
/// 무료 사용자 여부
bool get isFreeUser => !adRemovalPurchased;
/// 자동부활 버프 활성 여부 (elapsedMs 기준)
bool isAutoReviveActive(int elapsedMs) {
if (autoReviveEndMs == null) return false;
return elapsedMs < autoReviveEndMs!;
}
/// 5배속 버프 활성 여부 (elapsedMs 기준)
/// 유료 사용자는 항상 활성
bool isSpeedBoostActive(int elapsedMs) {
if (isPaidUser) return true;
if (speedBoostEndMs == null) return false;
return elapsedMs < speedBoostEndMs!;
}
/// 행운의 부적 버프 활성 여부 (elapsedMs 기준)
bool isLuckyCharmActive(int elapsedMs) {
if (luckyCharmEndMs == null) return false;
return elapsedMs < luckyCharmEndMs!;
}
/// 굴리기 가능 여부
bool get canRoll => rollsRemaining > 0;
/// 되돌리기 가능 여부
bool canUndo(int historyLength) {
if (undoRemaining <= 0) return false;
if (rollHistory == null || rollHistory!.isEmpty) return false;
return historyLength > 0;
}
/// 실제 사용 가능한 되돌리기 횟수
int availableUndos(int historyLength) {
if (rollHistory == null) return 0;
final historyAvailable = rollHistory!.length;
return undoRemaining < historyAvailable ? undoRemaining : historyAvailable;
}
/// 최대 보물 상자 개수
int get maxChests => isPaidUser ? 10 : 5;
/// 보물 상자가 가득 찼는지 여부
bool get isChestsFull => pendingChests >= maxChests;
}
// =============================================================================
// JSON 변환 헬퍼
// =============================================================================
/// Stats 리스트 → JSON
List<Map<String, dynamic>>? _statsListToJson(List<Stats>? stats) {
if (stats == null) return null;
return stats
.map((s) => {
'str': s.str,
'con': s.con,
'dex': s.dex,
'int': s.intelligence,
'wis': s.wis,
'cha': s.cha,
'hpMax': s.hpMax,
'mpMax': s.mpMax,
})
.toList();
}
/// JSON → Stats 리스트
List<Stats>? _statsListFromJson(List<dynamic>? json) {
if (json == null) return null;
return json.map((e) {
final m = e as Map<String, dynamic>;
return Stats(
str: m['str'] as int? ?? 0,
con: m['con'] as int? ?? 0,
dex: m['dex'] as int? ?? 0,
intelligence: m['int'] as int? ?? 0,
wis: m['wis'] as int? ?? 0,
cha: m['cha'] as int? ?? 0,
hpMax: m['hpMax'] as int? ?? 0,
mpMax: m['mpMax'] as int? ?? 0,
);
}).toList();
}
/// DateTime → JSON (밀리초 타임스탬프)
int? _dateTimeToJson(DateTime? dateTime) {
return dateTime?.millisecondsSinceEpoch;
}
/// JSON → DateTime
DateTime? _dateTimeFromJson(int? timestamp) {
if (timestamp == null) return null;
return DateTime.fromMillisecondsSinceEpoch(timestamp);
}

View File

@@ -0,0 +1,444 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'monetization_state.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models',
);
MonetizationState _$MonetizationStateFromJson(Map<String, dynamic> json) {
return _MonetizationState.fromJson(json);
}
/// @nodoc
mixin _$MonetizationState {
/// IAP 광고 제거 구매 여부
bool get adRemovalPurchased => throw _privateConstructorUsedError;
/// 캐릭터 생성 굴리기 남은 횟수 (0-5)
int get rollsRemaining => throw _privateConstructorUsedError;
/// 되돌리기 남은 횟수
int get undoRemaining => throw _privateConstructorUsedError;
/// 되돌리기용 스탯 히스토리 (JSON 변환 커스텀)
@JsonKey(fromJson: _statsListFromJson, toJson: _statsListToJson)
List<Stats>? get rollHistory => throw _privateConstructorUsedError;
/// 자동부활 버프 종료 시점 (elapsedMs 기준)
int? get autoReviveEndMs => throw _privateConstructorUsedError;
/// 5배속 버프 종료 시점 (elapsedMs 기준)
int? get speedBoostEndMs => throw _privateConstructorUsedError;
/// 마지막 플레이 시각 (복귀 보상 계산용)
@JsonKey(fromJson: _dateTimeFromJson, toJson: _dateTimeToJson)
DateTime? get lastPlayTime => throw _privateConstructorUsedError;
/// 미개봉 보물 상자 개수
int get pendingChests => throw _privateConstructorUsedError;
/// 행운의 부적 버프 종료 시점 (elapsedMs 기준)
int? get luckyCharmEndMs => throw _privateConstructorUsedError;
/// Serializes this MonetizationState to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
/// Create a copy of MonetizationState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$MonetizationStateCopyWith<MonetizationState> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $MonetizationStateCopyWith<$Res> {
factory $MonetizationStateCopyWith(
MonetizationState value,
$Res Function(MonetizationState) then,
) = _$MonetizationStateCopyWithImpl<$Res, MonetizationState>;
@useResult
$Res call({
bool adRemovalPurchased,
int rollsRemaining,
int undoRemaining,
@JsonKey(fromJson: _statsListFromJson, toJson: _statsListToJson)
List<Stats>? rollHistory,
int? autoReviveEndMs,
int? speedBoostEndMs,
@JsonKey(fromJson: _dateTimeFromJson, toJson: _dateTimeToJson)
DateTime? lastPlayTime,
int pendingChests,
int? luckyCharmEndMs,
});
}
/// @nodoc
class _$MonetizationStateCopyWithImpl<$Res, $Val extends MonetizationState>
implements $MonetizationStateCopyWith<$Res> {
_$MonetizationStateCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of MonetizationState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? adRemovalPurchased = null,
Object? rollsRemaining = null,
Object? undoRemaining = null,
Object? rollHistory = freezed,
Object? autoReviveEndMs = freezed,
Object? speedBoostEndMs = freezed,
Object? lastPlayTime = freezed,
Object? pendingChests = null,
Object? luckyCharmEndMs = freezed,
}) {
return _then(
_value.copyWith(
adRemovalPurchased: null == adRemovalPurchased
? _value.adRemovalPurchased
: adRemovalPurchased // ignore: cast_nullable_to_non_nullable
as bool,
rollsRemaining: null == rollsRemaining
? _value.rollsRemaining
: rollsRemaining // ignore: cast_nullable_to_non_nullable
as int,
undoRemaining: null == undoRemaining
? _value.undoRemaining
: undoRemaining // ignore: cast_nullable_to_non_nullable
as int,
rollHistory: freezed == rollHistory
? _value.rollHistory
: rollHistory // ignore: cast_nullable_to_non_nullable
as List<Stats>?,
autoReviveEndMs: freezed == autoReviveEndMs
? _value.autoReviveEndMs
: autoReviveEndMs // ignore: cast_nullable_to_non_nullable
as int?,
speedBoostEndMs: freezed == speedBoostEndMs
? _value.speedBoostEndMs
: speedBoostEndMs // ignore: cast_nullable_to_non_nullable
as int?,
lastPlayTime: freezed == lastPlayTime
? _value.lastPlayTime
: lastPlayTime // ignore: cast_nullable_to_non_nullable
as DateTime?,
pendingChests: null == pendingChests
? _value.pendingChests
: pendingChests // ignore: cast_nullable_to_non_nullable
as int,
luckyCharmEndMs: freezed == luckyCharmEndMs
? _value.luckyCharmEndMs
: luckyCharmEndMs // ignore: cast_nullable_to_non_nullable
as int?,
)
as $Val,
);
}
}
/// @nodoc
abstract class _$$MonetizationStateImplCopyWith<$Res>
implements $MonetizationStateCopyWith<$Res> {
factory _$$MonetizationStateImplCopyWith(
_$MonetizationStateImpl value,
$Res Function(_$MonetizationStateImpl) then,
) = __$$MonetizationStateImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({
bool adRemovalPurchased,
int rollsRemaining,
int undoRemaining,
@JsonKey(fromJson: _statsListFromJson, toJson: _statsListToJson)
List<Stats>? rollHistory,
int? autoReviveEndMs,
int? speedBoostEndMs,
@JsonKey(fromJson: _dateTimeFromJson, toJson: _dateTimeToJson)
DateTime? lastPlayTime,
int pendingChests,
int? luckyCharmEndMs,
});
}
/// @nodoc
class __$$MonetizationStateImplCopyWithImpl<$Res>
extends _$MonetizationStateCopyWithImpl<$Res, _$MonetizationStateImpl>
implements _$$MonetizationStateImplCopyWith<$Res> {
__$$MonetizationStateImplCopyWithImpl(
_$MonetizationStateImpl _value,
$Res Function(_$MonetizationStateImpl) _then,
) : super(_value, _then);
/// Create a copy of MonetizationState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? adRemovalPurchased = null,
Object? rollsRemaining = null,
Object? undoRemaining = null,
Object? rollHistory = freezed,
Object? autoReviveEndMs = freezed,
Object? speedBoostEndMs = freezed,
Object? lastPlayTime = freezed,
Object? pendingChests = null,
Object? luckyCharmEndMs = freezed,
}) {
return _then(
_$MonetizationStateImpl(
adRemovalPurchased: null == adRemovalPurchased
? _value.adRemovalPurchased
: adRemovalPurchased // ignore: cast_nullable_to_non_nullable
as bool,
rollsRemaining: null == rollsRemaining
? _value.rollsRemaining
: rollsRemaining // ignore: cast_nullable_to_non_nullable
as int,
undoRemaining: null == undoRemaining
? _value.undoRemaining
: undoRemaining // ignore: cast_nullable_to_non_nullable
as int,
rollHistory: freezed == rollHistory
? _value._rollHistory
: rollHistory // ignore: cast_nullable_to_non_nullable
as List<Stats>?,
autoReviveEndMs: freezed == autoReviveEndMs
? _value.autoReviveEndMs
: autoReviveEndMs // ignore: cast_nullable_to_non_nullable
as int?,
speedBoostEndMs: freezed == speedBoostEndMs
? _value.speedBoostEndMs
: speedBoostEndMs // ignore: cast_nullable_to_non_nullable
as int?,
lastPlayTime: freezed == lastPlayTime
? _value.lastPlayTime
: lastPlayTime // ignore: cast_nullable_to_non_nullable
as DateTime?,
pendingChests: null == pendingChests
? _value.pendingChests
: pendingChests // ignore: cast_nullable_to_non_nullable
as int,
luckyCharmEndMs: freezed == luckyCharmEndMs
? _value.luckyCharmEndMs
: luckyCharmEndMs // ignore: cast_nullable_to_non_nullable
as int?,
),
);
}
}
/// @nodoc
@JsonSerializable()
class _$MonetizationStateImpl extends _MonetizationState {
const _$MonetizationStateImpl({
this.adRemovalPurchased = false,
this.rollsRemaining = 5,
this.undoRemaining = 1,
@JsonKey(fromJson: _statsListFromJson, toJson: _statsListToJson)
final List<Stats>? rollHistory,
this.autoReviveEndMs,
this.speedBoostEndMs,
@JsonKey(fromJson: _dateTimeFromJson, toJson: _dateTimeToJson)
this.lastPlayTime,
this.pendingChests = 0,
this.luckyCharmEndMs,
}) : _rollHistory = rollHistory,
super._();
factory _$MonetizationStateImpl.fromJson(Map<String, dynamic> json) =>
_$$MonetizationStateImplFromJson(json);
/// IAP 광고 제거 구매 여부
@override
@JsonKey()
final bool adRemovalPurchased;
/// 캐릭터 생성 굴리기 남은 횟수 (0-5)
@override
@JsonKey()
final int rollsRemaining;
/// 되돌리기 남은 횟수
@override
@JsonKey()
final int undoRemaining;
/// 되돌리기용 스탯 히스토리 (JSON 변환 커스텀)
final List<Stats>? _rollHistory;
/// 되돌리기용 스탯 히스토리 (JSON 변환 커스텀)
@override
@JsonKey(fromJson: _statsListFromJson, toJson: _statsListToJson)
List<Stats>? get rollHistory {
final value = _rollHistory;
if (value == null) return null;
if (_rollHistory is EqualUnmodifiableListView) return _rollHistory;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(value);
}
/// 자동부활 버프 종료 시점 (elapsedMs 기준)
@override
final int? autoReviveEndMs;
/// 5배속 버프 종료 시점 (elapsedMs 기준)
@override
final int? speedBoostEndMs;
/// 마지막 플레이 시각 (복귀 보상 계산용)
@override
@JsonKey(fromJson: _dateTimeFromJson, toJson: _dateTimeToJson)
final DateTime? lastPlayTime;
/// 미개봉 보물 상자 개수
@override
@JsonKey()
final int pendingChests;
/// 행운의 부적 버프 종료 시점 (elapsedMs 기준)
@override
final int? luckyCharmEndMs;
@override
String toString() {
return 'MonetizationState(adRemovalPurchased: $adRemovalPurchased, rollsRemaining: $rollsRemaining, undoRemaining: $undoRemaining, rollHistory: $rollHistory, autoReviveEndMs: $autoReviveEndMs, speedBoostEndMs: $speedBoostEndMs, lastPlayTime: $lastPlayTime, pendingChests: $pendingChests, luckyCharmEndMs: $luckyCharmEndMs)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$MonetizationStateImpl &&
(identical(other.adRemovalPurchased, adRemovalPurchased) ||
other.adRemovalPurchased == adRemovalPurchased) &&
(identical(other.rollsRemaining, rollsRemaining) ||
other.rollsRemaining == rollsRemaining) &&
(identical(other.undoRemaining, undoRemaining) ||
other.undoRemaining == undoRemaining) &&
const DeepCollectionEquality().equals(
other._rollHistory,
_rollHistory,
) &&
(identical(other.autoReviveEndMs, autoReviveEndMs) ||
other.autoReviveEndMs == autoReviveEndMs) &&
(identical(other.speedBoostEndMs, speedBoostEndMs) ||
other.speedBoostEndMs == speedBoostEndMs) &&
(identical(other.lastPlayTime, lastPlayTime) ||
other.lastPlayTime == lastPlayTime) &&
(identical(other.pendingChests, pendingChests) ||
other.pendingChests == pendingChests) &&
(identical(other.luckyCharmEndMs, luckyCharmEndMs) ||
other.luckyCharmEndMs == luckyCharmEndMs));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(
runtimeType,
adRemovalPurchased,
rollsRemaining,
undoRemaining,
const DeepCollectionEquality().hash(_rollHistory),
autoReviveEndMs,
speedBoostEndMs,
lastPlayTime,
pendingChests,
luckyCharmEndMs,
);
/// Create a copy of MonetizationState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$MonetizationStateImplCopyWith<_$MonetizationStateImpl> get copyWith =>
__$$MonetizationStateImplCopyWithImpl<_$MonetizationStateImpl>(
this,
_$identity,
);
@override
Map<String, dynamic> toJson() {
return _$$MonetizationStateImplToJson(this);
}
}
abstract class _MonetizationState extends MonetizationState {
const factory _MonetizationState({
final bool adRemovalPurchased,
final int rollsRemaining,
final int undoRemaining,
@JsonKey(fromJson: _statsListFromJson, toJson: _statsListToJson)
final List<Stats>? rollHistory,
final int? autoReviveEndMs,
final int? speedBoostEndMs,
@JsonKey(fromJson: _dateTimeFromJson, toJson: _dateTimeToJson)
final DateTime? lastPlayTime,
final int pendingChests,
final int? luckyCharmEndMs,
}) = _$MonetizationStateImpl;
const _MonetizationState._() : super._();
factory _MonetizationState.fromJson(Map<String, dynamic> json) =
_$MonetizationStateImpl.fromJson;
/// IAP 광고 제거 구매 여부
@override
bool get adRemovalPurchased;
/// 캐릭터 생성 굴리기 남은 횟수 (0-5)
@override
int get rollsRemaining;
/// 되돌리기 남은 횟수
@override
int get undoRemaining;
/// 되돌리기용 스탯 히스토리 (JSON 변환 커스텀)
@override
@JsonKey(fromJson: _statsListFromJson, toJson: _statsListToJson)
List<Stats>? get rollHistory;
/// 자동부활 버프 종료 시점 (elapsedMs 기준)
@override
int? get autoReviveEndMs;
/// 5배속 버프 종료 시점 (elapsedMs 기준)
@override
int? get speedBoostEndMs;
/// 마지막 플레이 시각 (복귀 보상 계산용)
@override
@JsonKey(fromJson: _dateTimeFromJson, toJson: _dateTimeToJson)
DateTime? get lastPlayTime;
/// 미개봉 보물 상자 개수
@override
int get pendingChests;
/// 행운의 부적 버프 종료 시점 (elapsedMs 기준)
@override
int? get luckyCharmEndMs;
/// Create a copy of MonetizationState
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$MonetizationStateImplCopyWith<_$MonetizationStateImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@@ -0,0 +1,35 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'monetization_state.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_$MonetizationStateImpl _$$MonetizationStateImplFromJson(
Map<String, dynamic> json,
) => _$MonetizationStateImpl(
adRemovalPurchased: json['adRemovalPurchased'] as bool? ?? false,
rollsRemaining: (json['rollsRemaining'] as num?)?.toInt() ?? 5,
undoRemaining: (json['undoRemaining'] as num?)?.toInt() ?? 1,
rollHistory: _statsListFromJson(json['rollHistory'] as List?),
autoReviveEndMs: (json['autoReviveEndMs'] as num?)?.toInt(),
speedBoostEndMs: (json['speedBoostEndMs'] as num?)?.toInt(),
lastPlayTime: _dateTimeFromJson((json['lastPlayTime'] as num?)?.toInt()),
pendingChests: (json['pendingChests'] as num?)?.toInt() ?? 0,
luckyCharmEndMs: (json['luckyCharmEndMs'] as num?)?.toInt(),
);
Map<String, dynamic> _$$MonetizationStateImplToJson(
_$MonetizationStateImpl instance,
) => <String, dynamic>{
'adRemovalPurchased': instance.adRemovalPurchased,
'rollsRemaining': instance.rollsRemaining,
'undoRemaining': instance.undoRemaining,
'rollHistory': _statsListToJson(instance.rollHistory),
'autoReviveEndMs': instance.autoReviveEndMs,
'speedBoostEndMs': instance.speedBoostEndMs,
'lastPlayTime': _dateTimeToJson(instance.lastPlayTime),
'pendingChests': instance.pendingChests,
'luckyCharmEndMs': instance.luckyCharmEndMs,
};

View File

@@ -301,6 +301,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.3" version: "2.1.3"
google_mobile_ads:
dependency: "direct main"
description:
name: google_mobile_ads
sha256: "0d4a3744b5e8ed1b8be6a1b452d309f811688855a497c6113fc4400f922db603"
url: "https://pub.dev"
source: hosted
version: "5.3.1"
graphs: graphs:
dependency: transitive dependency: transitive
description: description:
@@ -341,6 +349,38 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.7.2" version: "4.7.2"
in_app_purchase:
dependency: "direct main"
description:
name: in_app_purchase
sha256: "5cddd7f463f3bddb1d37a72b95066e840d5822d66291331d7f8f05ce32c24b6c"
url: "https://pub.dev"
source: hosted
version: "3.2.3"
in_app_purchase_android:
dependency: transitive
description:
name: in_app_purchase_android
sha256: abb254ae159a5a9d4f867795ecb076864faeba59ce015ab81d4cca380f23df45
url: "https://pub.dev"
source: hosted
version: "0.4.0+8"
in_app_purchase_platform_interface:
dependency: transitive
description:
name: in_app_purchase_platform_interface
sha256: "1d353d38251da5b9fea6635c0ebfc6bb17a2d28d0e86ea5e083bf64244f1fb4c"
url: "https://pub.dev"
source: hosted
version: "1.4.0"
in_app_purchase_storekit:
dependency: transitive
description:
name: in_app_purchase_storekit
sha256: f7cbbd7fb47ab5a4fb736fc3f20ae81a4f6def0af9297b3c525ca727761e2589
url: "https://pub.dev"
source: hosted
version: "0.4.7"
intl: intl:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -834,6 +874,38 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.3" version: "3.0.3"
webview_flutter:
dependency: transitive
description:
name: webview_flutter
sha256: a3da219916aba44947d3a5478b1927876a09781174b5a2b67fa5be0555154bf9
url: "https://pub.dev"
source: hosted
version: "4.13.1"
webview_flutter_android:
dependency: transitive
description:
name: webview_flutter_android
sha256: eeeb3fcd5f0ff9f8446c9f4bbc18a99b809e40297528a3395597d03aafb9f510
url: "https://pub.dev"
source: hosted
version: "4.10.11"
webview_flutter_platform_interface:
dependency: transitive
description:
name: webview_flutter_platform_interface
sha256: "63d26ee3aca7256a83ccb576a50272edd7cfc80573a4305caa98985feb493ee0"
url: "https://pub.dev"
source: hosted
version: "2.14.0"
webview_flutter_wkwebview:
dependency: transitive
description:
name: webview_flutter_wkwebview
sha256: e49f378ed066efb13fc36186bbe0bd2425630d4ea0dbc71a18fdd0e4d8ed8ebc
url: "https://pub.dev"
source: hosted
version: "3.23.5"
xdg_directories: xdg_directories:
dependency: transitive dependency: transitive
description: description:

View File

@@ -41,6 +41,10 @@ dependencies:
# Code generation annotations # Code generation annotations
freezed_annotation: ^2.4.1 freezed_annotation: ^2.4.1
json_annotation: ^4.9.0 json_annotation: ^4.9.0
# AdMob 광고
google_mobile_ads: ^5.3.0
# IAP (인앱 결제)
in_app_purchase: ^3.2.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: