Files
ifrbuddy/lib/screens/expectation_input_page.dart
2024-10-18 17:23:30 +02:00

426 lines
16 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:keyboard_actions/keyboard_actions.dart'; // Correct package import
import 'comparison_page.dart';
import 'final_clearance_page.dart'; // Import final clearance page
import '../utils/frequency_input_formatter.dart';
class ExpectationInputPage extends StatefulWidget {
final bool isDarkMode;
final Function toggleDarkMode;
const ExpectationInputPage({
super.key,
required this.isDarkMode,
required this.toggleDarkMode,
});
@override
ExpectationInputPageState createState() => ExpectationInputPageState();
}
class ExpectationInputPageState extends State<ExpectationInputPage> {
final _formKey = GlobalKey<FormState>();
final TextEditingController _expectedClearanceLimitController =
TextEditingController();
final TextEditingController _expectedRouteController = TextEditingController();
final TextEditingController _expectedAltitudeController =
TextEditingController();
final TextEditingController _expectedSquawkController =
TextEditingController();
final TextEditingController _expectedFrequencyController =
TextEditingController();
// Reintroduce FocusNodes
final FocusNode _clearanceLimitFocusNode = FocusNode();
final FocusNode _routeFocusNode = FocusNode();
final FocusNode _altitudeFocusNode = FocusNode();
final FocusNode _squawkFocusNode = FocusNode();
final FocusNode _frequencyFocusNode = FocusNode();
@override
void dispose() {
_expectedClearanceLimitController.dispose();
_expectedRouteController.dispose();
_expectedAltitudeController.dispose();
_expectedSquawkController.dispose();
_expectedFrequencyController.dispose();
_clearanceLimitFocusNode.dispose();
_routeFocusNode.dispose();
_altitudeFocusNode.dispose();
_squawkFocusNode.dispose();
_frequencyFocusNode.dispose();
super.dispose();
}
void _navigateToComparisonPage() {
if (_formKey.currentState?.validate() ?? false) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ComparisonPage(
expectedClearanceLimit: _expectedClearanceLimitController.text,
expectedRoute: _expectedRouteController.text,
expectedAltitude: _expectedAltitudeController.text,
expectedSquawk: _expectedSquawkController.text,
expectedFrequency: _expectedFrequencyController.text,
isDarkMode: widget.isDarkMode,
toggleDarkMode: widget.toggleDarkMode,
),
),
);
}
}
// Function to skip directly to final clearance page
void _skipReadback() {
if (_formKey.currentState?.validate() ?? false) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => FinalClearanceDisplay(
clearanceLimit: _expectedClearanceLimitController.text,
route: _expectedRouteController.text,
altitude: _expectedAltitudeController.text,
squawk: _expectedSquawkController.text,
frequency: _expectedFrequencyController.text,
isDarkMode: widget.isDarkMode,
toggleDarkMode: widget.toggleDarkMode,
),
),
);
}
}
// Updated method to build a text field with optional input
Widget buildTextField({
required String label,
required TextEditingController controller,
required FocusNode currentFocus,
FocusNode? nextFocus,
TextInputType keyboardType = TextInputType.text,
List<TextInputFormatter>? inputFormatters,
bool isLastField = false,
bool enableAutocorrect = false, // To disable autocorrect
bool enableSuggestions = false, // To disable suggestions
bool enableIMEPersonalizedLearning = false, // iOS-specific
}) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 10),
child: TextFormField(
controller: controller,
focusNode: currentFocus,
keyboardType: keyboardType,
textInputAction:
isLastField ? TextInputAction.done : TextInputAction.next,
inputFormatters: inputFormatters,
autocorrect: enableAutocorrect,
enableSuggestions: enableSuggestions,
enableIMEPersonalizedLearning: enableIMEPersonalizedLearning,
onFieldSubmitted: (_) {
if (isLastField) {
// If it's the last field, unfocus to close the keyboard
currentFocus.unfocus();
// Optionally, you can trigger form submission here
// _navigateToComparisonPage();
} else if (nextFocus != null) {
FocusScope.of(context).requestFocus(nextFocus);
}
},
// Updated validator to allow empty inputs
validator: (value) {
// If you want to perform validation only when input is not empty,
// you can add conditional checks here.
// Example: Validate numerical fields if they are not empty
if (value != null && value.isNotEmpty) {
if (keyboardType == TextInputType.number ||
keyboardType == const TextInputType.numberWithOptions(decimal: true)) {
final number = double.tryParse(value);
if (number == null) {
return 'Please enter a valid number';
}
// Add more specific validations if needed
}
// Example: Validate Transponder (Squawk) field
if (label == 'Transponder (Squawk)') {
if (!RegExp(r'^[0-7]{1,4}$').hasMatch(value)) {
return 'Squawk must be 1-4 digits between 0 and 7';
}
}
// Add other conditional validations as necessary
}
// If no issues, return null
return null;
},
decoration: InputDecoration(
labelText: label,
border: const OutlineInputBorder(),
),
),
);
}
// Configure KeyboardActions
KeyboardActionsConfig _buildConfig(BuildContext context) {
return KeyboardActionsConfig(
keyboardSeparatorColor: Colors.grey, // Optional: Customize the separator color
nextFocus: true, // Automatically handle next focus
actions: [
// Define actions for each FocusNode
KeyboardActionsItem(
focusNode: _clearanceLimitFocusNode,
toolbarButtons: [
(node) {
return GestureDetector(
onTap: () => node.nextFocus(),
child: const Padding(
padding: EdgeInsets.all(8.0),
child: Text(
"Next",
style: TextStyle(color: Colors.blue, fontSize: 16),
),
),
);
}
],
),
KeyboardActionsItem(
focusNode: _routeFocusNode,
toolbarButtons: [
(node) {
return GestureDetector(
onTap: () => node.nextFocus(),
child: const Padding(
padding: EdgeInsets.all(8.0),
child: Text(
"Next",
style: TextStyle(color: Colors.blue, fontSize: 16),
),
),
);
}
],
),
KeyboardActionsItem(
focusNode: _altitudeFocusNode,
toolbarButtons: [
(node) {
return GestureDetector(
onTap: () => node.nextFocus(),
child: const Padding(
padding: EdgeInsets.all(8.0),
child: Text(
"Next",
style: TextStyle(color: Colors.blue, fontSize: 16),
),
),
);
}
],
),
KeyboardActionsItem(
focusNode: _squawkFocusNode,
toolbarButtons: [
(node) {
return GestureDetector(
onTap: () => node.nextFocus(),
child: const Padding(
padding: EdgeInsets.all(8.0),
child: Text(
"Next",
style: TextStyle(color: Colors.blue, fontSize: 16),
),
),
);
}
],
),
KeyboardActionsItem(
focusNode: _frequencyFocusNode,
toolbarButtons: [
(node) {
return GestureDetector(
onTap: () {
node.unfocus(); // Dismiss the keyboard
_navigateToComparisonPage(); // Optionally, submit the form
},
child: const Padding(
padding: EdgeInsets.all(8.0),
child: Text(
"Done",
style: TextStyle(color: Colors.blue, fontSize: 16),
),
),
);
}
],
),
],
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('IFR Buddy'),
actions: [
IconButton(
icon: Icon(
widget.isDarkMode ? Icons.dark_mode : Icons.light_mode),
onPressed: () {
widget.toggleDarkMode();
},
),
],
),
body: GestureDetector(
// Dismiss the keyboard when tapping outside
onTap: () => FocusScope.of(context).unfocus(),
child: KeyboardActions(
config: _buildConfig(context), // Apply KeyboardActionsConfig
child: SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
const Card(
elevation: 2,
child: Padding(
padding: EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Welcome to IFR Buddy!',
style: TextStyle(
fontSize: 18, fontWeight: FontWeight.bold),
textAlign: TextAlign.left,
),
SizedBox(height: 10),
Text(
'Just an easy tool for writing down IFR clearances without a pen.',
style: TextStyle(fontSize: 16),
textAlign: TextAlign.left,
),
],
),
),
),
const SizedBox(height: 16),
Card(
elevation: 2,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Expected Clearance',
style: TextStyle(
fontSize: 22, fontWeight: FontWeight.bold),
),
const SizedBox(height: 10),
const Text(
'Enter your expected clearance information below. '
'Use the listening page or skip to the readback page.',
style: TextStyle(fontSize: 16),
textAlign: TextAlign.left,
),
const SizedBox(height: 16),
Form(
key: _formKey,
child: Column(
children: [
buildTextField(
label: 'Clearance Limit',
controller: _expectedClearanceLimitController,
currentFocus: _clearanceLimitFocusNode,
nextFocus: _routeFocusNode,
enableAutocorrect: false, // Disable autocorrect
enableSuggestions: false, // Disable suggestions
enableIMEPersonalizedLearning:
false, // iOS-specific
),
buildTextField(
label: 'Route/Sid',
controller: _expectedRouteController,
currentFocus: _routeFocusNode,
nextFocus: _altitudeFocusNode,
enableAutocorrect: false, // Disable autocorrect
enableSuggestions: false, // Disable suggestions
enableIMEPersonalizedLearning:
false, // iOS-specific
),
buildTextField(
label: 'Altitude',
controller: _expectedAltitudeController,
currentFocus: _altitudeFocusNode,
nextFocus: _squawkFocusNode,
keyboardType: TextInputType.number,
inputFormatters: [
FilteringTextInputFormatter.digitsOnly
],
enableAutocorrect: false, // Disable autocorrect
enableSuggestions: false, // Disable suggestions
enableIMEPersonalizedLearning:
false, // iOS-specific
),
buildTextField(
label: 'Transponder (Squawk)',
controller: _expectedSquawkController,
currentFocus: _squawkFocusNode,
nextFocus: _frequencyFocusNode,
keyboardType: TextInputType.number,
inputFormatters: [
FilteringTextInputFormatter.allow(
RegExp(r'[0-7]')),
LengthLimitingTextInputFormatter(4),
],
enableAutocorrect: false, // Disable autocorrect
enableSuggestions: false, // Disable suggestions
enableIMEPersonalizedLearning:
false, // iOS-specific
),
buildTextField(
label: 'Departure Frequency',
controller: _expectedFrequencyController,
currentFocus: _frequencyFocusNode,
isLastField: true,
keyboardType:
const TextInputType.numberWithOptions(decimal: true),
inputFormatters: [FrequencyInputFormatter()],
enableAutocorrect: false, // Disable autocorrect
enableSuggestions: false, // Disable suggestions
enableIMEPersonalizedLearning:
false, // iOS-specific
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: _skipReadback,
child: const Text('Skip to Readback'),
),
const SizedBox(height: 10),
ElevatedButton(
onPressed: _navigateToComparisonPage,
child: const Text('Navigate to Comparison Page'),
),
],
),
),
],
),
),
),
],
),
),
),
),
);
}
}