Compare commits
4 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b082e78eec | |||
| dc7de89690 | |||
| 1c3b52723f | |||
| 9327b6120f |
9 changed files with 747 additions and 223 deletions
10
README.md
10
README.md
|
|
@ -1,5 +1,4 @@
|
||||||

|

|
||||||
<sub>WIP Logo</sub>
|
|
||||||
|
|
||||||
# Simple FRC scouting app. Developed by me for Laser Robotics and the FRC community!
|
# Simple FRC scouting app. Developed by me for Laser Robotics and the FRC community!
|
||||||
|
|
||||||
|
|
@ -9,12 +8,11 @@ Input your team number and event, and start scouting! When you're done export yo
|
||||||
|
|
||||||
* Better and updated scouting questions, these are examples from Reefscape
|
* Better and updated scouting questions, these are examples from Reefscape
|
||||||
* Match scouting
|
* Match scouting
|
||||||
* Online accessible version
|
|
||||||
|
|
||||||
# Some extra notes
|
# Some extra notes
|
||||||
|
|
||||||
* This is a side project by a me and is my first Flutter project. Please leave suggestions and tips about what I could do better!
|
* This is a side project by me and is my first Flutter project. Please leave suggestions and tips about what I could do better!
|
||||||
* I am providing APK's, but it may not be the most up to date version.
|
* I am providing APK's, but it may not be the most up-to-date version.
|
||||||
* To run on iOS you will still need to compile it and install with Xcode
|
* To run on iOS you will still need to compile it and install with Xcode
|
||||||
|
|
||||||
# How to contribute
|
# How to contribute
|
||||||
|
|
@ -28,4 +26,4 @@ Input your team number and event, and start scouting! When you're done export yo
|
||||||
1. Run the command `flutter run` in your terminal while inside project folder.
|
1. Run the command `flutter run` in your terminal while inside project folder.
|
||||||
2. Open the app in a browser, on your phone, or as a computer app.
|
2. Open the app in a browser, on your phone, or as a computer app.
|
||||||
|
|
||||||
* If you plan to test on an iPhone, you must have a Apple Developer account and a MacOS device with Xcode.
|
* If you plan to test on an iPhone, you must have an Apple Developer account and a macOS device with Xcode.
|
||||||
|
|
@ -28,6 +28,8 @@ android {
|
||||||
targetSdk = flutter.targetSdkVersion
|
targetSdk = flutter.targetSdkVersion
|
||||||
versionCode = flutter.versionCode
|
versionCode = flutter.versionCode
|
||||||
versionName = flutter.versionName
|
versionName = flutter.versionName
|
||||||
|
val fileName = "${rootProject.name}_v${versionName}_${versionCode}"
|
||||||
|
setProperty("archivesBaseName", fileName)
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
|
|
|
||||||
136
lib/eventadder.dart
Normal file
136
lib/eventadder.dart
Normal file
|
|
@ -0,0 +1,136 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
class EventAdder extends StatefulWidget {
|
||||||
|
const EventAdder({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<EventAdder> createState() => _EventAdderState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _EventAdderState extends State<EventAdder> {
|
||||||
|
final _customEventNameController = TextEditingController();
|
||||||
|
final _customEventCodeController = TextEditingController();
|
||||||
|
List<String> customEventNames = [];
|
||||||
|
List<String> customEventCodes = [];
|
||||||
|
final _formKey = GlobalKey<FormState>();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_loadEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _loadEvents() async {
|
||||||
|
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||||
|
setState(() {
|
||||||
|
customEventNames = prefs.getStringList('custom_event_names') ?? [];
|
||||||
|
customEventCodes = prefs.getStringList('custom_event_codes') ?? [];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _saveEvent() async {
|
||||||
|
if (!_formKey.currentState!.validate()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||||
|
final String eventName = _customEventNameController.text;
|
||||||
|
String eventCode;
|
||||||
|
|
||||||
|
if (_customEventCodeController.text.isEmpty) {
|
||||||
|
eventCode = '${DateTime.now().year.toString()}${eventName.replaceAll(' ', '').substring(0,4)}';
|
||||||
|
} else {
|
||||||
|
eventCode = _customEventCodeController.text;
|
||||||
|
}
|
||||||
|
|
||||||
|
customEventNames.add(eventName);
|
||||||
|
customEventCodes.add(eventCode);
|
||||||
|
|
||||||
|
await prefs.setStringList('custom_event_names', customEventNames);
|
||||||
|
await prefs.setStringList('custom_event_codes', customEventCodes);
|
||||||
|
|
||||||
|
_customEventNameController.clear();
|
||||||
|
_customEventCodeController.clear();
|
||||||
|
setState(() {});
|
||||||
|
|
||||||
|
if (mounted) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text('Added Event: $eventName - $eventCode'),
|
||||||
|
behavior: SnackBarBehavior.floating,
|
||||||
|
margin: const EdgeInsets.all(16.0),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(title: const Text('Add Event')),
|
||||||
|
body: Form(
|
||||||
|
key: _formKey,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 16.0),
|
||||||
|
child: Text('Add Custom Event', style: Theme.of(context).textTheme.titleLarge)
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 16.0),
|
||||||
|
child: ListView(
|
||||||
|
shrinkWrap: true,
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||||
|
children: [
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
TextFormField(
|
||||||
|
controller: _customEventNameController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Event Name',
|
||||||
|
hintText: 'gm_construct Regional',
|
||||||
|
border: OutlineInputBorder()
|
||||||
|
),
|
||||||
|
validator: (value) {
|
||||||
|
if (value == null || value.isEmpty) {
|
||||||
|
return 'Please enter a event name';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
TextFormField(
|
||||||
|
controller: _customEventCodeController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Event Code',
|
||||||
|
hintText: '2026gmct',
|
||||||
|
border: OutlineInputBorder()
|
||||||
|
)
|
||||||
|
),
|
||||||
|
Text('Leave this field EMPTY unless you NEED a custom code.'),
|
||||||
|
const SizedBox(height: 16.0),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () { _saveEvent(); },
|
||||||
|
child: const Text('Create Event'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
),
|
||||||
|
Divider(),
|
||||||
|
Text('Custom Events', style: Theme.of(context).textTheme.titleLarge),
|
||||||
|
ListView.builder(
|
||||||
|
shrinkWrap: true,
|
||||||
|
itemCount: customEventNames.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return ListTile(
|
||||||
|
title: Text('${customEventNames[index]} - ${customEventCodes[index]}')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,30 +1,107 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:laserscouter/eventadder.dart';
|
||||||
import 'teampicker.dart';
|
import 'teampicker.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class EventPicker extends StatefulWidget {
|
||||||
|
const EventPicker({super.key, required this.eventNames, required this.eventCodes});
|
||||||
|
|
||||||
class EventPicker extends StatelessWidget {
|
|
||||||
final List<String> eventNames;
|
final List<String> eventNames;
|
||||||
final List<String> eventCodes;
|
final List<String> eventCodes;
|
||||||
|
|
||||||
const EventPicker({super.key, required this.eventNames, required this.eventCodes});
|
@override
|
||||||
|
State<EventPicker> createState() => _EventPickerState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _EventPickerState extends State<EventPicker> {
|
||||||
|
List<String> eventNames = [];
|
||||||
|
List<String> eventCodes = [];
|
||||||
|
List<bool> isCustom = [];
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
eventNames = List<String>.from(widget.eventNames);
|
||||||
|
eventCodes = List<String>.from(widget.eventCodes);
|
||||||
|
isCustom = List<bool>.filled(widget.eventCodes.length, false);
|
||||||
|
_refreshEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _refreshEvents() async {
|
||||||
|
eventNames = widget.eventNames.toList();
|
||||||
|
eventCodes = widget.eventCodes.toList();
|
||||||
|
isCustom = List<bool>.filled(widget.eventCodes.length, false).toList();
|
||||||
|
|
||||||
|
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||||
|
final storedNames = prefs.getStringList('custom_event_names') ?? [];
|
||||||
|
final storedCodes = prefs.getStringList('custom_event_codes') ?? [];
|
||||||
|
|
||||||
|
for (int i = 0; i < storedNames.length; i++) {
|
||||||
|
if (!eventCodes.contains(storedCodes[i])) {
|
||||||
|
eventNames.add(storedNames[i]);
|
||||||
|
eventCodes.add(storedCodes[i]);
|
||||||
|
isCustom.add(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(title: const Text('Event')),
|
appBar: AppBar(
|
||||||
|
title: const Text('Event'),
|
||||||
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
onPressed: () async {
|
||||||
|
await Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => const EventAdder(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
_refreshEvents();
|
||||||
|
},
|
||||||
|
icon: Icon(Icons.add),
|
||||||
|
)
|
||||||
|
]
|
||||||
|
),
|
||||||
body: ListView.builder(
|
body: ListView.builder(
|
||||||
itemCount: eventNames.length,
|
itemCount: eventNames.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
return ListTile(
|
return ListTile(
|
||||||
title: Text(eventNames[index]),
|
title: Text(eventNames[index]),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
|
bool isEventCustom = isCustom[index];
|
||||||
|
String eventCode = eventCodes[index];
|
||||||
|
String eventName = eventCodes[index];
|
||||||
|
|
||||||
|
if (isEventCustom) {
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => TeamPicker(
|
builder: (context) => TeamPicker(
|
||||||
eventCode: eventCodes[index],
|
eventCode: eventCode,
|
||||||
|
eventName: eventName,
|
||||||
|
isCustomOnly: isEventCustom,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => TeamPicker(
|
||||||
|
isCustomOnly: isEventCustom,
|
||||||
|
eventName: eventName,
|
||||||
|
eventCode: eventCode,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -67,11 +67,12 @@ class _LoginPageState extends State<LoginPage> {
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: const Text('Login'),
|
title: const Text('Login'),
|
||||||
),
|
),
|
||||||
body: Padding(
|
body: SafeArea(
|
||||||
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
children: [
|
||||||
|
const Spacer(),
|
||||||
Image.asset('assets/main.png', height: 75, alignment: Alignment.center),
|
Image.asset('assets/main.png', height: 75, alignment: Alignment.center),
|
||||||
const SizedBox(height: 16.0),
|
const SizedBox(height: 16.0),
|
||||||
TextField(
|
TextField(
|
||||||
|
|
@ -80,6 +81,7 @@ class _LoginPageState extends State<LoginPage> {
|
||||||
labelText: 'Team Number',
|
labelText: 'Team Number',
|
||||||
border: OutlineInputBorder(),
|
border: OutlineInputBorder(),
|
||||||
),
|
),
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16.0),
|
const SizedBox(height: 16.0),
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
|
|
@ -107,7 +109,11 @@ class _LoginPageState extends State<LoginPage> {
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
scaffoldMessenger.showSnackBar(
|
scaffoldMessenger.showSnackBar(
|
||||||
SnackBar(content: Text('Error: ${e.toString()}')),
|
SnackBar(
|
||||||
|
content: Text('Error: ${e.toString()}'),
|
||||||
|
behavior: SnackBarBehavior.floating,
|
||||||
|
margin: const EdgeInsets.all(16.0),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
|
|
@ -127,14 +133,9 @@ class _LoginPageState extends State<LoginPage> {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: const Text('Login'),
|
: const Text('Login'),
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
const Spacer(),
|
||||||
bottomNavigationBar: Padding (
|
Row (mainAxisAlignment: MainAxisAlignment.center,
|
||||||
padding: const EdgeInsets.all(50.0),
|
|
||||||
child: Row (
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
children: [
|
||||||
TextButton(
|
TextButton(
|
||||||
child: const Text('Info'),
|
child: const Text('Info'),
|
||||||
|
|
@ -145,7 +146,7 @@ class _LoginPageState extends State<LoginPage> {
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: const Text('Info'),
|
title: const Text('Info'),
|
||||||
content: const Text(
|
content: const Text(
|
||||||
"This app makes use of The Blue Alliance APIv3 through Laser Proxy. No API keys are stored on device. Laser Scouter was created by FRC 2077 Laser Robotics. \n\nVersion: Rebuilt 26.1.23"
|
"This app makes use of The Blue Alliance APIv3 through Laser Proxy. No API keys are stored on device. Laser Scouter was created by FRC 2077 Laser Robotics. \n\nVersion: Rebuilt v26.2.22"
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -172,7 +173,10 @@ class _LoginPageState extends State<LoginPage> {
|
||||||
child: const Text('Settings'),
|
child: const Text('Settings'),
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
)
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,19 +20,22 @@ class NotesPage extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _NotesPageState extends State<NotesPage> {
|
class _NotesPageState extends State<NotesPage> {
|
||||||
late SharedPreferences _prefs;
|
late SharedPreferences prefs;
|
||||||
ScoutingView _selectedView = ScoutingView.match;
|
ScoutingView _selectedView = ScoutingView.match;
|
||||||
bool _isLoading = true;
|
bool _isLoading = true;
|
||||||
|
|
||||||
final _autonRundownController = TextEditingController();
|
|
||||||
final _botPositionController = TextEditingController();
|
final _botPositionController = TextEditingController();
|
||||||
final _generalObservationsController = TextEditingController();
|
final _generalObservationsController = TextEditingController();
|
||||||
|
final _autonRundownController = TextEditingController();
|
||||||
final _driveTrainTypeController = TextEditingController();
|
final _intakePositionController = TextEditingController();
|
||||||
bool _hasVision = false;
|
final _scoreMechanismController = TextEditingController();
|
||||||
double _climbLevel = 100.0;
|
double _fuelPerCycle = 0;
|
||||||
bool _trenchable = false;
|
bool _canDriveUnderTrench = false;
|
||||||
|
bool _canDriveOverBump = false;
|
||||||
|
double _climbLevel = 0.0;
|
||||||
double _fuelCapacity = 0.0;
|
double _fuelCapacity = 0.0;
|
||||||
|
bool _canGiveToHumanPlayer = false;
|
||||||
|
double _cycleTime = 0.0;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
|
@ -44,8 +47,9 @@ class _NotesPageState extends State<NotesPage> {
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_autonRundownController.dispose();
|
_autonRundownController.dispose();
|
||||||
_botPositionController.dispose();
|
_botPositionController.dispose();
|
||||||
_driveTrainTypeController.dispose();
|
|
||||||
_generalObservationsController.dispose();
|
_generalObservationsController.dispose();
|
||||||
|
_intakePositionController.dispose();
|
||||||
|
_scoreMechanismController.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -54,21 +58,33 @@ class _NotesPageState extends State<NotesPage> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _loadNotes() async {
|
Future<void> _loadNotes() async {
|
||||||
_prefs = await SharedPreferences.getInstance();
|
prefs = await SharedPreferences.getInstance();
|
||||||
|
|
||||||
_autonRundownController.text = _prefs.getString(_generateKey('autonRundown')) ?? '';
|
_botPositionController.text = prefs.getString(_generateKey('botPosition')) ?? '';
|
||||||
_botPositionController.text = _prefs.getString(_generateKey('botPosition')) ?? '';
|
|
||||||
_generalObservationsController.text = _prefs.getString(_generateKey('generalObservations')) ?? '';
|
|
||||||
_driveTrainTypeController.text = _prefs.getString(_generateKey('driveTrainType')) ?? '';
|
|
||||||
_hasVision = _prefs.getBool(_generateKey('hasVision')) ?? false;
|
|
||||||
_climbLevel = _prefs.getDouble(_generateKey('climbLevel')) ?? 0.0;
|
|
||||||
_trenchable = _prefs.getBool(_generateKey('trenchable')) ?? false;
|
|
||||||
_fuelCapacity = _prefs.getDouble(_generateKey('fuelCapacity')) ?? 0.0;
|
|
||||||
_autonRundownController.addListener(() => _saveString('autonRundown', _autonRundownController.text));
|
|
||||||
_botPositionController.addListener(() => _saveString('botPosition', _botPositionController.text));
|
_botPositionController.addListener(() => _saveString('botPosition', _botPositionController.text));
|
||||||
_driveTrainTypeController.addListener(() => _saveString('driveTrainType', _driveTrainTypeController.text));
|
|
||||||
|
_generalObservationsController.text = prefs.getString(_generateKey('generalObservations')) ?? '';
|
||||||
_generalObservationsController.addListener(() => _saveString('generalObservations', _generalObservationsController.text));
|
_generalObservationsController.addListener(() => _saveString('generalObservations', _generalObservationsController.text));
|
||||||
|
|
||||||
|
_autonRundownController.text = prefs.getString(_generateKey('autonRundown')) ?? '';
|
||||||
|
_autonRundownController.addListener(() => _saveString('autonRundown', _autonRundownController.text));
|
||||||
|
|
||||||
|
_intakePositionController.text = prefs.getString(_generateKey('intakePosition')) ?? '';
|
||||||
|
_intakePositionController.addListener(() => _saveString('intakePosition', _intakePositionController.text));
|
||||||
|
|
||||||
|
_scoreMechanismController.text = prefs.getString(_generateKey('scoreMechanism')) ?? '';
|
||||||
|
_scoreMechanismController.addListener(() => _saveString('scoreMechanism', _scoreMechanismController.text));
|
||||||
|
|
||||||
|
_fuelPerCycle = prefs.getDouble(_generateKey('fuelPerCycle')) ?? 0.0;
|
||||||
|
_canDriveUnderTrench = prefs.getBool(_generateKey('canDriveUnderTrench')) ?? false;
|
||||||
|
_canDriveOverBump = prefs.getBool(_generateKey('canDriveOverBump')) ?? false;
|
||||||
|
_climbLevel = prefs.getDouble(_generateKey('climbLevel')) ?? 0.0;
|
||||||
|
_fuelCapacity = prefs.getDouble(_generateKey('fuelCapacity')) ?? 0.0;
|
||||||
|
_canGiveToHumanPlayer = prefs.getBool(_generateKey('canGiveToHumanPlayer')) ?? false;
|
||||||
|
_cycleTime = prefs.getDouble(_generateKey('cycleTime')) ?? 0.0;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_isLoading = false;
|
_isLoading = false;
|
||||||
|
|
@ -77,15 +93,15 @@ class _NotesPageState extends State<NotesPage> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _saveString(String field, String value) async {
|
Future<void> _saveString(String field, String value) async {
|
||||||
await _prefs.setString(_generateKey(field), value);
|
await prefs.setString(_generateKey(field), value);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _saveBool(String field, bool value) async {
|
Future<void> _saveBool(String field, bool value) async {
|
||||||
await _prefs.setBool(_generateKey(field), value);
|
await prefs.setBool(_generateKey(field), value);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _saveDouble(String field, double value) async {
|
Future<void> _saveDouble(String field, double value) async {
|
||||||
await _prefs.setDouble(_generateKey(field), value);
|
await prefs.setDouble(_generateKey(field), value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -96,7 +112,8 @@ class _NotesPageState extends State<NotesPage> {
|
||||||
),
|
),
|
||||||
body: _isLoading
|
body: _isLoading
|
||||||
? const Center(child: CircularProgressIndicator())
|
? const Center(child: CircularProgressIndicator())
|
||||||
: Column(
|
: SafeArea(
|
||||||
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(top: 16.0),
|
padding: const EdgeInsets.only(top: 16.0),
|
||||||
|
|
@ -127,6 +144,7 @@ class _NotesPageState extends State<NotesPage> {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -146,15 +164,6 @@ class _NotesPageState extends State<NotesPage> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
TextField(
|
|
||||||
controller: _autonRundownController,
|
|
||||||
decoration: const InputDecoration(
|
|
||||||
labelText: 'Autonomous Rundown',
|
|
||||||
hintText: 'Describe their auto',
|
|
||||||
border: OutlineInputBorder(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
TextField(
|
TextField(
|
||||||
controller: _generalObservationsController,
|
controller: _generalObservationsController,
|
||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(
|
||||||
|
|
@ -175,32 +184,51 @@ class _NotesPageState extends State<NotesPage> {
|
||||||
Text('Robot Technical Details', style: Theme.of(context).textTheme.titleMedium),
|
Text('Robot Technical Details', style: Theme.of(context).textTheme.titleMedium),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
TextField(
|
TextField(
|
||||||
controller: _driveTrainTypeController,
|
controller: _autonRundownController,
|
||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(
|
||||||
labelText: 'Drivetrain Type',
|
labelText: 'Autonomous Rundown',
|
||||||
hintText: 'e.g., Swerve, Tank',
|
hintText: 'Describe their auto',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
TextField(
|
||||||
|
controller: _intakePositionController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Intake Position',
|
||||||
|
hintText: 'e.g., Ground',
|
||||||
border: OutlineInputBorder(),
|
border: OutlineInputBorder(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
SwitchListTile(
|
SwitchListTile(
|
||||||
title: Text('Has Vision/AprilTags', style: Theme.of(context).textTheme.titleSmall),
|
title: Text('Can Drive Over Bump', style: Theme.of(context).textTheme.titleSmall),
|
||||||
value: _hasVision,
|
value: _canDriveOverBump,
|
||||||
onChanged: (bool value) {
|
onChanged: (bool value) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_hasVision = value;
|
_canDriveOverBump = value;
|
||||||
});
|
});
|
||||||
_saveBool('hasVision', value);
|
_saveBool('canDriveOverBump', value);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
SwitchListTile(
|
SwitchListTile(
|
||||||
title: Text('Can Go Under Trench', style: Theme.of(context).textTheme.titleSmall),
|
title: Text('Can Go Under Trench', style: Theme.of(context).textTheme.titleSmall),
|
||||||
value: _trenchable,
|
value: _canDriveUnderTrench,
|
||||||
onChanged: (bool value) {
|
onChanged: (bool value) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_trenchable = value;
|
_canDriveUnderTrench = value;
|
||||||
});
|
});
|
||||||
_saveBool('trenchable', value);
|
_saveBool('canDriveUnderTrench', value);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
SwitchListTile(
|
||||||
|
title: Text('Can Give Fuel To Human Player', style: Theme.of(context).textTheme.titleSmall),
|
||||||
|
value: _canGiveToHumanPlayer,
|
||||||
|
onChanged: (bool value) {
|
||||||
|
setState(() {
|
||||||
|
_canGiveToHumanPlayer = value;
|
||||||
|
});
|
||||||
|
_saveBool('canGiveToHumanPlayer', value);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
|
@ -253,6 +281,56 @@ class _NotesPageState extends State<NotesPage> {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text('Fuel Per Cycle - ${_fuelPerCycle.round()}', style: Theme.of(context).textTheme.titleSmall),
|
||||||
|
Slider(
|
||||||
|
value: _fuelPerCycle,
|
||||||
|
min: 0,
|
||||||
|
max: _fuelCapacity,
|
||||||
|
divisions: 50,
|
||||||
|
label: _fuelPerCycle.round().toString(),
|
||||||
|
onChanged: (double value) {
|
||||||
|
setState(() {
|
||||||
|
_fuelPerCycle = value;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onChangeEnd: (double value) {
|
||||||
|
_saveDouble('fuelPerCycle', value);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text('Cycle Time - ${_cycleTime.round()}s', style: Theme.of(context).textTheme.titleSmall),
|
||||||
|
Slider(
|
||||||
|
value: _cycleTime,
|
||||||
|
min: 0,
|
||||||
|
max: 60,
|
||||||
|
divisions: 60,
|
||||||
|
label: _cycleTime.round().toString(),
|
||||||
|
onChanged: (double value) {
|
||||||
|
setState(() {
|
||||||
|
_cycleTime = value;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onChangeEnd: (double value) {
|
||||||
|
_saveDouble('cycleTime', value);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
147
lib/teamadder.dart
Normal file
147
lib/teamadder.dart
Normal file
|
|
@ -0,0 +1,147 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
class TeamAdder extends StatefulWidget {
|
||||||
|
final String eventCode;
|
||||||
|
const TeamAdder({super.key, required this.eventCode});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<TeamAdder> createState() => _TeamAdderState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _TeamAdderState extends State<TeamAdder> {
|
||||||
|
final _teamNumberController = TextEditingController();
|
||||||
|
final _teamNameController = TextEditingController();
|
||||||
|
List<String> customTeamNumbers = [];
|
||||||
|
List<String> customTeamNames = [];
|
||||||
|
final _formKey = GlobalKey<FormState>();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_loadTeams();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _loadTeams() async {
|
||||||
|
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||||
|
setState(() {
|
||||||
|
customTeamNumbers = prefs.getStringList('custom_team_numbers_${widget.eventCode}') ?? [];
|
||||||
|
customTeamNames = prefs.getStringList('custom_team_names_${widget.eventCode}') ?? [];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _saveTeam() async {
|
||||||
|
if (!_formKey.currentState!.validate()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final String teamNumber = _teamNumberController.text;
|
||||||
|
final String teamName = _teamNameController.text;
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
customTeamNumbers.add(teamNumber);
|
||||||
|
customTeamNames.add(teamName);
|
||||||
|
});
|
||||||
|
|
||||||
|
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||||
|
await prefs.setStringList('custom_team_numbers_${widget.eventCode}', customTeamNumbers);
|
||||||
|
await prefs.setStringList('custom_team_names_${widget.eventCode}', customTeamNames);
|
||||||
|
|
||||||
|
_teamNumberController.clear();
|
||||||
|
_teamNameController.clear();
|
||||||
|
|
||||||
|
if (mounted) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text('Added Team: $teamNumber - $teamName'),
|
||||||
|
behavior: SnackBarBehavior.floating,
|
||||||
|
margin: const EdgeInsets.all(16.0),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_teamNumberController.dispose();
|
||||||
|
_teamNameController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: const Text('Add Team'),
|
||||||
|
),
|
||||||
|
body: Form(
|
||||||
|
key: _formKey,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text('Add a New Team', style: Theme.of(context).textTheme.titleLarge),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
TextFormField(
|
||||||
|
controller: _teamNumberController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Team Number',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
validator: (value) {
|
||||||
|
if (value == null || value.isEmpty) {
|
||||||
|
return 'Please enter a team number';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
TextFormField(
|
||||||
|
controller: _teamNameController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Team Name',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
validator: (value) {
|
||||||
|
if (value == null || value.isEmpty) {
|
||||||
|
return 'Please enter a team name';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed: _saveTeam,
|
||||||
|
child: const Text('Save Team'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Divider(height: 32),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||||
|
child: Text('Saved Teams', style: Theme.of(context).textTheme.titleLarge),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: ListView.builder(
|
||||||
|
itemCount: customTeamNumbers.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return ListTile(
|
||||||
|
title: Text('${customTeamNumbers[index]} - ${customTeamNames[index]}'),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,14 +1,17 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:laserscouter/core/api.dart';
|
import 'package:laserscouter/core/api.dart';
|
||||||
import 'notespage.dart';
|
import 'notespage.dart';
|
||||||
|
import 'teamadder.dart';
|
||||||
import 'package:to_csv/to_csv.dart' as csv_export;
|
import 'package:to_csv/to_csv.dart' as csv_export;
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
|
||||||
class TeamPicker extends StatefulWidget {
|
class TeamPicker extends StatefulWidget {
|
||||||
final String eventCode;
|
final String eventCode;
|
||||||
|
final String eventName;
|
||||||
|
final bool isCustomOnly;
|
||||||
|
|
||||||
const TeamPicker({super.key, required this.eventCode});
|
const TeamPicker({super.key, required this.eventCode, required this.eventName,this.isCustomOnly = false});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<TeamPicker> createState() => _TeamPickerState();
|
State<TeamPicker> createState() => _TeamPickerState();
|
||||||
|
|
@ -29,14 +32,17 @@ class _TeamPickerState extends State<TeamPicker> {
|
||||||
Future<void> _exportData() async {
|
Future<void> _exportData() async {
|
||||||
List<String> header = [];
|
List<String> header = [];
|
||||||
header.add('Team Number');
|
header.add('Team Number');
|
||||||
header.add('Drivetrain Type');
|
|
||||||
header.add('Has Vision');
|
|
||||||
header.add('Climb Level');
|
|
||||||
header.add('Trenchable');
|
|
||||||
header.add('Fuel Capacity');
|
|
||||||
header.add('Bot Position');
|
header.add('Bot Position');
|
||||||
|
header.add('Gen. Observations');
|
||||||
header.add('Auton Rundown');
|
header.add('Auton Rundown');
|
||||||
header.add('General Observations');
|
header.add('Intake Position');
|
||||||
|
header.add('Can Drive Over Bump');
|
||||||
|
header.add('Can Go Under Trench');
|
||||||
|
header.add('Can Give Fuel to HP');
|
||||||
|
header.add('Climb Level');
|
||||||
|
header.add('Fuel Capacity');
|
||||||
|
header.add('Fuel per Cycle');
|
||||||
|
header.add("Cycle Time");
|
||||||
|
|
||||||
List<List<String>> data = [];
|
List<List<String>> data = [];
|
||||||
for (int i = 0; i < teamCodes.length; i++) {
|
for (int i = 0; i < teamCodes.length; i++) {
|
||||||
|
|
@ -44,68 +50,144 @@ class _TeamPickerState extends State<TeamPicker> {
|
||||||
return '${teamCodes[i]}_${widget.eventCode}_$field';
|
return '${teamCodes[i]}_${widget.eventCode}_$field';
|
||||||
}
|
}
|
||||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||||
String? autonRundown = prefs.getString(generateKey('autonRundown'));
|
|
||||||
String? botPosition = prefs.getString(generateKey('botPosition'));
|
String? botPosition = prefs.getString(generateKey('botPosition'));
|
||||||
String? generalObservations = prefs.getString(generateKey('generalObservations'));
|
String? generalObservations = prefs.getString(generateKey('generalObservations'));
|
||||||
String? driveTrainType = prefs.getString(generateKey('driveTrainType'));
|
String? autonRundown = prefs.getString(generateKey('autonRundown'));
|
||||||
String? hasVision = prefs.getBool(generateKey('hasVision')).toString();
|
String? intakePosition = prefs.getString(generateKey('intakePosition'));
|
||||||
|
String? canDriveOverBump = prefs.getBool(generateKey('canDriveOverBump')).toString();
|
||||||
|
String? canDriveUnderTrench = prefs.getBool(generateKey('canDriveUnderTrench')).toString();
|
||||||
|
String? canGiveToHumanPlayer = prefs.getBool(generateKey('canGiveToHumanPlayer')).toString();
|
||||||
String? climbLevel = prefs.getDouble(generateKey('climbLevel')).toString();
|
String? climbLevel = prefs.getDouble(generateKey('climbLevel')).toString();
|
||||||
String? trenchable = prefs.getBool(generateKey('trenchable')).toString();
|
|
||||||
String? fuelCapacity = prefs.getDouble(generateKey('fuelCapacity')).toString();
|
String? fuelCapacity = prefs.getDouble(generateKey('fuelCapacity')).toString();
|
||||||
|
String? fuelPerCycle = prefs.getDouble(generateKey('fuelPerCycle')).toString();
|
||||||
|
String? cycleTime = prefs.getDouble(generateKey('cycleTime')).toString();
|
||||||
|
|
||||||
if (hasVision == 'null') {
|
if (canDriveOverBump == 'null') {
|
||||||
hasVision = '';
|
canDriveOverBump = '';
|
||||||
|
}
|
||||||
|
if (canDriveUnderTrench == 'null') {
|
||||||
|
canDriveUnderTrench = '';
|
||||||
|
}
|
||||||
|
if (canGiveToHumanPlayer == 'null') {
|
||||||
|
canGiveToHumanPlayer = '';
|
||||||
}
|
}
|
||||||
if (climbLevel == 'null') {
|
if (climbLevel == 'null') {
|
||||||
climbLevel = '';
|
climbLevel = '';
|
||||||
}
|
}
|
||||||
if (trenchable == 'null') {
|
|
||||||
trenchable = '';
|
|
||||||
}
|
|
||||||
if (fuelCapacity == 'null') {
|
if (fuelCapacity == 'null') {
|
||||||
fuelCapacity = '';
|
fuelCapacity = '';
|
||||||
}
|
}
|
||||||
|
if (fuelPerCycle == 'null') {
|
||||||
|
fuelPerCycle = '';
|
||||||
|
}
|
||||||
|
if (cycleTime == 'null') {
|
||||||
|
cycleTime = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
List<String> teamData = [];
|
List<String> teamData = [];
|
||||||
teamData.add(teamCodes[i]);
|
teamData.add(teamCodes[i]);
|
||||||
teamData.add(driveTrainType ?? '');
|
|
||||||
teamData.add(hasVision);
|
|
||||||
teamData.add(climbLevel);
|
|
||||||
teamData.add(trenchable);
|
|
||||||
teamData.add(fuelCapacity);
|
|
||||||
teamData.add(botPosition ?? '');
|
teamData.add(botPosition ?? '');
|
||||||
teamData.add(autonRundown ?? '');
|
|
||||||
teamData.add(generalObservations ?? '');
|
teamData.add(generalObservations ?? '');
|
||||||
|
teamData.add(autonRundown ?? '');
|
||||||
|
teamData.add(intakePosition ?? '');
|
||||||
|
teamData.add(canDriveOverBump);
|
||||||
|
teamData.add(canDriveUnderTrench);
|
||||||
|
teamData.add(canGiveToHumanPlayer);
|
||||||
|
teamData.add(climbLevel);
|
||||||
|
teamData.add(fuelCapacity);
|
||||||
|
teamData.add(fuelPerCycle);
|
||||||
|
teamData.add(cycleTime);
|
||||||
data.add(teamData);
|
data.add(teamData);
|
||||||
}
|
}
|
||||||
csv_export.myCSV(header, data, setHeadersInFirstRow: true, emptyRowsConfig: {1: 1}, fileName: 'laserscouter_${widget.eventCode}.csv');
|
csv_export.myCSV(header, data, setHeadersInFirstRow: true, fileName: 'laserscouter_${widget.eventCode}.csv');
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _fetchTeams() async {
|
Future<void> _fetchTeams() async {
|
||||||
try {
|
try {
|
||||||
final EventSearchResult result = await eventSearch(widget.eventCode);
|
List<String> apiTeamNames = [];
|
||||||
|
List<String> apiTeamCodes = [];
|
||||||
|
|
||||||
|
if (!widget.isCustomOnly) {
|
||||||
|
final EventSearchResult apiResult = await eventSearch(widget.eventCode);
|
||||||
|
apiTeamNames = apiResult.teamNames;
|
||||||
|
apiTeamCodes = apiResult.teamCodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||||
|
final List<String> customTeamNames = prefs.getStringList('custom_team_names_${widget.eventName}') ?? [];
|
||||||
|
final List<String> customTeamCodes = prefs.getStringList('custom_team_numbers_${widget.eventCode}') ?? [];
|
||||||
|
|
||||||
|
List<String> combinedNames = [];
|
||||||
|
List<String> combinedCodes = [];
|
||||||
|
for (int i = 0; i < apiTeamCodes.length; i++) {
|
||||||
|
if (!combinedCodes.contains(apiTeamCodes[i])) {
|
||||||
|
combinedCodes.add(apiTeamCodes[i]);
|
||||||
|
if (i < apiTeamNames.length) {
|
||||||
|
combinedNames.add(apiTeamNames[i]);
|
||||||
|
} else {
|
||||||
|
combinedNames.add('');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (int i = 0; i < customTeamCodes.length; i++) {
|
||||||
|
if (!combinedCodes.contains(customTeamCodes[i])) {
|
||||||
|
combinedCodes.add(customTeamCodes[i]);
|
||||||
|
if (i < customTeamNames.length) {
|
||||||
|
combinedNames.add(customTeamNames[i]);
|
||||||
|
} else {
|
||||||
|
combinedNames.add('');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
teamNames = result.teamNames;
|
teamNames = combinedNames.toList();
|
||||||
teamCodes = result.teamCodes;
|
teamCodes = combinedCodes.toList();
|
||||||
isLoading = false;
|
isLoading = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
errorMessage = "Failed to load teams. Please try again.";
|
errorMessage = widget.isCustomOnly
|
||||||
|
? "Could not load custom teams."
|
||||||
|
: "Failed to load teams. Please try again.";
|
||||||
isLoading = false;
|
isLoading = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Future<void> _refreshTeams() async {
|
||||||
|
setState(() {
|
||||||
|
isLoading = true;
|
||||||
|
});
|
||||||
|
await _fetchTeams();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: const Text('Teams'),
|
title: const Text('Teams'),
|
||||||
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
onPressed: () async {
|
||||||
|
await Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => TeamAdder(eventCode: widget.eventCode,),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
_refreshTeams();
|
||||||
|
},
|
||||||
|
icon: Icon(Icons.add),
|
||||||
|
)
|
||||||
|
]
|
||||||
),
|
),
|
||||||
body: _buildBody(),
|
body: _buildBody(),
|
||||||
floatingActionButton: FloatingActionButton(
|
floatingActionButton: FloatingActionButton(
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||||
# In Windows, build-name is used as the major, minor, and patch parts
|
# In Windows, build-name is used as the major, minor, and patch parts
|
||||||
# of the product and file versions while build-number is used as the build suffix.
|
# of the product and file versions while build-number is used as the build suffix.
|
||||||
version: 26.1.23
|
version: 26.2.22
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.10.7
|
sdk: ^3.10.7
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue