Compare commits

...

15 Commits

Author SHA1 Message Date
c7233daec7 mac OS compatibility 2024-11-08 23:20:09 +01:00
c03e9556b7 UI cleanup 2024-11-08 23:05:01 +01:00
bcd10ed512 simbrief own card in ui 2024-11-08 23:02:28 +01:00
4ab8b70524 simrbrief integration 2024-11-08 22:59:47 +01:00
10377a2a0a removed squawk from expected clearance page 2024-11-08 22:38:29 +01:00
69f0600d21 s 2024-10-21 04:13:22 +02:00
0ff3b71ef4 Merge pull request 'Easy pocketbase tracker' (#1) from usage-counter into main
Reviewed-on: #1
2024-10-21 01:43:35 +00:00
49e6806250 Easy pocketbase tracker 2024-10-21 03:42:22 +02:00
20e1c07519 Update pubspec.lock 2024-10-20 18:36:28 +02:00
db6e489de6 changed button name 2024-10-20 18:13:38 +02:00
51dd38ca9a fixed order from CRATF to CRAFT on main page (lol) 2024-10-20 18:06:37 +02:00
5ee6b33097 Icons 2024-10-20 02:18:41 +02:00
4aa5bf784b Update README.md 2024-10-19 12:42:57 +00:00
ec76a028cb Up2Date 2024-10-19 14:17:37 +02:00
9f98189983 LICENSE 2024-10-18 18:07:26 +02:00
14 changed files with 472 additions and 448 deletions

3
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"cmake.ignoreCMakeListsMissing": true
}

View File

View File

@@ -1,14 +1,3 @@
# IFR Buddy
**IFR Buddy** is a Flutter-based web application designed to help pilots and aviation enthusiasts easily record Instrument Flight Rules (IFR) clearances without the need for a pen. Featuring seamless navigation between input fields, dark mode, and a user-friendly interface, IFR Buddy simplifies the process of entering essential IFR details such as Clearance Limit, Route/SID, Altitude, Squawk Code, and Departure Frequency.
## Features
- **Seamless Navigation:** Move between input fields using keyboard buttons.
- **Dark Mode:** Toggle between light and dark themes for comfortable usage in any lighting condition.
- **Efficient Data Entry:** Quickly input and manage Clearance Limit, Route/SID, Altitude, Squawk Code, and Departure Frequency.
## License
This project is licensed under the MIT License.
# ifrbuddy
A simple way to write down IFR clearances using the CRAFT method.

1
buildandpush.sh Normal file
View File

@@ -0,0 +1 @@
flutter build web --release && docker buildx build --platform linux/amd64 -t git.degnedict.de/bene/ifrbuddy --push .

View File

