games/packages/feature_common/lib/screens/base_game_screen.dart

138 lines
4.3 KiB
Dart
Raw Normal View History

2025-12-15 18:18:17 +09:00
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:service_api/service_api.dart';
class GameResultArgs {
final String gameType;
final String contextId;
final int primaryScore;
// [Legacy Support] 기존 게임 호환용 필드
final String? userId;
final String? userName; // [Fix] userName 필드 존재 확인
final int? secondaryScore;
// [Fix] 타입 불일치 해결: String을 받는 비동기 함수로 명시
final Future<void> Function(String)? onProgressSave;
final VoidCallback? onNextGame;
// [New Feature] 신규 게임 자동 저장용 필드
final int? stars;
final int? levelIndex;
final String Function(int score, int? subScore)? scoreFormatter;
GameResultArgs({
required this.gameType,
required this.contextId,
required this.primaryScore,
this.userId,
this.userName,
this.secondaryScore,
this.onProgressSave,
this.onNextGame,
this.stars,
this.levelIndex,
this.scoreFormatter,
});
}
abstract class BaseGameScreen extends StatefulWidget {
final VoidCallback? onNextGame;
const BaseGameScreen({super.key, this.onNextGame});
}
abstract class BaseGameScreenState<T extends BaseGameScreen> extends State<T> {
void showCommonGameCompletion(GameResultArgs args) async {
// 1. [New] 자동 저장 로직
if (args.stars != null && args.levelIndex != null) {
try {
if (!mounted) return;
final identityService = context.read<IdentityService>();
await identityService.submitGameResult(
gameType: args.gameType,
level: args.levelIndex!,
stars: args.stars!,
);
} catch (e) {
debugPrint("자동 저장 실패: $e");
}
}
// 2. [Legacy] 수동 저장 로직 호환 (기존 게임용)
if (args.onProgressSave != null) {
// 기존 게임들이 String 인자를 기대하므로 더미 문자열 전달
await args.onProgressSave!("legacy_save");
}
if (!mounted) return;
// 3. 팝업 표시
final VoidCallback? nextCallback = widget.onNextGame ?? args.onNextGame;
final bool isDailyCourse = nextCallback != null;
showDialog(
context: context,
barrierDismissible: false,
builder: (ctx) => AlertDialog(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
title: const Row(
children: [
Icon(Icons.emoji_events, color: Colors.orange, size: 28),
SizedBox(width: 8),
Text('훈련 완료!'),
],
),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
args.scoreFormatter != null
? args.scoreFormatter!(args.primaryScore, args.secondaryScore)
: "점수: ${args.primaryScore}",
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 12),
if (args.stars != null)
Row(
children: List.generate(3, (index) => Icon(
index < args.stars! ? Icons.star : Icons.star_border,
color: Colors.amber,
size: 32,
)),
),
if (args.stars != null) const SizedBox(height: 16),
Text(isDailyCourse
? "수고하셨습니다. 다음 훈련으로 이동합니다."
: "수고하셨습니다. 로비로 돌아갑니다."
),
],
),
actions: [
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blueAccent,
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
),
onPressed: () {
Navigator.pop(ctx);
if (isDailyCourse) {
nextCallback!();
} else {
if (Navigator.canPop(context)) {
Navigator.pop(context);
}
}
},
child: Text(isDailyCourse ? "다음 게임" : "확인"),
),
],
),
);
}
}