...
This commit is contained in:
@@ -26,18 +26,17 @@ class _GameCompletionScreenState extends State<GameCompletionScreen> {
|
||||
GameRankWithRankNumber? _myRankResult;
|
||||
String? _dialogErrorMessage;
|
||||
String _submittedPlayerName = "";
|
||||
|
||||
// 🔽 [신규] 랭킹 등록을 건너뛰었는지 확인하는 플래그
|
||||
bool _didSkipRank = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
// 🔽 [핵심 수정]
|
||||
// 랭킹 등록 여부와 상관없이, 이 화면에 진입한 것 자체가 "레벨 클리어"이므로
|
||||
// onProgressSave (레벨 잠금 해제)를 즉시 호출합니다.
|
||||
// (playerName은 이 콜백에서 사용되지 않으므로 빈 값을 전달합니다.)
|
||||
// 레벨 클리어 (레벨 잠금 해제)를 즉시 호출
|
||||
widget.args.onProgressSave("");
|
||||
|
||||
// --- (이하 기존 로직) ---
|
||||
final session = context.read<SessionNotifier>().session;
|
||||
_nameController = TextEditingController(text: session?.userName ?? widget.args.userName);
|
||||
|
||||
@@ -45,7 +44,7 @@ class _GameCompletionScreenState extends State<GameCompletionScreen> {
|
||||
if (session != null && !session.isGuest) {
|
||||
_rankStep = _RankSubmissionStep.submitting;
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_submitRank(autoSubmitName: session.userName);
|
||||
_submitRank(autoSubmitName: session.userName);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -91,13 +90,8 @@ class _GameCompletionScreenState extends State<GameCompletionScreen> {
|
||||
await _identityService.saveUserName(playerName);
|
||||
}
|
||||
|
||||
// 🔽 [수정]
|
||||
// onProgressSave는 initState에서 이미 호출되었지만,
|
||||
// saveMaxUnlockedLevel 함수 자체가 멱등성(Idempotent)을 가지므로
|
||||
// (이미 레벨이 6인데 6으로 덮어써도 문제없음)
|
||||
// 혹시 모를 실패에 대비해 여기서 한 번 더 호출해도 안전합니다.
|
||||
await widget.args.onProgressSave(playerName);
|
||||
|
||||
await widget.args.onProgressSave(playerName); // 레벨 저장 재확인
|
||||
|
||||
setState(() {
|
||||
_rankingList = result.topRanks;
|
||||
_myRankResult = result.myRank;
|
||||
@@ -109,35 +103,123 @@ class _GameCompletionScreenState extends State<GameCompletionScreen> {
|
||||
setState(() {
|
||||
_rankStep = _RankSubmissionStep.enterName;
|
||||
if (autoSubmitName != null) {
|
||||
_rankStep = _RankSubmissionStep.showList;
|
||||
_rankStep = _RankSubmissionStep.showList; // 자동 등록 실패 시 리스트라도 보여줌
|
||||
}
|
||||
_dialogErrorMessage = e.toString().replaceFirst("Exception: ", "");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// 닫기 버튼 로직
|
||||
void _closeScreen() {
|
||||
// 이 화면만 닫습니다. (GameScreen으로 돌아감)
|
||||
Navigator.of(context).pop();
|
||||
/// 🔽 [신규] 랭킹 등록 건너뛰기 및 화면 닫기
|
||||
void _skipRankAndClose() {
|
||||
setState(() {
|
||||
_didSkipRank = true;
|
||||
_rankStep = _RankSubmissionStep.showList; // 리스트 화면으로 전환하여 기록은 볼 수 있게 함
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// ... (이하 UI 빌드 로직은 모두 동일) ...
|
||||
final theme = Theme.of(context);
|
||||
/// 🔽 [신규] 점수 표시 위젯 (최상단 고정)
|
||||
Widget _buildScoreWidget(ThemeData theme) {
|
||||
final String scoreText = widget.args.scoreFormatter(
|
||||
widget.args.primaryScore, widget.args.secondaryScore);
|
||||
|
||||
// --- UI 섹션 정의 ---
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 20.0),
|
||||
child: Card(
|
||||
color: theme.colorScheme.primary.withOpacity(0.1),
|
||||
margin: EdgeInsets.zero,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(20.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Text(
|
||||
'나의 최종 기록',
|
||||
style: theme.textTheme.labelLarge?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: theme.colorScheme.primary,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
scoreText,
|
||||
style: theme.textTheme.headlineSmall?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: theme.colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 🔽 [신규] 이름 입력 및 버튼 섹션 (키보드 대응)
|
||||
Widget _buildNameEntrySection(ThemeData theme) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text('축하합니다! 랭킹에 등록할 이름을 입력하세요.', style: theme.textTheme.titleMedium),
|
||||
const SizedBox(height: 20),
|
||||
TextField(
|
||||
controller: _nameController,
|
||||
autofocus: true,
|
||||
maxLength: 20,
|
||||
decoration: InputDecoration(
|
||||
labelText: '이름 (20자 이내)',
|
||||
border: const OutlineInputBorder(),
|
||||
errorText: _dialogErrorMessage,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
// [🔥 수정] 버튼을 입력창 바로 아래 배치
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: _skipRankAndClose, child: const Text('건너뛰기')),
|
||||
const SizedBox(width: 10),
|
||||
ElevatedButton(
|
||||
onPressed: () => _submitRank(), child: const Text('랭킹 등록')),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/// 🔽 [신규] 랭킹 리스트 섹션 (기록 보기)
|
||||
Widget _buildRankingListSection(ThemeData theme) {
|
||||
if (_didSkipRank) {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text('랭킹 등록을 건너뛰었습니다.', style: theme.textTheme.titleMedium),
|
||||
const SizedBox(height: 10),
|
||||
Text('기록은 위 "나의 최종 기록"에서 확인 가능합니다.', style: theme.textTheme.bodyMedium),
|
||||
const SizedBox(height: 20),
|
||||
ElevatedButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: const Text('로비로 돌아가기'),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// 랭킹 리스트 (기존 로직과 유사)
|
||||
Widget topRankListWidget = _rankingList.isEmpty
|
||||
? const Center(child: Text("현재 랭킹이 없습니다."))
|
||||
? const Center(child: Text("등록된 랭킹이 없습니다."))
|
||||
: ListView.builder(
|
||||
itemCount: _rankingList.length,
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(), // SingleChildScrollView 내부이므로 필요
|
||||
itemBuilder: (context, index) {
|
||||
final rank = _rankingList[index];
|
||||
final bool isMe = rank.playerName == _submittedPlayerName;
|
||||
final String scoreText = widget.args.scoreFormatter(rank.primaryScore, rank.secondaryScore);
|
||||
|
||||
|
||||
return ListTile(
|
||||
selected: isMe,
|
||||
selectedTileColor: theme.primaryColor.withOpacity(0.1),
|
||||
@@ -160,7 +242,7 @@ class _GameCompletionScreenState extends State<GameCompletionScreen> {
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: ListTile(
|
||||
selected: true,
|
||||
selectedTileColor: theme.primaryColor.withOpacity(0.1),
|
||||
selectedTileColor: theme.colorScheme.secondary.withOpacity(0.1),
|
||||
leading: Text('$myRankNum.', style: const TextStyle(fontWeight: FontWeight.bold)),
|
||||
title: Text(myRank.playerName, style: const TextStyle(fontWeight: FontWeight.bold)),
|
||||
trailing: Text(scoreText, style: TextStyle(fontWeight: FontWeight.bold, color: theme.textTheme.bodyMedium?.color?.withOpacity(0.9))),
|
||||
@@ -169,62 +251,48 @@ class _GameCompletionScreenState extends State<GameCompletionScreen> {
|
||||
}
|
||||
}
|
||||
|
||||
Widget rankDisplaySection = Column(
|
||||
children: [
|
||||
if (_dialogErrorMessage != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8.0),
|
||||
child: Text(_dialogErrorMessage!, style: TextStyle(color: theme.colorScheme.error)),
|
||||
),
|
||||
Expanded(child: topRankListWidget),
|
||||
if (myRankWidget != null) ...[
|
||||
const Divider(height: 16, thickness: 1),
|
||||
myRankWidget,
|
||||
],
|
||||
],
|
||||
);
|
||||
|
||||
Widget nameEntryWidget = Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text('축하합니다! 랭킹에 등록할 이름을 입력하세요.', style: theme.textTheme.titleMedium),
|
||||
const SizedBox(height: 20),
|
||||
TextField(
|
||||
controller: _nameController,
|
||||
autofocus: true,
|
||||
maxLength: 20,
|
||||
decoration: InputDecoration(
|
||||
labelText: '이름 (20자 이내)',
|
||||
border: const OutlineInputBorder(),
|
||||
errorText: _dialogErrorMessage,
|
||||
),
|
||||
return Expanded(
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
if (_dialogErrorMessage != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8.0),
|
||||
child: Text(_dialogErrorMessage!, style: TextStyle(color: theme.colorScheme.error)),
|
||||
),
|
||||
topRankListWidget,
|
||||
if (myRankWidget != null) ...[
|
||||
const Divider(height: 16, thickness: 1),
|
||||
myRankWidget,
|
||||
],
|
||||
const SizedBox(height: 40),
|
||||
ElevatedButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: const Text('로비로 돌아가기'),
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// --- 상태에 따라 UI와 버튼 결정 ---
|
||||
Widget content;
|
||||
List<Widget> actions = [];
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
String titleText;
|
||||
Widget content;
|
||||
|
||||
if (_rankStep == _RankSubmissionStep.enterName) {
|
||||
titleText = '🎉 게임 완료!';
|
||||
content = nameEntryWidget;
|
||||
actions = [
|
||||
TextButton(onPressed: _closeScreen, child: const Text('나중에 하기')),
|
||||
ElevatedButton(onPressed: () => _submitRank(), child: const Text('랭킹 등록')),
|
||||
];
|
||||
}
|
||||
content = _buildNameEntrySection(theme); // 이름 입력 섹션
|
||||
}
|
||||
else if (_rankStep == _RankSubmissionStep.submitting) {
|
||||
titleText = '랭킹 등록 중...';
|
||||
content = const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
}
|
||||
else { // _RankSubmissionStep.showList
|
||||
titleText = '🏆 랭킹 (${widget.args.contextId})';
|
||||
content = rankDisplaySection;
|
||||
actions = [
|
||||
TextButton(onPressed: _closeScreen, child: const Text('닫기')),
|
||||
];
|
||||
titleText = _didSkipRank ? '✅ 기록 확인' : '🏆 랭킹 등록 완료';
|
||||
content = _buildRankingListSection(theme); // 랭킹 리스트 섹션
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
@@ -232,17 +300,29 @@ class _GameCompletionScreenState extends State<GameCompletionScreen> {
|
||||
title: Text(titleText),
|
||||
automaticallyImplyLeading: false,
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: content,
|
||||
),
|
||||
bottomNavigationBar: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: actions,
|
||||
body: SafeArea(
|
||||
child: Column(
|
||||
children: [
|
||||
// 1. [🔥 수정] 최종 기록 섹션 (스크롤과 분리된 최상단)
|
||||
_buildScoreWidget(theme),
|
||||
|
||||
// 2. [🔥 수정] 메인 컨텐츠 섹션
|
||||
if (_rankStep == _RankSubmissionStep.enterName || _rankStep == _RankSubmissionStep.submitting)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: content,
|
||||
)
|
||||
else
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: content,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// ❌ bottomNavigationBar는 제거됨
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user