import 'dart:developer'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; // ๐Ÿ‘ˆ [์ถ”๊ฐ€] import 'package:service_api/service_api.dart'; import '../models/game_result_args.dart'; // ์Šค๋„์ฟ /์ŠคํŒŒ์ด๋”์™€ ๋™์ผํ•œ enum enum _RankSubmissionStep { enterName, submitting, showList } class GameCompletionScreen extends StatefulWidget { final GameResultArgs args; const GameCompletionScreen({super.key, required this.args}); @override State createState() => _GameCompletionScreenState(); } class _GameCompletionScreenState extends State { // ์„œ๋น„์Šค ์ดˆ๊ธฐํ™” final PuzzleService _puzzleService = PuzzleService(); final IdentityService _identityService = IdentityService(); // ์ƒํƒœ ๋ณ€์ˆ˜ late final TextEditingController _nameController; _RankSubmissionStep _rankStep = _RankSubmissionStep.enterName; List _rankingList = []; GameRankWithRankNumber? _myRankResult; String? _dialogErrorMessage; String _submittedPlayerName = ""; @override void initState() { super.initState(); // ๐Ÿ”ฝ [์ˆ˜์ •] ์„ธ์…˜์—์„œ userName์„ ๊ฐ€์ ธ์˜ด // (์ด ํ™”๋ฉด์€ build ์ด์ „์— ํ˜ธ์ถœ๋˜๋ฏ€๋กœ 'read' ์‚ฌ์šฉ) final session = context.read().session; _nameController = TextEditingController(text: session?.userName ?? widget.args.userName); // ๐Ÿ”ฝ [์ˆ˜์ •] ๊ฒŒ์ŠคํŠธ๊ฐ€ ์•„๋‹ˆ๋ฉด, ์ด๋ฆ„ ์ž…๋ ฅ ๋‹จ๊ณ„๋ฅผ ๊ฑด๋„ˆ๋›ฐ๊ณ  ์ฆ‰์‹œ ๋“ฑ๋ก if (session != null && !session.isGuest) { _rankStep = _RankSubmissionStep.submitting; // build๊ฐ€ ์™„๋ฃŒ๋œ ํ›„ ๋“ฑ๋ก ์‹œ์ž‘ WidgetsBinding.instance.addPostFrameCallback((_) { // [์ค‘์š”] ์„ธ์…˜์˜ userName์œผ๋กœ ์ž๋™ ์ œ์ถœ _submitRank(autoSubmitName: session.userName); }); } } @override void dispose() { _nameController.dispose(); super.dispose(); } /// ๋žญํ‚น ๋“ฑ๋ก ๋กœ์ง (๊ณตํ†ตํ™”) // ๐Ÿ”ฝ [์ˆ˜์ •] _submitRank๊ฐ€ ์ž๋™ ์ œ์ถœ์šฉ ์ด๋ฆ„์„ ๋ฐ›๋„๋ก Future _submitRank({String? autoSubmitName}) async { String playerName; // ์ž๋™ ์ œ์ถœ(๋กœ๊ทธ์ธ ์ƒํƒœ)์ด ์•„๋‹ˆ๋ฉด(๊ฒŒ์ŠคํŠธ๋ฉด), ์ปจํŠธ๋กค๋Ÿฌ์—์„œ ์ด๋ฆ„์„ ๊ฐ€์ ธ์˜ด if (autoSubmitName == null) { playerName = _nameController.text.trim(); if (playerName.isEmpty) { setState(() { _dialogErrorMessage = "์ด๋ฆ„์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”."; }); return; } } else { playerName = autoSubmitName; } setState(() { _rankStep = _RankSubmissionStep.submitting; _submittedPlayerName = playerName; _dialogErrorMessage = null; }); final rankDto = UnifiedRankDto( userId: widget.args.userId, gameType: widget.args.gameType, contextId: widget.args.contextId, playerName: playerName, // ๐Ÿ‘ˆ [์ˆ˜์ •] primaryScore: widget.args.primaryScore, secondaryScore: widget.args.secondaryScore, ); try { // 1. ๋žญํ‚น ๋“ฑ๋ก final RankSubmissionResult result = await _puzzleService.submitRank(rankDto); // 2. ์ด๋ฆ„ ์ €์žฅ (๊ณตํ†ต) // [์ˆ˜์ •] ๊ฒŒ์ŠคํŠธ์ผ ๋•Œ๋งŒ ์ด๋ฆ„์„ ์ €์žฅ (์†Œ์…œ ๋กœ๊ทธ์ธ์€ ์ด๋ฏธ ์ด๋ฆ„์ด ์žˆ์Œ) if (autoSubmitName == null) { await _identityService.saveUserName(playerName); } // 3. ๊ฒŒ์ž„๋ณ„ ํ›„์† ์ฒ˜๋ฆฌ (๋ ˆ๋ฒจ ์ž ๊ธˆ ํ•ด์ œ ๋“ฑ) await widget.args.onProgressSave(playerName); setState(() { _rankingList = result.topRanks; _myRankResult = result.myRank; _rankStep = _RankSubmissionStep.showList; }); } catch (e) { log("!!! ๋žญํ‚น ๋“ฑ๋ก ์‹คํŒจ !!!", error: e); setState(() { _rankStep = _RankSubmissionStep.enterName; // ๐Ÿ”ฝ [์ˆ˜์ •] ๊ฒŒ์ŠคํŠธ๊ฐ€ ์•„๋‹ ๋•Œ ์‹คํŒจํ•˜๋ฉด, ์ด๋ฆ„ ์ž…๋ ฅ์ฐฝ ๋Œ€์‹  ๋ฆฌ์ŠคํŠธ๋กœ ๋ณด๋ƒ„ if (autoSubmitName != null) { _rankStep = _RankSubmissionStep.showList; } _dialogErrorMessage = e.toString().replaceFirst("Exception: ", ""); }); } } /// ๋‹ซ๊ธฐ ๋ฒ„ํŠผ ๋กœ์ง void _closeScreen() { // 1. ์ด ํŒ์—… ํ™”๋ฉด์„ ๋‹ซ๊ณ  Navigator.of(context).pop(); // 2. ์ด์ „ ํ™”๋ฉด(๊ฒŒ์ž„ ํ™”๋ฉด)์„ ๋‹ซ๋„๋ก ์ฝœ๋ฐฑ ํ˜ธ์ถœ widget.args.onScreenClose(); } @override Widget build(BuildContext context) { final theme = Theme.of(context); // --- UI ์„น์…˜ ์ •์˜ (์Šค๋„์ฟ /์ŠคํŒŒ์ด๋”์™€ ๋™์ผ) --- Widget topRankListWidget = _rankingList.isEmpty ? const Center(child: Text("ํ˜„์žฌ ๋žญํ‚น์ด ์—†์Šต๋‹ˆ๋‹ค.")) : ListView.builder( itemCount: _rankingList.length, shrinkWrap: true, 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), leading: Text('${index + 1}.', style: const TextStyle(fontWeight: FontWeight.bold)), title: Text(rank.playerName, style: TextStyle(fontWeight: isMe ? FontWeight.bold : FontWeight.normal)), trailing: Text( scoreText, style: TextStyle( fontWeight: FontWeight.bold, color: theme.textTheme.bodyMedium?.color?.withOpacity(0.9) ) ), ); }, ); Widget? myRankWidget; if (_myRankResult != null) { final myRank = _myRankResult!.rankData; final myRankNum = _myRankResult!.rankNumber; bool isMeInTop10 = _rankingList.any( (topRank) => topRank.playerName == myRank.playerName ); if (!isMeInTop10) { // [์ˆ˜์ •] ์ ์ˆ˜ ํฌ๋งทํ„ฐ๋ฅผ ์ฃผ์ž…๋ฐ›์€ ํ•จ์ˆ˜๋กœ ๋Œ€์ฒด final String scoreText = widget.args.scoreFormatter( myRank.primaryScore, myRank.secondaryScore ); myRankWidget = Padding( padding: const EdgeInsets.only(top: 8.0), child: ListTile( selected: true, selectedTileColor: theme.primaryColor.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) ) ), ), ); } } 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, // [์ˆ˜์ •] 10 -> 20 decoration: InputDecoration( labelText: '์ด๋ฆ„ (20์ž ์ด๋‚ด)', border: const OutlineInputBorder(), errorText: _dialogErrorMessage, ), ), ], ); // --- ์ƒํƒœ์— ๋”ฐ๋ผ UI์™€ ๋ฒ„ํŠผ ๊ฒฐ์ • --- Widget content; List actions = []; String titleText; if (_rankStep == _RankSubmissionStep.enterName) { titleText = '๐ŸŽ‰ ๊ฒŒ์ž„ ์™„๋ฃŒ!'; content = nameEntryWidget; actions = [ TextButton( onPressed: _closeScreen, // ๋‹ซ๊ธฐ child: const Text('๋‚˜์ค‘์— ํ•˜๊ธฐ'), ), ElevatedButton( onPressed: () => _submitRank(), // ๐Ÿ‘ˆ [์ˆ˜์ •] ์ธ์ž ์—†์ด ํ˜ธ์ถœ child: const Text('๋žญํ‚น ๋“ฑ๋ก'), ), ]; } 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('๋‹ซ๊ธฐ'), ) ]; } // [์ˆ˜์ •] AlertDialog๊ฐ€ ์•„๋‹Œ ์ „์ฒด ํ™”๋ฉด Scaffold๋กœ ๋ณ€๊ฒฝ return Scaffold( appBar: AppBar( 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, ), ), ); } }