@@ -12,7 +12,8 @@ class ComparisonPage extends StatefulWidget {
final bool isDarkMode;
final Function toggleDarkMode;
const ComparisonPage({super.key,
const ComparisonPage({
super.key,
required this.expectedClearanceLimit,
required this.expectedRoute,
required this.expectedAltitude,
@@ -159,130 +160,130 @@ class ComparisonPageState extends State<ComparisonPage> {
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('IFR Buddy'), // Global title
actions: [
IconButton(
icon: Icon(widget.isDarkMode ? Icons.dark_mode : Icons.light_mode),
onPressed: () {
widget.toggleDarkMode();
},
),
],
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: ListView(
children: [
// Explanation Text Card
const Card(
elevation: 2,
child: Padding(
padding: EdgeInsets.all(16.0),
child: Text(
'Use this Page while receiving your clearance. The arrow button copies the excpected value. Same if left empty.',
style: TextStyle(fontSize: 16),
textAlign: TextAlign.center,
),
),
),
const SizedBox(height: 16),
// Main content side-by-side
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Expected Clearance Column
Expanded(
child: Card(
elevation: 2,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Expected',
style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold),
),
const Divider(),
buildExpectedClearanceField('Clearance Limit', widget.expectedClearanceLimit),
buildExpectedClearanceField('Route', widget.expectedRoute),
buildExpectedClearanceField('Altitude', widget.expectedAltitude),
buildExpectedClearanceField('Transponder (Squawk)', widget.expectedSquawk),
buildExpectedClearanceField('Frequency', widget.expectedFrequency),
],
),
),
),
),
const SizedBox(width: 16), // Spacer between columns
// Actual Clearance Column
Expanded(
child: Card(
elevation: 2,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Clearance',
style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold),
),
const Divider(),
buildActualClearanceField(
'Clearance Limit',
_actualClearanceLimitController,
widget.expectedClearanceLimit,
),
buildActualClearanceField(
'Route',
_actualRouteController,
widget.expectedRoute,
),
buildActualClearanceField(
'Altitude',
_actualAltitudeController,
widget.expectedAltitude,
focusNode: _actualAltitudeFocusNode,
),
buildActualClearanceField(
'Transponder (Squawk)',
_actualSquawkController,
widget.expectedSquawk,
formatterList: [
FilteringTextInputFormatter.allow(RegExp(r'[0-7]')),
LengthLimitingTextInputFormatter(4),
],
),
buildActualClearanceField(
'Frequency',
_actualFrequencyController,
widget.expectedFrequency,
formatterList: [FrequencyInputFormatter()],
),
],
),
),
),
),
],
),
const SizedBox(height: 20),
// Proceed Button
ElevatedButton(
onPressed: _navigateToFinalClearance,
child: const Text('Proceed to Clearance Display'),
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('IFR Buddy'), // Global title
actions: [
IconButton(
icon: Icon(widget.isDarkMode ? Icons.dark_mode : Icons.light_mode),
onPressed: () {
widget.toggleDarkMode();
},
),
],
),
),
);
}
body: Padding(
padding: const EdgeInsets.all(16.0),
child: ListView(
children: [
// Explanation Text Card
const Card(
elevation: 2,
child: Padding(
padding: EdgeInsets.all(16.0),
child: Text(
'Use this Page while receiving your clearance.\nThe arrow button copies the expected value. If left empty, expected values are still copied. the arrow buttons are a help for the Brain :)',
style: TextStyle(fontSize: 16),
textAlign: TextAlign.center,
),
),
),
const SizedBox(height: 16),
// Main content side-by-side
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Expected Clearance Column
Expanded(
child: Card(
elevation: 2,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Expected',
style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold),
),
const Divider(),
buildExpectedClearanceField('Clearance Limit', widget.expectedClearanceLimit),
buildExpectedClearanceField('Route', widget.expectedRoute),
buildExpectedClearanceField('Altitude', widget.expectedAltitude),
buildExpectedClearanceField('Frequency', widget.expectedFrequency),
// buildExpectedClearanceField('Transponder (Squawk)', widget.expectedSquawk),
],
),
),
),
),
const SizedBox(width: 16), // Spacer between columns
// Actual Clearance Column
Expanded(
child: Card(
elevation: 2,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Clearance',
style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold),
),
const Divider(),
buildActualClearanceField(
'Clearance Limit',
_actualClearanceLimitController,
widget.expectedClearanceLimit,
),
buildActualClearanceField(
'Route',
_actualRouteController,
widget.expectedRoute,
),
buildActualClearanceField(
'Altitude',
_actualAltitudeController,
widget.expectedAltitude,
focusNode: _actualAltitudeFocusNode,
),
buildActualClearanceField(
'Frequency',
_actualFrequencyController,
widget.expectedFrequency,
formatterList: [FrequencyInputFormatter()],
),
buildActualClearanceField(
'Transponder (Squawk)',
_actualSquawkController,
widget.expectedSquawk,
formatterList: [
FilteringTextInputFormatter.allow(RegExp(r'[0-7]')),
LengthLimitingTextInputFormatter(4),
],
),
],
),
),
),
),
],
),
const SizedBox(height: 20),
// Proceed Button
ElevatedButton(
onPressed: _navigateToFinalClearance,
child: const Text('Proceed to Readback page'),
),
],
),
),
);
}
}

View File

