From 724f08f56d247a891bb395c282c38d0be82bcac5 Mon Sep 17 00:00:00 2001 From: JiWoong Sul Date: Fri, 16 Jan 2026 20:08:10 +0900 Subject: [PATCH] =?UTF-8?q?feat(monetization):=20=EC=88=98=EC=9D=B5?= =?UTF-8?q?=ED=99=94=20=EC=8B=9C=EC=8A=A4=ED=85=9C=20=EA=B8=B0=EB=B0=98=20?= =?UTF-8?q?=EB=AA=A8=EB=8D=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - MonetizationState freezed 모델 추가 - google_mobile_ads, in_app_purchase 의존성 추가 - IAP 구매 상태, 버프 종료 시점, 복귀 보상 데이터 관리 --- lib/src/core/model/monetization_state.dart | 162 +++++++ .../model/monetization_state.freezed.dart | 444 ++++++++++++++++++ lib/src/core/model/monetization_state.g.dart | 35 ++ pubspec.lock | 72 +++ pubspec.yaml | 4 + 5 files changed, 717 insertions(+) create mode 100644 lib/src/core/model/monetization_state.dart create mode 100644 lib/src/core/model/monetization_state.freezed.dart create mode 100644 lib/src/core/model/monetization_state.g.dart diff --git a/lib/src/core/model/monetization_state.dart b/lib/src/core/model/monetization_state.dart new file mode 100644 index 0000000..f0d7289 --- /dev/null +++ b/lib/src/core/model/monetization_state.dart @@ -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? 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 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>? _statsListToJson(List? 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? _statsListFromJson(List? json) { + if (json == null) return null; + return json.map((e) { + final m = e as Map; + 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); +} diff --git a/lib/src/core/model/monetization_state.freezed.dart b/lib/src/core/model/monetization_state.freezed.dart new file mode 100644 index 0000000..427c3aa --- /dev/null +++ b/lib/src/core/model/monetization_state.freezed.dart @@ -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 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 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? 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 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 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? 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?, + 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? 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?, + 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? rollHistory, + this.autoReviveEndMs, + this.speedBoostEndMs, + @JsonKey(fromJson: _dateTimeFromJson, toJson: _dateTimeToJson) + this.lastPlayTime, + this.pendingChests = 0, + this.luckyCharmEndMs, + }) : _rollHistory = rollHistory, + super._(); + + factory _$MonetizationStateImpl.fromJson(Map json) => + _$$MonetizationStateImplFromJson(json); + + /// IAP 광고 제거 구매 여부 + @override + @JsonKey() + final bool adRemovalPurchased; + + /// 캐릭터 생성 굴리기 남은 횟수 (0-5) + @override + @JsonKey() + final int rollsRemaining; + + /// 되돌리기 남은 횟수 + @override + @JsonKey() + final int undoRemaining; + + /// 되돌리기용 스탯 히스토리 (JSON 변환 커스텀) + final List? _rollHistory; + + /// 되돌리기용 스탯 히스토리 (JSON 변환 커스텀) + @override + @JsonKey(fromJson: _statsListFromJson, toJson: _statsListToJson) + List? 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 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? 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 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? 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; +} diff --git a/lib/src/core/model/monetization_state.g.dart b/lib/src/core/model/monetization_state.g.dart new file mode 100644 index 0000000..34e053c --- /dev/null +++ b/lib/src/core/model/monetization_state.g.dart @@ -0,0 +1,35 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'monetization_state.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$MonetizationStateImpl _$$MonetizationStateImplFromJson( + Map 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 _$$MonetizationStateImplToJson( + _$MonetizationStateImpl instance, +) => { + '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, +}; diff --git a/pubspec.lock b/pubspec.lock index 586b05d..3f2a970 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -301,6 +301,14 @@ packages: url: "https://pub.dev" source: hosted 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: dependency: transitive description: @@ -341,6 +349,38 @@ packages: url: "https://pub.dev" source: hosted 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: dependency: "direct main" description: @@ -834,6 +874,38 @@ packages: url: "https://pub.dev" source: hosted 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: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 8b3926f..aa09528 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -41,6 +41,10 @@ dependencies: # Code generation annotations freezed_annotation: ^2.4.1 json_annotation: ^4.9.0 + # AdMob 광고 + google_mobile_ads: ^5.3.0 + # IAP (인앱 결제) + in_app_purchase: ^3.2.0 dev_dependencies: flutter_test: