138 lines
4.3 KiB
Dart
138 lines
4.3 KiB
Dart
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 ? "다음 게임" : "확인"),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
} |