@@ -1,9 +1,13 @@
// ignore_for_file: prefer_const_constructors
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';
import '../utils/frequency_input_formatter.dart'; // Ensure this path is correct
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'package:shared_preferences/shared_preferences.dart';
class ExpectationInputPage extends StatefulWidget {
final bool isDarkMode;
@@ -31,13 +35,46 @@ class ExpectationInputPageState extends State<ExpectationInputPage> {
TextEditingController();
final TextEditingController _expectedFrequencyController =
TextEditingController();
final TextEditingController _simbriefIdController = TextEditingController();
// Reintroduce FocusNodes
// FocusNodes for each field
final FocusNode _clearanceLimitFocusNode = FocusNode();
final FocusNode _routeFocusNode = FocusNode();
final FocusNode _altitudeFocusNode = FocusNode();
final FocusNode _squawkFocusNode = FocusNode();
final FocusNode _frequencyFocusNode = FocusNode();
final FocusNode _squawkFocusNode = FocusNode();
// Neue Variablen
bool _saveSimbriefId = false;
static const String _simbriefIdKey = 'simbrief_id';
@override
void initState() {
super.initState();
_loadSavedSimbriefId();
}
// Methode zum Laden der gespeicherten ID
Future<void> _loadSavedSimbriefId() async {
final prefs = await SharedPreferences.getInstance();
final savedId = prefs.getString(_simbriefIdKey);
if (savedId != null) {
setState(() {
_simbriefIdController.text = savedId;
_saveSimbriefId = true;
});
}
}
// Methode zum Speichern der ID
Future<void> _saveSimbriefIdToPrefs() async {
final prefs = await SharedPreferences.getInstance();
if (_saveSimbriefId) {
await prefs.setString(_simbriefIdKey, _simbriefIdController.text);
} else {
await prefs.remove(_simbriefIdKey);
}
}
@override
void dispose() {
@@ -49,11 +86,49 @@ class ExpectationInputPageState extends State<ExpectationInputPage> {
_clearanceLimitFocusNode.dispose();
_routeFocusNode.dispose();
_altitudeFocusNode.dispose();
_squawkFocusNode.dispose();
_frequencyFocusNode.dispose();
_squawkFocusNode.dispose();
super.dispose();
}
Future<void> _fetchSimbriefData() async {
try {
if (_saveSimbriefId) {
await _saveSimbriefIdToPrefs();
}
final response = await http.get(
Uri.parse('https://www.simbrief.com/api/xml.fetcher.php?userid=${_simbriefIdController.text}&json=1'),
);
// Prüfe ob Widget noch mounted ist
if (!mounted) return;
if (response.statusCode == 200) {
final data = json.decode(response.body);
String fullRoute = data['general']['route'] ?? '';
String sid = fullRoute.split(' ').first;
setState(() {
_expectedClearanceLimitController.text = data['destination']['icao_code'] ?? '';
_expectedRouteController.text = sid;
_expectedAltitudeController.text = '';
_expectedFrequencyController.text = '';
});
if (!mounted) return; // Zweiter Check vor ScaffoldMessenger
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('SimBrief Daten erfolgreich geladen')),
);
}
} catch (e) {
if (!mounted) return; // Check vor Error-SnackBar
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Fehler beim Laden der SimBrief Daten: $e')),
);
}
}
void _navigateToComparisonPage() {
if (_formKey.currentState?.validate() ?? false) {
Navigator.push(
@@ -93,7 +168,7 @@ class ExpectationInputPageState extends State<ExpectationInputPage> {
}
}
// Updated method to build a text field with optional input
// Method to build a text field with optional input and standard keyboard
Widget buildTextField({
required String label,
required TextEditingController controller,
@@ -120,20 +195,15 @@ class ExpectationInputPageState extends State<ExpectationInputPage> {
enableIMEPersonalizedLearning: enableIMEPersonalizedLearning,
onFieldSubmitted: (_) {
if (isLastField) {
// If it's the last field, unfocus to close the keyboard
// If it's the last field, unfocus to close the keyboard and submit
currentFocus.unfocus();
// Optionally, you can trigger form submission here
// _navigateToComparisonPage();
_navigateToComparisonPage();
} else if (nextFocus != null) {
FocusScope.of(context).requestFocus(nextFocus);
}
},
// Updated validator to allow empty inputs
// Validator allows empty inputs but validates if input is present
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)) {
@@ -144,7 +214,7 @@ class ExpectationInputPageState extends State<ExpectationInputPage> {
// Add more specific validations if needed
}
// Example: Validate Transponder (Squawk) field
// 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';
@@ -165,105 +235,6 @@ class ExpectationInputPageState extends State<ExpectationInputPage> {
);
}
// 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(
@@ -282,145 +253,216 @@ class ExpectationInputPageState extends State<ExpectationInputPage> {
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: SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
// IntrinsicHeight Widget hinzufügen
IntrinsicHeight(
child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch, // Stretch hinzufügen
children: [
// Welcome Card - nimmt 60% der Breite
Expanded(
flex: 3,
child: Card(
elevation: 2,
child: Padding(
padding: const EdgeInsets.all(16.0),
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
crossAxisAlignment: CrossAxisAlignment.start,
children: const [
Text(
'Welcome to IFR Buddy!',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
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'),
SizedBox(height: 10),
Text(
'Just an easy tool for writing down IFR clearances without a pen.',
style: TextStyle(fontSize: 16),
),
],
),
),
],
),
),
const SizedBox(width: 16), // Abstand zwischen den Cards
// SimBrief Card - nimmt 40% der Breite
Expanded(
flex: 2,
child: Card(
elevation: 2,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'SimBrief Import',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 10),
TextFormField(
controller: _simbriefIdController,
decoration: const InputDecoration(
labelText: 'SimBrief Pilot ID',
border: OutlineInputBorder(),
),
),
const SizedBox(height: 10),
Row(
children: [
Checkbox(
value: _saveSimbriefId,
onChanged: (bool? value) {
setState(() {
_saveSimbriefId = value ?? false;
});
_saveSimbriefIdToPrefs();
},
),
const Text('Save SimBrief ID'),
],
),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: _fetchSimbriefData,
child: const Text('Load SimBrief Data'),
),
),
],
),
),
),
),
],
),
),
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 (Or use the SimBrief import). '
'Use the listening page or skip to the readback page.',
style: TextStyle(fontSize: 16),
textAlign: TextAlign.left,
),
const SizedBox(height: 16),
Padding(
padding: const EdgeInsets.all(8.0),
),
Form(
key: _formKey,
child: Column(
children: [
buildTextField(
label: 'Clearance Limit',
controller: _expectedClearanceLimitController,
currentFocus: _clearanceLimitFocusNode,
nextFocus: _routeFocusNode,
keyboardType: TextInputType.text, // Standard keyboard
inputFormatters: [
// Allow letters, numbers, spaces, and hyphens
FilteringTextInputFormatter.allow(
RegExp(r'[A-Za-z0-9\s\-]')),
],
enableAutocorrect: false, // Disable autocorrect
enableSuggestions: false, // Disable suggestions
enableIMEPersonalizedLearning:
false, // iOS-specific
),
buildTextField(
label: 'Route/Sid',
controller: _expectedRouteController,
currentFocus: _routeFocusNode,
nextFocus: _altitudeFocusNode,
keyboardType: TextInputType.text, // Standard keyboard
inputFormatters: [
// Allow letters, numbers, spaces, and hyphens
FilteringTextInputFormatter.allow(
RegExp(r'[A-Za-z0-9\s\-]')),
],
enableAutocorrect: false, // Disable autocorrect
enableSuggestions: false, // Disable suggestions
enableIMEPersonalizedLearning:
false, // iOS-specific
),
buildTextField(
label: 'Altitude',
controller: _expectedAltitudeController,
currentFocus: _altitudeFocusNode,
nextFocus: _frequencyFocusNode, // Updated next focus
keyboardType: TextInputType.text, // Standard keyboard
inputFormatters: null, // No restrictions
enableAutocorrect: false, // Disable autocorrect
enableSuggestions: false, // Disable suggestions
enableIMEPersonalizedLearning:
false, // iOS-specific
),
buildTextField(
label: 'Departure Frequency', // Changed label
controller: _expectedFrequencyController,
currentFocus: _frequencyFocusNode,
nextFocus: _squawkFocusNode, // Next focus to Squawk
keyboardType: TextInputType.text, // Standard keyboard
inputFormatters: [
FrequencyInputFormatter(), // Handles decimal inputs
],
enableAutocorrect: false, // Disable autocorrect
enableSuggestions: false, // Disable suggestions
enableIMEPersonalizedLearning:
false, // iOS-specific
),
// buildTextField(
// label: 'Transponder (Squawk)', // Now last field
// controller: _expectedSquawkController,
// currentFocus: _squawkFocusNode,
// isLastField: true, // Mark as last field
// keyboardType: TextInputType.text, // Standard keyboard
// inputFormatters: [
// // Allow only digits 0-7 and limit to 4 characters
// FilteringTextInputFormatter.allow(
// RegExp(r'[0-7]')),
// LengthLimitingTextInputFormatter(4),
// ],
// enableAutocorrect: false, // Disable autocorrect
// enableSuggestions: false, // Disable suggestions
// enableIMEPersonalizedLearning:
// false, // iOS-specific
// ),
const SizedBox(height: 20),
ElevatedButton(
onPressed: _navigateToComparisonPage,
child: const Text('Next'),
),
const SizedBox(height: 10),
TextButton(
onPressed: _skipReadback,
child: const Text('Skip to end'),
),
],
),
),
],
),
),
],
),
),
],
),
),
),
);
}
}
}

