Compare commits

...

3 commits

5 changed files with 72 additions and 46 deletions

View file

@ -1,5 +1,4 @@
![Laser Scouter](https://raw.githubusercontent.com/Raktbastr/laserscouter/refs/heads/main/assets/laserscouterlogo.png) ![Laser Scouter](https://raw.githubusercontent.com/Raktbastr/laserscouter/refs/heads/main/assets/main.png)
<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.

View file

@ -146,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.2.14}" "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"
), ),
); );
} }

View file

@ -28,7 +28,7 @@ class _NotesPageState extends State<NotesPage> {
final _generalObservationsController = TextEditingController(); final _generalObservationsController = TextEditingController();
final _autonRundownController = TextEditingController(); final _autonRundownController = TextEditingController();
final _intakePositionController = TextEditingController(); final _intakePositionController = TextEditingController();
final _scoreMechanisimController = TextEditingController(); final _scoreMechanismController = TextEditingController();
double _fuelPerCycle = 0; double _fuelPerCycle = 0;
bool _canDriveUnderTrench = false; bool _canDriveUnderTrench = false;
bool _canDriveOverBump = false; bool _canDriveOverBump = false;
@ -49,7 +49,7 @@ class _NotesPageState extends State<NotesPage> {
_botPositionController.dispose(); _botPositionController.dispose();
_generalObservationsController.dispose(); _generalObservationsController.dispose();
_intakePositionController.dispose(); _intakePositionController.dispose();
_scoreMechanisimController.dispose(); _scoreMechanismController.dispose();
super.dispose(); super.dispose();
} }
@ -58,7 +58,7 @@ class _NotesPageState extends State<NotesPage> {
} }
Future<void> _loadNotes() async { Future<void> _loadNotes() async {
SharedPreferences prefs = await SharedPreferences.getInstance(); prefs = await SharedPreferences.getInstance();
_botPositionController.text = prefs.getString(_generateKey('botPosition')) ?? ''; _botPositionController.text = prefs.getString(_generateKey('botPosition')) ?? '';
_botPositionController.addListener(() => _saveString('botPosition', _botPositionController.text)); _botPositionController.addListener(() => _saveString('botPosition', _botPositionController.text));
@ -72,8 +72,8 @@ class _NotesPageState extends State<NotesPage> {
_intakePositionController.text = prefs.getString(_generateKey('intakePosition')) ?? ''; _intakePositionController.text = prefs.getString(_generateKey('intakePosition')) ?? '';
_intakePositionController.addListener(() => _saveString('intakePosition', _intakePositionController.text)); _intakePositionController.addListener(() => _saveString('intakePosition', _intakePositionController.text));
_scoreMechanisimController.text = prefs.getString(_generateKey('scoreMechanism')) ?? ''; _scoreMechanismController.text = prefs.getString(_generateKey('scoreMechanism')) ?? '';
_scoreMechanisimController.addListener(() => _saveString('scoreMechanism', _scoreMechanisimController.text)); _scoreMechanismController.addListener(() => _saveString('scoreMechanism', _scoreMechanismController.text));
_fuelPerCycle = prefs.getDouble(_generateKey('fuelPerCycle')) ?? 0.0; _fuelPerCycle = prefs.getDouble(_generateKey('fuelPerCycle')) ?? 0.0;
_canDriveUnderTrench = prefs.getBool(_generateKey('canDriveUnderTrench')) ?? false; _canDriveUnderTrench = prefs.getBool(_generateKey('canDriveUnderTrench')) ?? false;
@ -107,12 +107,12 @@ class _NotesPageState extends State<NotesPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text('Notes'), title: Text('Notes'),
), ),
body: _isLoading body: _isLoading
? const Center(child: CircularProgressIndicator()) ? const Center(child: CircularProgressIndicator())
: SafeArea( : SafeArea(
child: Column( child: Column(
children: [ children: [
Padding( Padding(
@ -144,7 +144,7 @@ class _NotesPageState extends State<NotesPage> {
), ),
], ],
), ),
) )
); );
} }
@ -192,6 +192,15 @@ class _NotesPageState extends State<NotesPage> {
), ),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
TextField(
controller: _intakePositionController,
decoration: const InputDecoration(
labelText: 'Intake Position',
hintText: 'e.g., Ground',
border: OutlineInputBorder(),
),
),
const SizedBox(height: 16),
SwitchListTile( SwitchListTile(
title: Text('Can Drive Over Bump', style: Theme.of(context).textTheme.titleSmall), title: Text('Can Drive Over Bump', style: Theme.of(context).textTheme.titleSmall),
value: _canDriveOverBump, value: _canDriveOverBump,
@ -303,12 +312,12 @@ class _NotesPageState extends State<NotesPage> {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('Cycle Time - ${_cycleTime.round()}', style: Theme.of(context).textTheme.titleSmall), Text('Cycle Time - ${_cycleTime.round()}s', style: Theme.of(context).textTheme.titleSmall),
Slider( Slider(
value: _cycleTime, value: _cycleTime,
min: 0, min: 0,
max: _cycleTime, max: 60,
divisions: 50, divisions: 60,
label: _cycleTime.round().toString(), label: _cycleTime.round().toString(),
onChanged: (double value) { onChanged: (double value) {
setState(() { setState(() {

View file

@ -32,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++) {
@ -47,41 +50,57 @@ 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 {

View file

@ -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.2.14+2 version: 26.2.22
environment: environment:
sdk: ^3.10.7 sdk: ^3.10.7