337 lines
No EOL
11 KiB
Dart
337 lines
No EOL
11 KiB
Dart
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;
|
|
final String teamName;
|
|
|
|
const NotesPage({
|
|
super.key,
|
|
required this.teamCode,
|
|
required this.teamName,
|
|
required this.eventCode,
|
|
});
|
|
|
|
@override
|
|
State<NotesPage> createState() => _NotesPageState();
|
|
}
|
|
|
|
class _NotesPageState extends State<NotesPage> {
|
|
late SharedPreferences prefs;
|
|
ScoutingView _selectedView = ScoutingView.match;
|
|
bool _isLoading = true;
|
|
|
|
final _botPositionController = TextEditingController();
|
|
final _generalObservationsController = TextEditingController();
|
|
final _autonRundownController = TextEditingController();
|
|
final _intakePositionController = TextEditingController();
|
|
final _scoreMechanismController = TextEditingController();
|
|
double _fuelPerCycle = 0;
|
|
bool _canDriveUnderTrench = false;
|
|
bool _canDriveOverBump = false;
|
|
double _climbLevel = 0.0;
|
|
double _fuelCapacity = 0.0;
|
|
bool _canGiveToHumanPlayer = false;
|
|
double _cycleTime = 0.0;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_loadNotes();
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_autonRundownController.dispose();
|
|
_botPositionController.dispose();
|
|
_generalObservationsController.dispose();
|
|
_intakePositionController.dispose();
|
|
_scoreMechanismController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
String _generateKey(String field) {
|
|
return '${widget.teamCode}_${widget.eventCode}_$field';
|
|
}
|
|
|
|
Future<void> _loadNotes() async {
|
|
prefs = await SharedPreferences.getInstance();
|
|
|
|
_botPositionController.text = prefs.getString(_generateKey('botPosition')) ?? '';
|
|
_botPositionController.addListener(() => _saveString('botPosition', _botPositionController.text));
|
|
|
|
_generalObservationsController.text = prefs.getString(_generateKey('generalObservations')) ?? '';
|
|
_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) {
|
|
setState(() {
|
|
_isLoading = false;
|
|
});
|
|
}
|
|
}
|
|
|
|
Future<void> _saveString(String field, String value) async {
|
|
await prefs.setString(_generateKey(field), value);
|
|
}
|
|
|
|
Future<void> _saveBool(String field, bool value) async {
|
|
await prefs.setBool(_generateKey(field), value);
|
|
}
|
|
|
|
Future<void> _saveDouble(String field, double value) async {
|
|
await prefs.setDouble(_generateKey(field), value);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
appBar: AppBar(
|
|
title: Text('Notes'),
|
|
),
|
|
body: _isLoading
|
|
? const Center(child: CircularProgressIndicator())
|
|
: SafeArea(
|
|
child: 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<ScoutingView>(
|
|
segments: const [
|
|
ButtonSegment(value: ScoutingView.match, label: Text('Match Scouting')),
|
|
ButtonSegment(value: ScoutingView.pit, label: Text('Pit Scouting')),
|
|
],
|
|
selected: {_selectedView},
|
|
onSelectionChanged: (newSelection) {
|
|
setState(() {
|
|
_selectedView = newSelection.first;
|
|
});
|
|
},
|
|
),
|
|
),
|
|
Expanded(
|
|
child: AnimatedSwitcher(
|
|
duration: const Duration(milliseconds: 300),
|
|
child: _selectedView == ScoutingView.match
|
|
? _buildMatchScoutingView()
|
|
: _buildPitScoutingView(),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
)
|
|
);
|
|
}
|
|
|
|
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: _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: _autonRundownController,
|
|
decoration: const InputDecoration(
|
|
labelText: 'Autonomous Rundown',
|
|
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(),
|
|
),
|
|
),
|
|
const SizedBox(height: 16),
|
|
SwitchListTile(
|
|
title: Text('Can Drive Over Bump', style: Theme.of(context).textTheme.titleSmall),
|
|
value: _canDriveOverBump,
|
|
onChanged: (bool value) {
|
|
setState(() {
|
|
_canDriveOverBump = value;
|
|
});
|
|
_saveBool('canDriveOverBump', value);
|
|
},
|
|
),
|
|
SwitchListTile(
|
|
title: Text('Can Go Under Trench', style: Theme.of(context).textTheme.titleSmall),
|
|
value: _canDriveUnderTrench,
|
|
onChanged: (bool value) {
|
|
setState(() {
|
|
_canDriveUnderTrench = 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),
|
|
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);
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
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);
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
} |