diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index e659f42..1623c75 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -26,4 +26,5 @@
+
diff --git a/lib/core/api.dart b/lib/core/api.dart
index ff0d958..427df21 100644
--- a/lib/core/api.dart
+++ b/lib/core/api.dart
@@ -2,7 +2,7 @@ import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
-String proxyURL = "https://laserscouter.halfheart.net/";
+String proxyURL = "https://laserproxy.halfheart.net/";
class TeamSearchResult {
final List eventNames;
diff --git a/lib/core/theme.dart b/lib/core/theme.dart
index a7358dc..e1b80f9 100644
--- a/lib/core/theme.dart
+++ b/lib/core/theme.dart
@@ -84,8 +84,9 @@ final ThemeData laserTheme = ThemeData(
sliderTheme: SliderThemeData(
activeTrackColor: const Color(0xFF00245D),
- thumbColor: const Color(0xFF00245D),
+ thumbColor: const Color(0xFFFFFFFF),
inactiveTrackColor: Colors.grey.shade800,
+ valueIndicatorColor: Colors.white
),
checkboxTheme: CheckboxThemeData(
@@ -101,7 +102,7 @@ final ThemeData laserTheme = ThemeData(
switchTheme: SwitchThemeData(
thumbColor: WidgetStateProperty.resolveWith((states) {
if (states.contains(WidgetState.selected)) {
- return const Color(0xFF00245D);
+ return const Color(0xFFFFFFFF);
}
return null;
}),
diff --git a/lib/notespage.dart b/lib/notespage.dart
index 6df2aa0..f7de9c3 100644
--- a/lib/notespage.dart
+++ b/lib/notespage.dart
@@ -1,6 +1,8 @@
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
+enum ScoutingView { match, pit }
+
class NotesPage extends StatefulWidget {
final String teamCode;
final String eventCode;
@@ -18,10 +20,19 @@ class NotesPage extends StatefulWidget {
}
class _NotesPageState extends State {
- final TextEditingController _checkboxes = TextEditingController();
- final TextEditingController _controller2 = TextEditingController();
- final TextEditingController _switchvalue = TextEditingController();
- double _slidervalue = 0.0;
+ late SharedPreferences _prefs;
+ ScoutingView _selectedView = ScoutingView.match;
+ bool _isLoading = true;
+
+ final _autonRundownController = TextEditingController();
+ final _botPositionController = TextEditingController();
+ final _generalObservationsController = TextEditingController();
+
+ final _driveTrainTypeController = TextEditingController();
+ bool _hasVision = false;
+ double _climbLevel = 100.0;
+ bool _trenchable = false;
+ double _fuelCapacity = 0.0;
@override
void initState() {
@@ -31,40 +42,50 @@ class _NotesPageState extends State {
@override
void dispose() {
- _saveNotes();
- _checkboxes.dispose();
- _controller2.dispose();
- _switchvalue.dispose();
+ _autonRundownController.dispose();
+ _botPositionController.dispose();
+ _driveTrainTypeController.dispose();
+ _generalObservationsController.dispose();
super.dispose();
}
+ String _generateKey(String field) {
+ return '${widget.teamCode}_${widget.eventCode}_$field';
+ }
+
Future _loadNotes() async {
- SharedPreferences prefs = await SharedPreferences.getInstance();
+ _prefs = await SharedPreferences.getInstance();
+
+ _autonRundownController.text = _prefs.getString(_generateKey('autonRundown')) ?? '';
+ _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));
+ _driveTrainTypeController.addListener(() => _saveString('driveTrainType', _driveTrainTypeController.text));
+ _generalObservationsController.addListener(() => _saveString('generalObservations', _generalObservationsController.text));
+
if (mounted) {
setState(() {
- _checkboxes.text =
- prefs.getString('${widget.teamCode}_${widget.eventCode}_note1') ?? '';
- _controller2.text =
- prefs.getString('${widget.teamCode}_${widget.eventCode}_note2') ?? '';
- _switchvalue.text =
- prefs.getString('${widget.teamCode}_${widget.eventCode}_note3') ?? '';
- _slidervalue = double.tryParse(prefs
- .getString('${widget.teamCode}_${widget.eventCode}_note4') ??
- '0.0') ?? 0.0;
+ _isLoading = false;
});
}
}
- Future _saveNotes() async {
- SharedPreferences prefs = await SharedPreferences.getInstance();
- await prefs.setString(
- '${widget.teamCode}_${widget.eventCode}_note1', _checkboxes.text);
- await prefs.setString(
- '${widget.teamCode}_${widget.eventCode}_note2', _controller2.text);
- await prefs.setString(
- '${widget.teamCode}_${widget.eventCode}_note3', _switchvalue.text);
- await prefs.setString(
- '${widget.teamCode}_${widget.eventCode}_note4', _slidervalue.toString());
+ Future _saveString(String field, String value) async {
+ await _prefs.setString(_generateKey(field), value);
+ }
+
+ Future _saveBool(String field, bool value) async {
+ await _prefs.setBool(_generateKey(field), value);
+ }
+
+ Future _saveDouble(String field, double value) async {
+ await _prefs.setDouble(_generateKey(field), value);
}
@override
@@ -73,108 +94,166 @@ class _NotesPageState extends State {
appBar: AppBar(
title: Text('Notes'),
),
- body: Padding(
- padding: const EdgeInsets.all(16.0),
- child: ListView(
- children: [
- Text(widget.teamName, style: Theme.of(context).textTheme.titleLarge, textAlign: TextAlign.center),
- Text('Bot Starting Position',
- style: Theme.of(context).textTheme.titleMedium),
- CheckboxListTile(
- title: const Text('Left'),
- value: _checkboxes.text.contains('Left'),
- onChanged: (bool? value) {
+ body: _isLoading
+ ? const Center(child: CircularProgressIndicator())
+ : Column(
+ children: [
+ Padding(
+ padding: const EdgeInsets.only(top: 16.0),
+ child: Text(widget.teamName, style: Theme.of(context).textTheme.titleLarge)
+ ),
+ Padding(
+ padding: const EdgeInsets.all(16.0),
+ child: SegmentedButton(
+ segments: const [
+ ButtonSegment(value: ScoutingView.match, label: Text('Match Scouting')),
+ ButtonSegment(value: ScoutingView.pit, label: Text('Pit Scouting')),
+ ],
+ selected: {_selectedView},
+ onSelectionChanged: (newSelection) {
setState(() {
- if (value == true) {
- _checkboxes.text += 'Left ';
- } else {
- _checkboxes.text =
- _checkboxes.text.replaceAll('Left ', '');
- }
- _saveNotes();
+ _selectedView = newSelection.first;
});
},
),
- CheckboxListTile(
- title: const Text('Mid'),
- value: _checkboxes.text.contains('Mid'),
- onChanged: (bool? value) {
- setState(() {
- if (value == true) {
- _checkboxes.text += 'Mid ';
- } else {
- _checkboxes.text = _checkboxes.text.replaceAll('Mid ', '');
- }
- _saveNotes();
- });
- },
+ ),
+ Expanded(
+ child: AnimatedSwitcher(
+ duration: const Duration(milliseconds: 300),
+ child: _selectedView == ScoutingView.match
+ ? _buildMatchScoutingView()
+ : _buildPitScoutingView(),
),
- CheckboxListTile(
- title: const Text('Right'),
- value: _checkboxes.text.contains('Right'),
- onChanged: (bool? value) {
- setState(() {
- if (value == true) {
- _checkboxes.text += 'Right ';
- } else {
- _checkboxes.text =
- _checkboxes.text.replaceAll('Right ', '');
- }
- _saveNotes();
- });
- },
- ),
- const Divider(),
- TextField(
- controller: _controller2,
- decoration: const InputDecoration(labelText: 'Auton Rundown'),
- maxLines: null,
- keyboardType: TextInputType.multiline,
- onChanged: (text) => _saveNotes(),
- ),
- const Divider(),
- CheckboxListTile(
- title: const Text('Can Score Algae'),
- value: _switchvalue.text.contains('Can Score Algae'),
- onChanged: (bool? value) {
- setState(() {
- _switchvalue.text =
- value == true ? 'Can Score Algae' : '';
- _saveNotes();
- });
- },
- ),
- CheckboxListTile(
- title: const Text('Cannot Score Algae'),
- value: _switchvalue.text.contains('Cannot Score Algae'),
- onChanged: (bool? value) {
- setState(() {
- _switchvalue.text =
- value == true ? 'Cannot Score Algae' : '';
- _saveNotes();
- });
- },
- ),
- const Divider(),
- Text('Coral Level', style: Theme.of(context).textTheme.titleMedium),
- Slider(
- value: _slidervalue,
- onChanged: (double value) {
- setState(() {
- _slidervalue = value;
- });
- },
- onChangeEnd: (double value) {
- _saveNotes();
- },
- min: 0,
- max: 3,
- divisions: 3,
- label: _slidervalue.round().toString(),
- ),
- ],
- ),
+ ),
+ ],
),
);
}
+
+ Widget _buildMatchScoutingView() {
+ return ListView(
+ key: const ValueKey('match'),
+ padding: const EdgeInsets.symmetric(horizontal: 16.0),
+ children: [
+ Text('Match-Specific Observations', style: Theme.of(context).textTheme.titleMedium),
+ const SizedBox(height: 16),
+ TextField(
+ controller: _botPositionController,
+ decoration: const InputDecoration(
+ labelText: 'Bot Position / Role',
+ hintText: 'e.g., Shooter, Defense, Collector',
+ border: OutlineInputBorder(),
+ ),
+ ),
+ const SizedBox(height: 16),
+ TextField(
+ controller: _autonRundownController,
+ decoration: const InputDecoration(
+ labelText: 'Autonomous Rundown',
+ hintText: 'Describe their auto',
+ border: OutlineInputBorder(),
+ ),
+ ),
+ const SizedBox(height: 16),
+ TextField(
+ controller: _generalObservationsController,
+ decoration: const InputDecoration(
+ labelText: 'Observations',
+ hintText: 'General observations',
+ border: OutlineInputBorder(),
+ ),
+ )
+ ],
+ );
+ }
+
+ Widget _buildPitScoutingView() {
+ return ListView(
+ key: const ValueKey('pit'),
+ padding: const EdgeInsets.symmetric(horizontal: 16.0),
+ children: [
+ Text('Robot Technical Details', style: Theme.of(context).textTheme.titleMedium),
+ const SizedBox(height: 16),
+ TextField(
+ controller: _driveTrainTypeController,
+ decoration: const InputDecoration(
+ labelText: 'Drivetrain Type',
+ hintText: 'e.g., Swerve, Tank',
+ border: OutlineInputBorder(),
+ ),
+ ),
+ const SizedBox(height: 16),
+ SwitchListTile(
+ title: Text('Has Vision/AprilTags', style: Theme.of(context).textTheme.titleSmall),
+ value: _hasVision,
+ onChanged: (bool value) {
+ setState(() {
+ _hasVision = value;
+ });
+ _saveBool('hasVision', value);
+ },
+ ),
+ SwitchListTile(
+ title: Text('Can Go Under Trench', style: Theme.of(context).textTheme.titleSmall),
+ value: _trenchable,
+ onChanged: (bool value) {
+ setState(() {
+ _trenchable = value;
+ });
+ _saveBool('trenchable', value);
+ },
+ ),
+ const SizedBox(height: 16),
+ Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 16.0),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text('Max Climb Level - ${_climbLevel.round()}', style: Theme.of(context).textTheme.titleSmall),
+ Slider(
+ value: _climbLevel,
+ min: 0,
+ max: 3,
+ divisions: 3,
+ label: _climbLevel.round().toString(),
+ onChanged: (double value) {
+ setState(() {
+ _climbLevel = value;
+ });
+ },
+ onChangeEnd: (double value) {
+ _saveDouble('climbLevel', value);
+ },
+ ),
+ ],
+ ),
+ ),
+ const SizedBox(height: 16),
+ Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 16.0),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text('Fuel Capacity - ${_fuelCapacity.round()}', style: Theme.of(context).textTheme.titleSmall),
+ Slider(
+ value: _fuelCapacity,
+ min: 0,
+ max: 50,
+ divisions: 50,
+ label: _fuelCapacity.round().toString(),
+ onChanged: (double value) {
+ setState(() {
+ _fuelCapacity = value;
+ });
+ },
+ onChangeEnd: (double value) {
+ _saveDouble('fuelCapacity', value);
+ },
+ ),
+ ],
+ ),
+ ),
+ ],
+ );
+ }
}
diff --git a/lib/settings.dart b/lib/settings.dart
index db1de22..a35592f 100644
--- a/lib/settings.dart
+++ b/lib/settings.dart
@@ -56,7 +56,7 @@ class _SettingsPageState extends State {
controller: _urlController,
decoration: const InputDecoration(
labelText: 'URL',
- hintText: 'https://laserscouter.halfheart.net/',
+ hintText: 'https://laserproxy.halfheart.net/',
border: OutlineInputBorder(),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: Color.fromARGB(255, 19, 81, 179)),
@@ -64,7 +64,7 @@ class _SettingsPageState extends State {
),
onChanged: (value) {
if (value.isEmpty) {
- proxyURL = "https://laserscouter.halfheart.net/";
+ proxyURL = "https://laserproxy.halfheart.net/";
}
else {
proxyURL = value;
diff --git a/lib/teampicker.dart b/lib/teampicker.dart
index 29a2e3f..83badfc 100644
--- a/lib/teampicker.dart
+++ b/lib/teampicker.dart
@@ -1,8 +1,9 @@
import 'package:flutter/material.dart';
import 'package:laserscouter/core/api.dart';
+import 'notespage.dart';
+import 'package:to_csv/to_csv.dart' as csv_export;
import 'package:shared_preferences/shared_preferences.dart';
-import 'package:to_csv/to_csv.dart' as exportcsv;
-import 'notespage.dart'; // Import the new notes page file
+
class TeamPicker extends StatefulWidget {
final String eventCode;
@@ -22,16 +23,67 @@ class _TeamPickerState extends State {
@override
void initState() {
super.initState();
- // Call the new async method to fetch teams
_fetchTeams();
}
+ Future _exportData() async {
+ List header = [];
+ 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('Auton Rundown');
+ header.add('General Observations');
+
+ List> data = [];
+ for (int i = 0; i < teamCodes.length; i++) {
+ String generateKey(String field) {
+ return '${teamCodes[i]}_${widget.eventCode}_$field';
+ }
+ SharedPreferences prefs = await SharedPreferences.getInstance();
+ String? autonRundown = prefs.getString(generateKey('autonRundown'));
+ String? botPosition = prefs.getString(generateKey('botPosition'));
+ String? generalObservations = prefs.getString(generateKey('generalObservations'));
+ String? driveTrainType = prefs.getString(generateKey('driveTrainType'));
+ String? hasVision = prefs.getBool(generateKey('hasVision')).toString();
+ String? climbLevel = prefs.getDouble(generateKey('climbLevel')).toString();
+ String? trenchable = prefs.getBool(generateKey('trenchable')).toString();
+ String? fuelCapacity = prefs.getDouble(generateKey('fuelCapacity')).toString();
+
+ if (hasVision == 'null') {
+ hasVision = '';
+ }
+ if (climbLevel == 'null') {
+ climbLevel = '';
+ }
+ if (trenchable == 'null') {
+ trenchable = '';
+ }
+ if (fuelCapacity == 'null') {
+ fuelCapacity = '';
+ }
+
+ List teamData = [];
+ teamData.add(teamCodes[i]);
+ teamData.add(driveTrainType ?? '');
+ teamData.add(hasVision);
+ teamData.add(climbLevel);
+ teamData.add(trenchable);
+ teamData.add(fuelCapacity);
+ teamData.add(botPosition ?? '');
+ teamData.add(autonRundown ?? '');
+ teamData.add(generalObservations ?? '');
+ data.add(teamData);
+ }
+ csv_export.myCSV(header, data, setHeadersInFirstRow: true, emptyRowsConfig: {1: 1}, fileName: 'laserscouter_${widget.eventCode}.csv');
+ }
+
Future _fetchTeams() async {
try {
- // Await the result from the refactored API function
final EventSearchResult result = await eventSearch(widget.eventCode);
-
- // Check if the widget is still mounted before updating state
if (mounted) {
setState(() {
teamNames = result.teamNames;
@@ -40,7 +92,6 @@ class _TeamPickerState extends State {
});
}
} catch (e) {
- // If an error occurs, update the state to show an error message
if (mounted) {
setState(() {
errorMessage = "Failed to load teams. Please try again.";
@@ -50,46 +101,17 @@ class _TeamPickerState extends State {
}
}
- Future makeCSV() async {
- List header = [
- 'Team Number',
- 'Bot Position',
- 'Auton Rundown',
- 'Can Score Algae',
- 'Coral Level'
- ];
- List> dataLists = [header];
-
- SharedPreferences prefs = await SharedPreferences.getInstance();
- for (int i = 0; i < teamCodes.length; i++) {
- List data = [];
- data.add(teamCodes[i]);
- String botPosition = prefs.getString('${teamCodes[i]}_${widget.eventCode}_note1') ?? '';
- String autonRundown = prefs.getString('${teamCodes[i]}_${widget.eventCode}_note2') ?? '';
- String canScoreAlgae = prefs.getString('${teamCodes[i]}_${widget.eventCode}_note3') ?? '';
- String coralLevel = prefs.getString('${teamCodes[i]}_${widget.eventCode}_note4') ?? '0.0';
- data.add(botPosition.trim());
- data.add(autonRundown);
- data.add(canScoreAlgae);
- data.add(coralLevel);
- dataLists.add(data);
- }
- exportcsv.myCSV(header, dataLists, fileName: '${widget.eventCode}-scouting-data');
- }
-
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Teams'),
- actions: [
- IconButton(
- icon: const Icon(Icons.share),
- onPressed: isLoading || teamCodes.isEmpty ? null : makeCSV,
- ),
- ],
),
body: _buildBody(),
+ floatingActionButton: FloatingActionButton(
+ onPressed: _exportData,
+ child: const Icon(Icons.share),
+ ),
);
}
@@ -140,4 +162,4 @@ class _TeamPickerState extends State {
},
);
}
-}
\ No newline at end of file
+}
diff --git a/pubspec.lock b/pubspec.lock
index 76bed0f..7efa8ce 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -249,7 +249,7 @@ packages:
source: hosted
version: "0.15.6"
http:
- dependency: transitive
+ dependency: "direct main"
description:
name: http
sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412"
@@ -372,10 +372,10 @@ packages:
dependency: transitive
description:
name: objective_c
- sha256: "9922a1ad59ac5afb154cc948aa6ded01987a75003651d0a2866afc23f4da624e"
+ sha256: "7fd0c4d8ac8980011753b9bdaed2bf15111365924cdeeeaeb596214ea2b03537"
url: "https://pub.dev"
source: hosted
- version: "9.2.3"
+ version: "9.2.4"
path:
dependency: transitive
description:
diff --git a/pubspec.yaml b/pubspec.yaml
index 6416eb1..00fd429 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -39,6 +39,7 @@ dependencies:
to_csv: ^6.0.1
flutter_launcher_icons: ^0.14.4
rename_app: ^1.6.5
+ http: ^1.6.0
dev_dependencies:
flutter_test: