-
Notifications
You must be signed in to change notification settings - Fork 619
Open
Labels
type-bugIncorrect behavior (everything from a crash to more subtle misbehavior)Incorrect behavior (everything from a crash to more subtle misbehavior)
Description
Relevant console output or errors
How did this error happen?
thank you
Relevant Dart code
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'dart:async';
// ---------------------------------------------------------
// 🚀 ONFIELD CHAMELEON - DUAL THEME & DYNAMIC VIEW
// ---------------------------------------------------------
void main() {
runApp(const OnFieldApp());
}
// Global Theme State Notifier
ValueNotifier<bool> isPurpleTheme = ValueNotifier<bool>(false);
class OnFieldApp extends StatelessWidget {
const OnFieldApp({super.key});
@override
Widget build(BuildContext context) {
return ValueListenableBuilder<bool>(
valueListenable: isPurpleTheme,
builder: (context, isPurple, child) {
// Define Colors based on mode
Color bgColor = isPurple ? const Color(0xFF0F0518) : const Color(0xFF0F172A); // Deep Purple vs Deep Navy
Color cardColor = isPurple ? const Color(0xFF1A0B2E) : const Color(0xFF1E293B);
Color accentColor = isPurple ? const Color(0xFF00E5FF) : const Color(0xFF39FF14); // Cyan vs Neon Green
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: bgColor,
primaryColor: accentColor,
cardColor: cardColor,
dialogBackgroundColor: cardColor,
colorScheme: ColorScheme.dark(primary: accentColor, secondary: isPurple ? Colors.purpleAccent : Colors.greenAccent),
textTheme: const TextTheme(bodyMedium: TextStyle(fontFamily: 'Verdana')),
),
home: const MainMatchScreen(),
);
},
);
}
}
class MainMatchScreen extends StatelessWidget {
const MainMatchScreen({super.key});
@override
Widget build(BuildContext context) {
var theme = Theme.of(context);
var accent = theme.primaryColor;
return DefaultTabController(
length: 3,
child: Scaffold(
appBar: AppBar(
backgroundColor: theme.scaffoldBackgroundColor,
elevation: 0,
title: Text(">> ONFIELD MANAGER <<",
style: TextStyle(color: accent, fontWeight: FontWeight.bold, fontSize: 16, letterSpacing: 1.5)),
centerTitle: true,
actions: [
// THEME TOGGLE BUTTON
IconButton(
icon: const Icon(Icons.color_lens),
color: accent,
tooltip: "Change Theme",
onPressed: () {
isPurpleTheme.value = !isPurpleTheme.value;
},
),
const SizedBox(width: 10),
],
bottom: TabBar(
indicatorColor: accent,
labelColor: Colors.white,
unselectedLabelColor: Colors.grey,
tabs: const [
Tab(text: "MATCH 1"),
Tab(text: "MATCH 2"),
Tab(text: "MATCH 3"),
],
),
),
body: const TabBarView(
physics: NeverScrollableScrollPhysics(),
children: [
SingleMatchInstance(matchId: "1"),
SingleMatchInstance(matchId: "2"),
SingleMatchInstance(matchId: "3"),
],
),
),
);
}
}
class SingleMatchInstance extends StatefulWidget {
final String matchId;
const SingleMatchInstance({super.key, required this.matchId});
@override
State<SingleMatchInstance> createState() => _SingleMatchInstanceState();
}
class _SingleMatchInstanceState extends State<SingleMatchInstance> with AutomaticKeepAliveClientMixin {
@override
bool get wantKeepAlive => true;
// ⏱️ State
Timer? _timer;
int _seconds = 0;
int _matchStage = 0; // 0 = Pre-Match (Lists Visible), 1+ = Live (Dashboard)
String _btnText = "KICK OFF"; // Initial Text
// 📊 Stats
Map<String, int> statsA = {'GOALS': 0, 'SHOTS': 0, 'CORNERS': 0, 'FOULS': 0, 'OFFSIDES': 0, 'SAVES': 0};
Map<String, int> statsB = {'GOALS': 0, 'SHOTS': 0, 'CORNERS': 0, 'FOULS': 0, 'OFFSIDES': 0, 'SAVES': 0};
// 📝 Logs
List<String> colPaid = [];
List<String> colGoals = [];
List<String> colAssists = [];
List<String> colCards = [];
// 👥 Players (15 Players per team)
List<String> playersA = List.generate(15, (i) => "Player A${i + 1}");
List<String> playersB = List.generate(15, (i) => "Player B${i + 1}");
List<bool> paidA = List.filled(15, false);
List<bool> paidB = List.filled(15, false);
// --- Logic ---
void _handleMatchCycle() {
setState(() {
if (_matchStage == 0) {
// PRE-MATCH -> START MATCH
_matchStage = 1;
_btnText = "PAUSE (1st)";
_startTimer();
} else if (_matchStage == 1) {
// PAUSE / HALF TIME
_matchStage = 2; _btnText = "START 2nd HALF"; _stopTimer();
} else if (_matchStage == 2) {
// START 2nd HALF
_matchStage = 3; _btnText = "END MATCH"; _startTimer();
} else if (_matchStage == 3) {
// END MATCH
_matchStage = 4; _btnText = "RESET MATCH"; _stopTimer();
} else {
// RESET
_resetAll();
}
});
}
void _startTimer() {
_timer?.cancel();
_timer = Timer.periodic(const Duration(seconds: 1), (_) => setState(() => _seconds++));
}
void _stopTimer() => _timer?.cancel();
String get _timeStr {
int m = _seconds ~/ 60;
int s = _seconds % 60;
return "${m.toString().padLeft(2,'0')}:${s.toString().padLeft(2,'0')}";
}
void _resetAll() {
_matchStage = 0; _btnText = "KICK OFF"; _seconds = 0;
statsA.updateAll((k,v)=>0); statsB.updateAll((k,v)=>0);
colPaid.clear(); colGoals.clear(); colAssists.clear(); colCards.clear();
// Keep names, reset paid? Optional. Let's keep names reset paid status visually
paidA = List.filled(15, false); paidB = List.filled(15, false);
}
// --- Actions ---
void _handleGoal() {
if (_matchStage == 0 || _matchStage == 4) return;
_showTeamSelectDialog("Who Scored?", (isTeamA) {
_showPlayerSelectDialog("Scorer ⚽", isTeamA, (scorer) {
_showPlayerSelectDialog("Assist 🎯", isTeamA, (assist) {
setState(() {
if (isTeamA) statsA['GOALS'] = statsA['GOALS']! + 1; else statsB['GOALS'] = statsB['GOALS']! + 1;
colGoals.insert(0, "$scorer ($_timeStr)");
colAssists.insert(0, "$assist");
});
}, includeNone: true);
});
});
}
void _handleCard(String type) {
if (_matchStage == 0 || _matchStage == 4) return;
_showTeamSelectDialog("Select Team", (isTeamA) {
_showPlayerSelectDialog("$type for:", isTeamA, (player) {
setState(() {
String icon = type == "YELLOW" ? "🟨" : "🟥";
colCards.insert(0, "$icon $player ($_timeStr)");
});
});
});
}
void _handleGenericStat(String statKey) {
if (_matchStage == 0 || _matchStage == 4) return;
showDialog(context: context, builder: (ctx) => AlertDialog(
title: Text("$statKey Team?", style: const TextStyle(color: Colors.white)),
content: Row(children: [
Expanded(child: _teamBtn("TEAM A", Theme.of(context).primaryColor, () { setState(() => statsA[statKey] = statsA[statKey]! + 1); Navigator.pop(ctx); })),
const SizedBox(width: 10),
Expanded(child: _teamBtn("TEAM B", Colors.white, () { setState(() => statsB[statKey] = statsB[statKey]! + 1); Navigator.pop(ctx); })),
]),
));
}
void _handleSave() {
if (_matchStage == 0 || _matchStage == 4) return;
showDialog(context: context, builder: (ctx) => AlertDialog(
title: const Text("Who Saved? 🧤", style: TextStyle(color: Colors.white)),
content: Column(mainAxisSize: MainAxisSize.min, children: [
ListTile(title: Text("Team A GK", style: TextStyle(color: Theme.of(context).primaryColor)), onTap: () { setState(() => statsA['SAVES'] = statsA['SAVES']! + 1); Navigator.pop(ctx); }),
ListTile(title: const Text("Team B GK", style: TextStyle(color: Colors.white)), onTap: () { setState(() => statsB['SAVES'] = statsB['SAVES']! + 1); Navigator.pop(ctx); }),
]),
));
}
// --- Dialogs ---
void _showTeamSelectDialog(String title, Function(bool) onSelect) {
showDialog(context: context, builder: (ctx) => AlertDialog(
title: Text(title, style: const TextStyle(color: Colors.white)),
content: Row(children: [
Expanded(child: _teamBtn("TEAM A", Theme.of(context).primaryColor, () { Navigator.pop(ctx); onSelect(true); })),
const SizedBox(width: 10),
Expanded(child: _teamBtn("TEAM B", Colors.white, () { Navigator.pop(ctx); onSelect(false); })),
]),
));
}
void _showPlayerSelectDialog(String title, bool isTeamA, Function(String) onSelect, {bool includeNone = false}) {
List<String> list = isTeamA ? playersA : playersB;
showModalBottomSheet(context: context, backgroundColor: Theme.of(context).cardColor, builder: (ctx) => Column(children: [
Padding(padding: const EdgeInsets.all(16), child: Text(title, style: TextStyle(color: Theme.of(context).primaryColor, fontSize: 18, fontWeight: FontWeight.bold))),
Expanded(child: ListView.builder(itemCount: list.length + (includeNone?1:0), itemBuilder: (ctx, i) {
if (includeNone && i==0) return ListTile(title: const Text("None", style: TextStyle(color: Colors.grey)), onTap: (){Navigator.pop(ctx); onSelect("None");});
int idx = includeNone ? i-1 : i;
return ListTile(title: Text(list[idx], style: const TextStyle(color: Colors.white)), onTap: (){Navigator.pop(ctx); onSelect(list[idx]);});
})),
]));
}
void _editName(int i, bool isTeamA) {
TextEditingController c = TextEditingController(text: isTeamA ? playersA[i] : playersB[i]);
showDialog(context: context, builder: (ctx) => AlertDialog(
title: const Text("Edit Name"),
content: TextField(controller: c, autofocus: true, style: const TextStyle(color: Colors.white)),
actions: [TextButton(onPressed: (){
setState(() { if(isTeamA) playersA[i] = c.text; else playersB[i] = c.text; });
Navigator.pop(ctx);
}, child: Text("SAVE", style: TextStyle(color: Theme.of(context).primaryColor)))]
));
}
void _togglePaid(int i, bool isTeamA) {
setState(() {
String name = isTeamA ? playersA[i] : playersB[i];
if (isTeamA) { paidA[i] = !paidA[i]; if(paidA[i]) colPaid.add(name); else colPaid.remove(name); }
else { paidB[i] = !paidB[i]; if(paidB[i]) colPaid.add(name); else colPaid.remove(name); }
});
}
void _printStats() {
String txt = "MATCH REPORT\n$statsA\n$statsB\nGoals:\n${colGoals.join('\n')}\nPaid:\n${colPaid.join(', ')}";
showDialog(context: context, builder: (ctx) => AlertDialog(content: SingleChildScrollView(child: Text(txt)), actions: [TextButton(onPressed:()=>Navigator.pop(ctx), child:const Text("OK"))]));
}
// --- UI COMPONENTS ---
Widget _teamBtn(String txt, Color clr, VoidCallback tap) => ElevatedButton(style: ElevatedButton.styleFrom(backgroundColor: clr.withOpacity(0.2), foregroundColor: clr), onPressed: tap, child: Text(txt));
@override
Widget build(BuildContext context) {
super.build(context);
var accent = Theme.of(context).primaryColor;
return Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
// 1. TOP BAR (Timer + Status + Actions)
_buildTopBar(accent),
const SizedBox(height: 10),
// 2. DYNAMIC CONTENT (Pre-Match VS Live Match)
Expanded(
flex: 3,
child: _matchStage == 0
? _buildPreMatchView(accent) // 🟢 Show Lists (15 players)
: _buildLiveDashboard(accent), // 🔴 Show Stats/Buttons
),
const SizedBox(height: 10),
// 3. BOTTOM COLUMNS (Always Visible)
Expanded(flex: 2, child: _buildBottomColumns(accent)),
],
),
);
}
Widget _buildTopBar(Color accent) {
return Container(
height: 55,
padding: const EdgeInsets.symmetric(horizontal: 16),
decoration: BoxDecoration(color: Theme.of(context).cardColor, borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.white10)),
child: Row(
children: [
// Status
_statusItem("1st", _matchStage >= 1, accent), const Icon(Icons.arrow_right, color: Colors.grey),
_statusItem("2nd", _matchStage >= 3, accent), const Icon(Icons.arrow_right, color: Colors.grey),
_statusItem("End", _matchStage == 4, accent),
const Spacer(),
Text(_timeStr, style: TextStyle(color: accent, fontSize: 26, fontWeight: FontWeight.bold, fontFamily: 'monospace')),
const Spacer(),
IconButton(icon: const Icon(Icons.print, color: Colors.blue), onPressed: _printStats),
IconButton(icon: const Icon(Icons.refresh, color: Colors.red), onPressed: _resetAll),
],
),
);
}
Widget _statusItem(String t, bool a, Color c) => Text(t, style: TextStyle(color: a ? c : Colors.grey, fontWeight: a ? FontWeight.bold : FontWeight.normal));
// --- VIEW 1: PRE-MATCH (Lists) ---
Widget _buildPreMatchView(Color accent) {
return Column(
children: [
// Kick Off Button Large
SizedBox(
width: double.infinity, height: 50,
child: ElevatedButton(
style: ElevatedButton.styleFrom(backgroundColor: accent, foregroundColor: Colors.black),
onPressed: _handleMatchCycle,
child: Text(_btnText, style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 18)),
),
),
const SizedBox(height: 10),
Expanded(
child: Row(
children: [
Expanded(child: _buildEditableList(true, accent)),
const SizedBox(width: 10),
Expanded(child: _buildEditableList(false, Colors.white)),
],
),
),
],
);
}
Widget _buildEditableList(bool isTeamA, Color color) {
List<String> list = isTeamA ? playersA : playersB;
List<bool> paid = isTeamA ? paidA : paidB;
return Container(
decoration: BoxDecoration(color: Colors.black26, borderRadius: BorderRadius.circular(12)),
child: Column(
children: [
Padding(padding: const EdgeInsets.all(8), child: Text(isTeamA ? "TEAM A (15)" : "TEAM B (15)", style: TextStyle(color: color, fontWeight: FontWeight.bold))),
Expanded(
child: ListView.builder(
itemCount: 15, // 15 Players
itemBuilder: (ctx, i) => Container(
margin: const EdgeInsets.symmetric(vertical: 2, horizontal: 8),
child: Row(
children: [
// Paid Checkbox
GestureDetector(
onTap: () => _togglePaid(i, isTeamA),
child: Container(
width: 20, height: 20,
decoration: BoxDecoration(
color: paid[i] ? Colors.green : Colors.transparent,
border: Border.all(color: paid[i] ? Colors.green : Colors.grey),
borderRadius: BorderRadius.circular(4),
),
child: paid[i] ? const Icon(Icons.check, size: 16, color: Colors.black) : null,
),
),
const SizedBox(width: 8),
// Editable Name
Expanded(
child: InkWell(
onTap: () => _editName(i, isTeamA),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 6),
child: Text(list[i], style: const TextStyle(color: Colors.white, fontSize: 13)),
),
),
),
],
),
),
),
),
],
),
);
}
// --- VIEW 2: LIVE MATCH (Dashboard) ---
Widget _buildLiveDashboard(Color accent) {
return Row(
children: [
// Left: Stats
Expanded(
flex: 4,
child: Container(
decoration: BoxDecoration(color: Colors.black26, borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.white10)),
child: Column(
children: [
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(color: accent.withOpacity(0.1), borderRadius: const BorderRadius.vertical(top: Radius.circular(12))),
child: Row(mainAxisAlignment: MainAxisAlignment.spaceAround, children: [
Text("TEAM A", style: TextStyle(color: accent, fontWeight: FontWeight.bold)),
Text("${statsA['GOALS']} - ${statsB['GOALS']}", style: const TextStyle(fontSize: 36, fontWeight: FontWeight.bold, color: Colors.white)),
const Text("TEAM B", style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold)),
]),
),
Expanded(child: ListView(
padding: const EdgeInsets.all(8),
children: [
_statRow("SHOTS", statsA['SHOTS']!, statsB['SHOTS']!, Colors.blue),
_statRow("FOULS", statsA['FOULS']!, statsB['FOULS']!, Colors.orange),
_statRow("CORNERS", statsA['CORNERS']!, statsB['CORNERS']!, Colors.white),
_statRow("OFFSIDES", statsA['OFFSIDES']!, statsB['OFFSIDES']!, Colors.purple),
_statRow("SAVES", statsA['SAVES']!, statsB['SAVES']!, Colors.teal),
],
)),
],
),
),
),
const SizedBox(width: 10),
// Right: Buttons & Control
Expanded(
flex: 5,
child: Column(
children: [
SizedBox(
width: double.infinity, height: 50,
child: ElevatedButton(
style: ElevatedButton.styleFrom(backgroundColor: _matchStage%2!=0 ? Colors.orange : accent, foregroundColor: Colors.black),
onPressed: _handleMatchCycle,
child: Text(_btnText, style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16)),
),
),
const SizedBox(height: 8),
Expanded(
child: GridView.count(
crossAxisCount: 4, crossAxisSpacing: 6, mainAxisSpacing: 6, childAspectRatio: 1.1,
children: [
_actionBtn("FOUL", Colors.orange, Icons.sports_handball, ()=>_handleGenericStat('FOULS')),
_actionBtn("CORNER", Colors.white, Icons.flag, ()=>_handleGenericStat('CORNERS')),
_actionBtn("SHOT", Colors.blue, Icons.sports_soccer, ()=>_handleGenericStat('SHOTS')),
_actionBtn("OFFSIDE", Colors.purple, Icons.do_not_step, ()=>_handleGenericStat('OFFSIDES')),
_actionBtn("GOAL", accent, Icons.sports_soccer, _handleGoal),
_actionBtn("SAVE", Colors.teal, Icons.back_hand, _handleSave),
_actionBtn("YELLOW", Colors.yellow, Icons.style, ()=>_handleCard('YELLOW')),
_actionBtn("RED", Colors.red, Icons.style, ()=>_handleCard('RED')),
],
),
),
],
),
),
],
);
}
Widget _statRow(String lbl, int v1, int v2, Color c) {
return Padding(padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 12), child: Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
Text("$v1", style: TextStyle(color: c, fontWeight: FontWeight.bold, fontSize: 16)),
Text(lbl, style: const TextStyle(color: Colors.grey, fontSize: 10)),
Text("$v2", style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 16)),
]));
}
Widget _actionBtn(String l, Color c, IconData i, VoidCallback t) {
return ElevatedButton(
style: ElevatedButton.styleFrom(backgroundColor: c.withOpacity(0.15), foregroundColor: c, padding: EdgeInsets.zero, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8), side: BorderSide(color: c.withOpacity(0.3)))),
onPressed: t,
child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [Icon(i, size: 20), Text(l, style: const TextStyle(fontSize: 10, fontWeight: FontWeight.bold))]),
);
}
Widget _buildBottomColumns(Color accent) {
return Container(
decoration: BoxDecoration(color: Colors.black45, borderRadius: BorderRadius.circular(12)),
child: Row(children: [
_logCol("PAID 💰", colPaid, Colors.green),
const VerticalDivider(width: 1, color: Colors.grey),
_logCol("GOALS ⚽", colGoals, accent),
const VerticalDivider(width: 1, color: Colors.grey),
_logCol("ASSISTS 🎯", colAssists, Colors.blue),
const VerticalDivider(width: 1, color: Colors.grey),
_logCol("CARDS 🟥", colCards, Colors.red),
]),
);
}
Widget _logCol(String t, List<String> d, Color c) {
return Expanded(child: Column(children: [
Container(width: double.infinity, padding: const EdgeInsets.all(6), color: Colors.white10, child: Text(t, textAlign: TextAlign.center, style: TextStyle(color: c, fontWeight: FontWeight.bold, fontSize: 10))),
Expanded(child: ListView.builder(itemCount: d.length, itemBuilder: (ctx, i) => Text(d[i], textAlign: TextAlign.center, style: const TextStyle(fontSize: 11, color: Colors.white70)))),
]));
}
}What OS did this error occur on?
No response
What browser(s) did you experience this issue on?
No response
Do you have any additional information?
No response
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
type-bugIncorrect behavior (everything from a crash to more subtle misbehavior)Incorrect behavior (everything from a crash to more subtle misbehavior)