feat(ui): 화면 및 컨트롤러 수익화 연동
- 앱 초기화에 광고/IAP 서비스 추가 - 게임 세션 컨트롤러 수익화 상태 관리 - 캐릭터 생성 화면 굴리기 제한 UI - 설정 화면 광고 제거 구매 UI - 애니메이션 패널 개선
This commit is contained in:
@@ -8,6 +8,8 @@ import 'package:asciineverdie/data/class_data.dart';
|
||||
import 'package:asciineverdie/data/game_text_l10n.dart' as game_l10n;
|
||||
import 'package:asciineverdie/data/race_data.dart';
|
||||
import 'package:asciineverdie/l10n/app_localizations.dart';
|
||||
import 'package:asciineverdie/src/core/engine/character_roll_service.dart';
|
||||
import 'package:asciineverdie/src/core/engine/iap_service.dart';
|
||||
import 'package:asciineverdie/src/core/model/class_traits.dart';
|
||||
import 'package:asciineverdie/src/core/model/game_state.dart';
|
||||
import 'package:asciineverdie/src/core/model/race_traits.dart';
|
||||
@@ -52,10 +54,6 @@ class _NewCharacterScreenState extends State<NewCharacterScreen> {
|
||||
int _wis = 0;
|
||||
int _cha = 0;
|
||||
|
||||
// 롤 이력 (Unroll 기능용) - 원본 OldRolls TListBox
|
||||
static const int _maxRollHistory = 20; // 최대 저장 개수
|
||||
final List<int> _rollHistory = [];
|
||||
|
||||
// 현재 RNG 시드 (Re-Roll 전 저장)
|
||||
int _currentSeed = 0;
|
||||
|
||||
@@ -68,10 +66,19 @@ class _NewCharacterScreenState extends State<NewCharacterScreen> {
|
||||
// 굴리기 버튼 연속 클릭 방지
|
||||
bool _isRolling = false;
|
||||
|
||||
// 굴리기/되돌리기 서비스
|
||||
final CharacterRollService _rollService = CharacterRollService.instance;
|
||||
|
||||
// 서비스 초기화 완료 여부
|
||||
bool _isServiceInitialized = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
// 서비스 초기화
|
||||
_initializeService();
|
||||
|
||||
// 초기 랜덤화
|
||||
final random = math.Random();
|
||||
_selectedRaceIndex = random.nextInt(_races.length);
|
||||
@@ -89,6 +96,16 @@ class _NewCharacterScreenState extends State<NewCharacterScreen> {
|
||||
_scrollToSelectedItems();
|
||||
}
|
||||
|
||||
/// 서비스 초기화
|
||||
Future<void> _initializeService() async {
|
||||
await _rollService.initialize();
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isServiceInitialized = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_nameController.dispose();
|
||||
@@ -144,12 +161,35 @@ class _NewCharacterScreenState extends State<NewCharacterScreen> {
|
||||
if (_isRolling) return;
|
||||
_isRolling = true;
|
||||
|
||||
// 현재 시드를 이력에 저장
|
||||
_rollHistory.insert(0, _currentSeed);
|
||||
// 굴리기 가능 여부 확인
|
||||
if (!_rollService.canRoll) {
|
||||
_isRolling = false;
|
||||
_showRechargeDialog();
|
||||
return;
|
||||
}
|
||||
|
||||
// 최대 개수 초과 시 가장 오래된 항목 제거
|
||||
if (_rollHistory.length > _maxRollHistory) {
|
||||
_rollHistory.removeLast();
|
||||
// 현재 상태를 서비스에 저장
|
||||
final currentStats = Stats(
|
||||
str: _str,
|
||||
con: _con,
|
||||
dex: _dex,
|
||||
intelligence: _int,
|
||||
wis: _wis,
|
||||
cha: _cha,
|
||||
hpMax: 0,
|
||||
mpMax: 0,
|
||||
);
|
||||
|
||||
final success = _rollService.roll(
|
||||
currentStats: currentStats,
|
||||
currentRaceIndex: _selectedRaceIndex,
|
||||
currentKlassIndex: _selectedKlassIndex,
|
||||
currentSeed: _currentSeed,
|
||||
);
|
||||
|
||||
if (!success) {
|
||||
_isRolling = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// 새 시드로 굴림
|
||||
@@ -173,14 +213,103 @@ class _NewCharacterScreenState extends State<NewCharacterScreen> {
|
||||
});
|
||||
}
|
||||
|
||||
/// Unroll 버튼 클릭 (이전 롤로 복원)
|
||||
void _onUnroll() {
|
||||
if (_rollHistory.isEmpty) return;
|
||||
/// 굴리기 충전 다이얼로그
|
||||
Future<void> _showRechargeDialog() async {
|
||||
final isPaidUser = IAPService.instance.isAdRemovalPurchased;
|
||||
|
||||
setState(() {
|
||||
_currentSeed = _rollHistory.removeAt(0);
|
||||
});
|
||||
_rollStats();
|
||||
final result = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
backgroundColor: RetroColors.panelBg,
|
||||
title: const Text(
|
||||
'RECHARGE ROLLS',
|
||||
style: TextStyle(
|
||||
fontFamily: 'PressStart2P',
|
||||
fontSize: 14,
|
||||
color: RetroColors.gold,
|
||||
),
|
||||
),
|
||||
content: Text(
|
||||
isPaidUser
|
||||
? 'Recharge 5 rolls for free?'
|
||||
: 'Watch an ad to recharge 5 rolls?',
|
||||
style: const TextStyle(
|
||||
fontFamily: 'PressStart2P',
|
||||
fontSize: 12,
|
||||
color: RetroColors.textLight,
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, false),
|
||||
child: const Text(
|
||||
'CANCEL',
|
||||
style: TextStyle(
|
||||
fontFamily: 'PressStart2P',
|
||||
fontSize: 11,
|
||||
color: RetroColors.textDisabled,
|
||||
),
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, true),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (!isPaidUser) ...[
|
||||
const Icon(Icons.play_circle, size: 14, color: RetroColors.gold),
|
||||
const SizedBox(width: 4),
|
||||
],
|
||||
const Text(
|
||||
'RECHARGE',
|
||||
style: TextStyle(
|
||||
fontFamily: 'PressStart2P',
|
||||
fontSize: 11,
|
||||
color: RetroColors.gold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
if (result == true && mounted) {
|
||||
final success = await _rollService.rechargeRollsWithAd();
|
||||
if (success && mounted) {
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Unroll 버튼 클릭 (이전 롤로 복원)
|
||||
Future<void> _onUnroll() async {
|
||||
if (!_rollService.canUndo) return;
|
||||
|
||||
final isPaidUser = IAPService.instance.isAdRemovalPurchased;
|
||||
RollSnapshot? snapshot;
|
||||
|
||||
if (isPaidUser) {
|
||||
snapshot = _rollService.undoPaidUser();
|
||||
} else {
|
||||
snapshot = await _rollService.undoFreeUser();
|
||||
}
|
||||
|
||||
if (snapshot != null && mounted) {
|
||||
setState(() {
|
||||
_str = snapshot!.stats.str;
|
||||
_con = snapshot.stats.con;
|
||||
_dex = snapshot.stats.dex;
|
||||
_int = snapshot.stats.intelligence;
|
||||
_wis = snapshot.stats.wis;
|
||||
_cha = snapshot.stats.cha;
|
||||
_selectedRaceIndex = snapshot.raceIndex;
|
||||
_selectedKlassIndex = snapshot.klassIndex;
|
||||
_currentSeed = snapshot.seed;
|
||||
});
|
||||
_scrollToSelectedItems();
|
||||
}
|
||||
}
|
||||
|
||||
/// 이름 생성 버튼 클릭
|
||||
@@ -266,6 +395,9 @@ class _NewCharacterScreenState extends State<NewCharacterScreen> {
|
||||
queue: QueueState.empty(),
|
||||
);
|
||||
|
||||
// 캐릭터 생성 완료 알림 (되돌리기 상태 초기화)
|
||||
_rollService.onCharacterCreated();
|
||||
|
||||
widget.onCharacterCreated?.call(initialState, testMode: _cheatsEnabled);
|
||||
}
|
||||
|
||||
@@ -493,34 +625,27 @@ class _NewCharacterScreenState extends State<NewCharacterScreen> {
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
RetroTextButton(
|
||||
text: l10n.unroll,
|
||||
icon: Icons.undo,
|
||||
onPressed: _rollHistory.isEmpty ? null : _onUnroll,
|
||||
isPrimary: false,
|
||||
),
|
||||
_buildUndoButton(l10n),
|
||||
const SizedBox(width: 16),
|
||||
RetroTextButton(
|
||||
text: l10n.roll,
|
||||
icon: Icons.casino,
|
||||
onPressed: _onReroll,
|
||||
),
|
||||
_buildRollButton(l10n),
|
||||
],
|
||||
),
|
||||
if (_rollHistory.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8),
|
||||
child: Center(
|
||||
child: Text(
|
||||
game_l10n.uiRollHistory(_rollHistory.length),
|
||||
style: const TextStyle(
|
||||
fontFamily: 'PressStart2P',
|
||||
fontSize: 13,
|
||||
color: RetroColors.textDisabled,
|
||||
),
|
||||
// 남은 횟수 표시
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8),
|
||||
child: Center(
|
||||
child: Text(
|
||||
_rollService.canUndo
|
||||
? 'Undo: ${_rollService.availableUndos} | Rolls: ${_rollService.rollsRemaining}/5'
|
||||
: 'Rolls: ${_rollService.rollsRemaining}/5',
|
||||
style: const TextStyle(
|
||||
fontFamily: 'PressStart2P',
|
||||
fontSize: 11,
|
||||
color: RetroColors.textDisabled,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -790,4 +915,96 @@ class _NewCharacterScreenState extends State<NewCharacterScreen> {
|
||||
ClassPassiveType.firstStrikeBonus => passive.description,
|
||||
};
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// 굴리기/되돌리기 버튼 위젯
|
||||
// ===========================================================================
|
||||
|
||||
/// 되돌리기 버튼
|
||||
Widget _buildUndoButton(L10n l10n) {
|
||||
final canUndo = _rollService.canUndo;
|
||||
final isPaidUser = IAPService.instance.isAdRemovalPurchased;
|
||||
|
||||
return GestureDetector(
|
||||
onTap: canUndo ? _onUnroll : null,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: canUndo
|
||||
? RetroColors.panelBgLight
|
||||
: RetroColors.panelBg.withValues(alpha: 0.5),
|
||||
border: Border.all(
|
||||
color: canUndo ? RetroColors.panelBorderInner : RetroColors.panelBg,
|
||||
width: 2,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// 무료 유저는 광고 아이콘 표시
|
||||
if (!isPaidUser && canUndo) ...[
|
||||
const Icon(
|
||||
Icons.play_circle,
|
||||
size: 14,
|
||||
color: RetroColors.gold,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
],
|
||||
Icon(
|
||||
Icons.undo,
|
||||
size: 14,
|
||||
color: canUndo ? RetroColors.textLight : RetroColors.textDisabled,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
l10n.unroll.toUpperCase(),
|
||||
style: TextStyle(
|
||||
fontFamily: 'PressStart2P',
|
||||
fontSize: 11,
|
||||
color: canUndo ? RetroColors.textLight : RetroColors.textDisabled,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 굴리기 버튼
|
||||
Widget _buildRollButton(L10n l10n) {
|
||||
final canRoll = _rollService.canRoll;
|
||||
|
||||
return GestureDetector(
|
||||
onTap: _onReroll,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: RetroColors.panelBgLight,
|
||||
border: Border.all(color: RetroColors.gold, width: 2),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// 0회일 때 광고 아이콘 표시
|
||||
if (!canRoll) ...[
|
||||
const Icon(Icons.play_circle, size: 14, color: RetroColors.gold),
|
||||
const SizedBox(width: 4),
|
||||
],
|
||||
const Icon(Icons.casino, size: 14, color: RetroColors.gold),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
canRoll
|
||||
? '${l10n.roll.toUpperCase()} (${_rollService.rollsRemaining})'
|
||||
: l10n.roll.toUpperCase(),
|
||||
style: const TextStyle(
|
||||
fontFamily: 'PressStart2P',
|
||||
fontSize: 11,
|
||||
color: RetroColors.gold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user