View File

@@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:pocketbase/pocketbase.dart'; // Import the PocketBase package
import 'expectation_input_page.dart';
import 'edit_clearance_page.dart'; // Import the edit clearance page
import '../widgets/clearance_field.dart';
class FinalClearanceDisplay extends StatefulWidget {
@@ -12,7 +12,8 @@ class FinalClearanceDisplay extends StatefulWidget {
final bool isDarkMode;
final Function toggleDarkMode;
const FinalClearanceDisplay({super.key,
const FinalClearanceDisplay({
super.key,
required this.clearanceLimit,
required this.route,
required this.altitude,
@@ -32,6 +33,7 @@ class FinalClearanceDisplayState extends State<FinalClearanceDisplay> {
late String altitude;
late String squawk;
late String frequency;
final PocketBase pb = PocketBase('https://backend.degnedict.de'); // Initialize PocketBase
@override
void initState() {
@@ -41,36 +43,23 @@ class FinalClearanceDisplayState extends State<FinalClearanceDisplay> {
altitude = widget.altitude;
squawk = widget.squawk;
frequency = widget.frequency;
_createPageViewRecord(); // Create a record with the current timestamp
}
// Navigate to Edit Clearance Page and handle returned data
Future<void> _navigateToEditClearance() async {
final result = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => EditClearancePage(
clearanceLimit: clearanceLimit,
route: route,
altitude: altitude,
squawk: squawk,
frequency: frequency,
isDarkMode: widget.isDarkMode,
toggleDarkMode: widget.toggleDarkMode,
),
),
);
// Function to create a new record with a timestamp in epoch milliseconds in your PocketBase collection
Future<void> _createPageViewRecord() async {
try {
// Get the current time in epoch milliseconds
int currentTimeInMillis = DateTime.now().millisecondsSinceEpoch;
// If the result contains updated data, update the state with the new values
if (result != null && mounted) {
setState(() {
clearanceLimit = result['clearanceLimit'];
route = result['route'];
altitude = result['altitude'];
squawk = result['squawk'];
frequency = result['frequency'];
});
}
// Create a new record in the collection with the current epoch time
await pb.collection('IFRbuddyUsage').create(body: {
'timestamp': currentTimeInMillis, // Save current timestamp as epoch time (milliseconds)
});
} catch (e) {
}
}
// Navigate back to the first page (ExpectationInputPage)
void _navigateHome(BuildContext context) {
@@ -97,18 +86,7 @@ class FinalClearanceDisplayState extends State<FinalClearanceDisplay> {
_navigateHome(context);
},
),
actions: [
IconButton(
icon: const Icon(Icons.edit), // Edit button
onPressed: _navigateToEditClearance,
),
IconButton(
icon: Icon(widget.isDarkMode ? Icons.dark_mode : Icons.light_mode),
onPressed: () {
widget.toggleDarkMode();
},
),
],
actions: const [],
),
body: Center(
child: SingleChildScrollView(
@@ -129,8 +107,8 @@ class FinalClearanceDisplayState extends State<FinalClearanceDisplay> {
buildClearanceField('Clearance Limit', clearanceLimit),
buildClearanceField('Route', route),
buildClearanceField('Altitude', altitude),
buildClearanceField('Transponder (Squawk)', squawk),
buildClearanceField('Frequency', frequency),
buildClearanceField('Transponder (Squawk)', squawk),
],
),
),
@@ -139,4 +117,4 @@ class FinalClearanceDisplayState extends State<FinalClearanceDisplay> {
),
);
}
}
}

View File

@@ -8,5 +8,7 @@
<true/>
<key>com.apple.security.network.server</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
</dict>
</plist>

View File

@@ -4,5 +4,8 @@
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.network.server</key>w
</dict>
</plist>

View File

@@ -176,30 +176,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.9.0"
keyboard_actions:
dependency: "direct main"
description:
name: keyboard_actions
sha256: "31e0ab2a706ac8f58887efa60efc1f19aecdf37d8ab0f665a0f156d1fbeab650"
url: "https://pub.dev"
source: hosted
version: "4.2.0"
leak_tracker:
dependency: transitive
description:
name: leak_tracker
sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a"
sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05"
url: "https://pub.dev"
source: hosted
version: "10.0.4"
version: "10.0.5"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8"
sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806"
url: "https://pub.dev"
source: hosted
version: "3.0.3"
version: "3.0.5"
leak_tracker_testing:
dependency: transitive
description:
@@ -228,18 +220,18 @@ packages:
dependency: transitive
description:
name: material_color_utilities
sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
url: "https://pub.dev"
source: hosted
version: "0.8.0"
version: "0.11.1"
meta:
dependency: transitive
description:
name: meta
sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136"
sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
url: "https://pub.dev"
source: hosted
version: "1.12.0"
version: "1.15.0"
path:
dependency: transitive
description:
@@ -296,6 +288,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.8"
pocketbase:
dependency: "direct main"
description:
name: pocketbase
sha256: "1d2958a3a7cb1e0050f425f179bd6557441fafcf740a79d5b8b80d6954149790"
url: "https://pub.dev"
source: hosted
version: "0.18.1"
shared_preferences:
dependency: "direct main"
description:
@@ -401,10 +401,10 @@ packages:
dependency: transitive
description:
name: test_api
sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f"
sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb"
url: "https://pub.dev"
source: hosted
version: "0.7.0"
version: "0.7.2"
typed_data:
dependency: transitive
description:
@@ -425,10 +425,10 @@ packages:
dependency: transitive
description:
name: vm_service
sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec"
sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d"
url: "https://pub.dev"
source: hosted
version: "14.2.1"
version: "14.2.5"
web:
dependency: transitive
description:

View File

@@ -36,9 +36,8 @@ dependencies:
cupertino_icons: ^1.0.6
http: ^1.2.2
shared_preferences: ^2.3.2
keyboard_actions: ^4.2.0
flutter_launcher_icons: ^0.14.1
pocketbase: ^0.18.1
dev_dependencies:
flutter_test:
sdk: flutter
@@ -50,6 +49,12 @@ dev_dependencies:
# rules and activating additional ones.
flutter_lints: ^3.0.0
flutter_icons:
android: true
ios: true
macos: true # Enable macOS icon generation
image_path: "assets/icon-512.png" # Path to your icon image
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec

Binary file not shown.

Before

Width:  |  Height:  |  Size: 403 B

View File

@@ -18,7 +18,7 @@
<meta charset="UTF-8">
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
<meta name="description" content="A new Flutter project.">
<meta name="description" content="Write down IFR Clearances easily">
<!-- No cache tag -->
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
@@ -29,7 +29,7 @@
<!-- iOS meta tags & icons -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="ifrbuddy">
<meta name="apple-mobile-web-app-title" content="IFR Buddy">
<link rel="apple-touch-icon" href="icons/Icon-192.png">
<!-- Favicon -->

View File

@@ -1,11 +1,11 @@
{
"name": "ifrbuddy",
"short_name": "ifrbuddy",
"name": "IFR Buddy",
"short_name": "IFR Buddy",
"start_url": ".",
"display": "standalone",
"background_color": "#hexcode",
"theme_color": "#hexcode",
"description": "A new Flutter project.",
"description": "Write down IFR Clearances easily",
"orientation": "portrait-primary",
"prefer_related_applications": false,
"icons": [