305 lines
9.1 KiB
Dart
305 lines
9.1 KiB
Dart
import 'package:flutter/material.dart';
|
|
import '../services/formation_generator.dart';
|
|
import '../data/formation_positions.dart';
|
|
|
|
class ResultScreen extends StatefulWidget {
|
|
final ConstraintMode mode;
|
|
final bool rareStyleOn;
|
|
final String? selectedLeague;
|
|
final String? selectedCardType;
|
|
final List<Map<String, dynamic>> playerPool;
|
|
|
|
// チームスタイル重み
|
|
final Map<String, int> teamStyleWeights;
|
|
|
|
const ResultScreen({
|
|
super.key,
|
|
required this.mode,
|
|
required this.rareStyleOn,
|
|
required this.selectedLeague,
|
|
required this.selectedCardType,
|
|
required this.playerPool,
|
|
required this.teamStyleWeights,
|
|
});
|
|
|
|
@override
|
|
State<ResultScreen> createState() => _ResultScreenState();
|
|
}
|
|
|
|
class _ResultScreenState extends State<ResultScreen> {
|
|
Map<String, dynamic>? _result;
|
|
List<Map<String, String>> _visiblePlayers = [];
|
|
bool _isAnimating = false;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_reroll();
|
|
}
|
|
|
|
Future<void> _reroll() async {
|
|
setState(() {
|
|
_result = null;
|
|
_visiblePlayers = [];
|
|
_isAnimating = true;
|
|
});
|
|
|
|
final res = FormationGenerator.generate(
|
|
mode: widget.mode,
|
|
rareStyleOn: widget.rareStyleOn,
|
|
selectedLeague: widget.selectedLeague,
|
|
selectedCardType: widget.selectedCardType,
|
|
playerPool: widget.playerPool,
|
|
teamStyleWeights: widget.teamStyleWeights,
|
|
);
|
|
|
|
setState(() => _result = res);
|
|
|
|
final players = (res['positions'] as List).cast<dynamic>();
|
|
for (int i = 0; i < players.length; i++) {
|
|
await Future.delayed(const Duration(milliseconds: 140));
|
|
setState(() {
|
|
_visiblePlayers.add(Map<String, String>.from(players[i]));
|
|
});
|
|
}
|
|
|
|
setState(() => _isAnimating = false);
|
|
}
|
|
|
|
// =========================
|
|
// タップでフルネーム表示
|
|
// =========================
|
|
void _showPlayerNameDialog(String fullName) {
|
|
showDialog(
|
|
context: context,
|
|
builder: (_) => AlertDialog(
|
|
title: const Text('選手名'),
|
|
content: Text(
|
|
fullName,
|
|
style: const TextStyle(fontSize: 18),
|
|
),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () => Navigator.pop(context),
|
|
child: const Text('閉じる'),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
// ピッチ上は名字だけ(本家っぽい)
|
|
String _displayName(String fullName) {
|
|
final name = fullName.trim();
|
|
if (name.isEmpty) return '';
|
|
if (!name.contains(' ')) return name;
|
|
return name.split(RegExp(r'\s+')).last;
|
|
}
|
|
|
|
// =========================
|
|
// 色分け
|
|
// =========================
|
|
Color _getColor(String pos) {
|
|
if (pos == 'GK') return Colors.orange;
|
|
if (['CB', 'LSB', 'RSB'].contains(pos)) return Colors.blue;
|
|
if (['DMF', 'CMF', 'OMF', 'LMF', 'RMF'].contains(pos)) return Colors.green;
|
|
if (['CF', 'ST', 'RWG', 'LWG'].contains(pos)) return Colors.red;
|
|
return Colors.grey;
|
|
}
|
|
|
|
// =========================
|
|
// カード(タップ対応)
|
|
// =========================
|
|
Widget _buildPlayerCard(Map<String, String> data) {
|
|
final pos = data['pos'] ?? '';
|
|
final style = data['style'] ?? '';
|
|
final fullName = data['name'] ?? '';
|
|
|
|
final color = _getColor(pos);
|
|
|
|
// poolモードは name が主役、それ以外は style が主役
|
|
final middleText = (widget.mode == ConstraintMode.pool)
|
|
? (fullName.isEmpty ? '未登録' : _displayName(fullName))
|
|
: (_displayName(fullName));
|
|
|
|
final bottomText = (widget.mode == ConstraintMode.pool) ? '' : style;
|
|
|
|
return GestureDetector(
|
|
onTap: fullName.trim().isEmpty ? null : () => _showPlayerNameDialog(fullName),
|
|
child: Container(
|
|
width: 92,
|
|
height: 74,
|
|
decoration: BoxDecoration(
|
|
color: color.withOpacity(0.9),
|
|
borderRadius: BorderRadius.circular(10),
|
|
border: Border.all(color: Colors.white, width: 1),
|
|
),
|
|
padding: const EdgeInsets.all(6),
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Text(
|
|
pos,
|
|
style: const TextStyle(
|
|
color: Colors.white,
|
|
fontSize: 11,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
const SizedBox(height: 2),
|
|
Text(
|
|
middleText,
|
|
textAlign: TextAlign.center,
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
style: TextStyle(
|
|
color: (widget.mode == ConstraintMode.pool && fullName.isEmpty)
|
|
? Colors.white70
|
|
: Colors.white,
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w800,
|
|
),
|
|
),
|
|
const SizedBox(height: 2),
|
|
Text(
|
|
bottomText,
|
|
textAlign: TextAlign.center,
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
style: const TextStyle(
|
|
color: Colors.white,
|
|
fontSize: 10,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// =========================
|
|
// ピッチ配置
|
|
// =========================
|
|
Widget _buildPitch(String formation, List<Map<String, String>> roles) {
|
|
final layout = getFormationLayout(formation);
|
|
|
|
return LayoutBuilder(
|
|
builder: (context, constraints) {
|
|
final width = constraints.maxWidth;
|
|
final height = constraints.maxHeight;
|
|
const cardW = 92.0;
|
|
const cardH = 74.0;
|
|
|
|
final children = <Widget>[
|
|
Container(
|
|
width: width,
|
|
height: height,
|
|
decoration: BoxDecoration(
|
|
gradient: LinearGradient(
|
|
colors: [Colors.green.shade800, Colors.green.shade700],
|
|
begin: Alignment.topCenter,
|
|
end: Alignment.bottomCenter,
|
|
),
|
|
borderRadius: BorderRadius.circular(12),
|
|
border: Border.all(color: Colors.white54, width: 2),
|
|
),
|
|
),
|
|
];
|
|
|
|
int index = 0;
|
|
for (int row = 0; row < layout.length; row++) {
|
|
for (int col = 0; col < layout[row].length; col++) {
|
|
if (index >= roles.length) break;
|
|
|
|
final data = roles[index++];
|
|
final xNorm = layout[row][col]['x'] ?? 0.5;
|
|
final yNorm = layout[row][col]['y'] ?? 0.5;
|
|
|
|
children.add(
|
|
Positioned(
|
|
left: width * xNorm - cardW / 2,
|
|
top: height * yNorm - cardH / 2,
|
|
child: _buildPlayerCard(data),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
return Stack(children: children);
|
|
},
|
|
);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final formation = _result?['formation'] as String?;
|
|
final label = _result?['constraintLabel'] as String?;
|
|
final teamStyle = _result?['teamStyle'] as String?;
|
|
|
|
return Scaffold(
|
|
backgroundColor: Colors.green[900],
|
|
appBar: AppBar(
|
|
title: const Text('結果'),
|
|
backgroundColor: Colors.green[900],
|
|
),
|
|
body: Column(
|
|
children: [
|
|
const SizedBox(height: 10),
|
|
if (formation != null)
|
|
Column(
|
|
children: [
|
|
Text(
|
|
'フォーメーション: $formation',
|
|
style: const TextStyle(
|
|
color: Colors.white,
|
|
fontSize: 18,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
if (label != null)
|
|
Text(label, style: const TextStyle(color: Colors.white70)),
|
|
if (teamStyle != null)
|
|
Text('チームスタイル: $teamStyle',
|
|
style: const TextStyle(color: Colors.white70)),
|
|
const SizedBox(height: 4),
|
|
const Text(
|
|
'※カードをタップでフルネーム表示',
|
|
style: TextStyle(color: Colors.white54, fontSize: 12),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 10),
|
|
Expanded(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(8),
|
|
child: formation == null
|
|
? const Center(
|
|
child: Text(
|
|
'抽選中…',
|
|
style: TextStyle(color: Colors.white70, fontSize: 16),
|
|
),
|
|
)
|
|
: _buildPitch(formation, _visiblePlayers),
|
|
),
|
|
),
|
|
Padding(
|
|
padding: const EdgeInsets.fromLTRB(12, 0, 12, 16),
|
|
child: SizedBox(
|
|
width: double.infinity,
|
|
child: ElevatedButton.icon(
|
|
onPressed: _isAnimating ? null : _reroll,
|
|
icon: const Icon(Icons.shuffle),
|
|
label: const Text('再抽選'),
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: Colors.white,
|
|
foregroundColor: Colors.green[900],
|
|
padding: const EdgeInsets.symmetric(vertical: 14),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|