Compare commits
18 Commits
Stable
...
c7233daec7
| Author | SHA1 | Date | |
|---|---|---|---|
| c7233daec7 | |||
| c03e9556b7 | |||
| bcd10ed512 | |||
| 4ab8b70524 | |||
| 10377a2a0a | |||
| 69f0600d21 | |||
| 0ff3b71ef4 | |||
| 49e6806250 | |||
| 20e1c07519 | |||
| db6e489de6 | |||
| 51dd38ca9a | |||
| 5ee6b33097 | |||
| 4aa5bf784b | |||
| ec76a028cb | |||
| 9f98189983 | |||
| c344f57698 | |||
| 7d905a84a6 | |||
| 2e3a1993e0 |
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"cmake.ignoreCMakeListsMissing": true
|
||||||
|
}
|
||||||
7
LICENSE
Normal file
7
LICENSE
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
Copyright 2024 Degnedict
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
15
README.md
15
README.md
@@ -1,16 +1,3 @@
|
|||||||
# ifrbuddy
|
# ifrbuddy
|
||||||
|
|
||||||
A new Flutter project.
|
A simple way to write down IFR clearances using the CRAFT method.
|
||||||
|
|
||||||
## Getting Started
|
|
||||||
|
|
||||||
This project is a starting point for a Flutter application.
|
|
||||||
|
|
||||||
A few resources to get you started if this is your first Flutter project:
|
|
||||||
|
|
||||||
- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
|
|
||||||
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
|
|
||||||
|
|
||||||
For help getting started with Flutter development, view the
|
|
||||||
[online documentation](https://docs.flutter.dev/), which offers tutorials,
|
|
||||||
samples, guidance on mobile development, and a full API reference.
|
|
||||||
1
buildandpush.sh
Normal file
1
buildandpush.sh
Normal file
@@ -0,0 +1 @@
|
|||||||
|
flutter build web --release && docker buildx build --platform linux/amd64 -t git.degnedict.de/bene/ifrbuddy --push .
|
||||||
@@ -12,7 +12,8 @@ class ComparisonPage extends StatefulWidget {
|
|||||||
final bool isDarkMode;
|
final bool isDarkMode;
|
||||||
final Function toggleDarkMode;
|
final Function toggleDarkMode;
|
||||||
|
|
||||||
const ComparisonPage({super.key,
|
const ComparisonPage({
|
||||||
|
super.key,
|
||||||
required this.expectedClearanceLimit,
|
required this.expectedClearanceLimit,
|
||||||
required this.expectedRoute,
|
required this.expectedRoute,
|
||||||
required this.expectedAltitude,
|
required this.expectedAltitude,
|
||||||
@@ -159,130 +160,130 @@ class ComparisonPageState extends State<ComparisonPage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: const Text('IFR Buddy'), // Global title
|
title: const Text('IFR Buddy'), // Global title
|
||||||
actions: [
|
actions: [
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: Icon(widget.isDarkMode ? Icons.dark_mode : Icons.light_mode),
|
icon: Icon(widget.isDarkMode ? Icons.dark_mode : Icons.light_mode),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
widget.toggleDarkMode();
|
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'),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
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'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,13 @@
|
|||||||
|
// ignore_for_file: prefer_const_constructors
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:keyboard_actions/keyboard_actions.dart'; // Correct package import
|
|
||||||
import 'comparison_page.dart';
|
import 'comparison_page.dart';
|
||||||
import 'final_clearance_page.dart'; // Import final clearance page
|
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 {
|
class ExpectationInputPage extends StatefulWidget {
|
||||||
final bool isDarkMode;
|
final bool isDarkMode;
|
||||||
@@ -31,13 +35,46 @@ class ExpectationInputPageState extends State<ExpectationInputPage> {
|
|||||||
TextEditingController();
|
TextEditingController();
|
||||||
final TextEditingController _expectedFrequencyController =
|
final TextEditingController _expectedFrequencyController =
|
||||||
TextEditingController();
|
TextEditingController();
|
||||||
|
final TextEditingController _simbriefIdController = TextEditingController();
|
||||||
|
|
||||||
// Reintroduce FocusNodes
|
// FocusNodes for each field
|
||||||
final FocusNode _clearanceLimitFocusNode = FocusNode();
|
final FocusNode _clearanceLimitFocusNode = FocusNode();
|
||||||
final FocusNode _routeFocusNode = FocusNode();
|
final FocusNode _routeFocusNode = FocusNode();
|
||||||
final FocusNode _altitudeFocusNode = FocusNode();
|
final FocusNode _altitudeFocusNode = FocusNode();
|
||||||
final FocusNode _squawkFocusNode = FocusNode();
|
|
||||||
final FocusNode _frequencyFocusNode = 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
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
@@ -49,11 +86,49 @@ class ExpectationInputPageState extends State<ExpectationInputPage> {
|
|||||||
_clearanceLimitFocusNode.dispose();
|
_clearanceLimitFocusNode.dispose();
|
||||||
_routeFocusNode.dispose();
|
_routeFocusNode.dispose();
|
||||||
_altitudeFocusNode.dispose();
|
_altitudeFocusNode.dispose();
|
||||||
_squawkFocusNode.dispose();
|
|
||||||
_frequencyFocusNode.dispose();
|
_frequencyFocusNode.dispose();
|
||||||
|
_squawkFocusNode.dispose();
|
||||||
super.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() {
|
void _navigateToComparisonPage() {
|
||||||
if (_formKey.currentState?.validate() ?? false) {
|
if (_formKey.currentState?.validate() ?? false) {
|
||||||
Navigator.push(
|
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({
|
Widget buildTextField({
|
||||||
required String label,
|
required String label,
|
||||||
required TextEditingController controller,
|
required TextEditingController controller,
|
||||||
@@ -120,20 +195,15 @@ class ExpectationInputPageState extends State<ExpectationInputPage> {
|
|||||||
enableIMEPersonalizedLearning: enableIMEPersonalizedLearning,
|
enableIMEPersonalizedLearning: enableIMEPersonalizedLearning,
|
||||||
onFieldSubmitted: (_) {
|
onFieldSubmitted: (_) {
|
||||||
if (isLastField) {
|
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();
|
currentFocus.unfocus();
|
||||||
// Optionally, you can trigger form submission here
|
_navigateToComparisonPage();
|
||||||
// _navigateToComparisonPage();
|
|
||||||
} else if (nextFocus != null) {
|
} else if (nextFocus != null) {
|
||||||
FocusScope.of(context).requestFocus(nextFocus);
|
FocusScope.of(context).requestFocus(nextFocus);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// Updated validator to allow empty inputs
|
// Validator allows empty inputs but validates if input is present
|
||||||
validator: (value) {
|
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 (value != null && value.isNotEmpty) {
|
||||||
if (keyboardType == TextInputType.number ||
|
if (keyboardType == TextInputType.number ||
|
||||||
keyboardType == const TextInputType.numberWithOptions(decimal: true)) {
|
keyboardType == const TextInputType.numberWithOptions(decimal: true)) {
|
||||||
@@ -144,7 +214,7 @@ class ExpectationInputPageState extends State<ExpectationInputPage> {
|
|||||||
// Add more specific validations if needed
|
// Add more specific validations if needed
|
||||||
}
|
}
|
||||||
|
|
||||||
// Example: Validate Transponder (Squawk) field
|
// Validate Transponder (Squawk) field
|
||||||
if (label == 'Transponder (Squawk)') {
|
if (label == 'Transponder (Squawk)') {
|
||||||
if (!RegExp(r'^[0-7]{1,4}$').hasMatch(value)) {
|
if (!RegExp(r'^[0-7]{1,4}$').hasMatch(value)) {
|
||||||
return 'Squawk must be 1-4 digits between 0 and 7';
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
@@ -282,145 +253,216 @@ class ExpectationInputPageState extends State<ExpectationInputPage> {
|
|||||||
body: GestureDetector(
|
body: GestureDetector(
|
||||||
// Dismiss the keyboard when tapping outside
|
// Dismiss the keyboard when tapping outside
|
||||||
onTap: () => FocusScope.of(context).unfocus(),
|
onTap: () => FocusScope.of(context).unfocus(),
|
||||||
child: KeyboardActions(
|
child: SingleChildScrollView(
|
||||||
config: _buildConfig(context), // Apply KeyboardActionsConfig
|
padding: const EdgeInsets.all(16.0),
|
||||||
child: SingleChildScrollView(
|
child: Column(
|
||||||
padding: const EdgeInsets.all(16.0),
|
children: [
|
||||||
child: Column(
|
// IntrinsicHeight Widget hinzufügen
|
||||||
children: [
|
IntrinsicHeight(
|
||||||
const Card(
|
child: Row(
|
||||||
elevation: 2,
|
crossAxisAlignment: CrossAxisAlignment.stretch, // Stretch hinzufügen
|
||||||
child: Padding(
|
children: [
|
||||||
padding: EdgeInsets.all(16.0),
|
// Welcome Card - nimmt 60% der Breite
|
||||||
child: Column(
|
Expanded(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
flex: 3,
|
||||||
children: [
|
child: Card(
|
||||||
Text(
|
elevation: 2,
|
||||||
'Welcome to IFR Buddy!',
|
child: Padding(
|
||||||
style: TextStyle(
|
padding: const EdgeInsets.all(16.0),
|
||||||
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(
|
child: Column(
|
||||||
children: [
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
buildTextField(
|
children: const [
|
||||||
label: 'Clearance Limit',
|
Text(
|
||||||
controller: _expectedClearanceLimitController,
|
'Welcome to IFR Buddy!',
|
||||||
currentFocus: _clearanceLimitFocusNode,
|
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||||
nextFocus: _routeFocusNode,
|
|
||||||
enableAutocorrect: false, // Disable autocorrect
|
|
||||||
enableSuggestions: false, // Disable suggestions
|
|
||||||
enableIMEPersonalizedLearning:
|
|
||||||
false, // iOS-specific
|
|
||||||
),
|
),
|
||||||
buildTextField(
|
SizedBox(height: 10),
|
||||||
label: 'Route/Sid',
|
Text(
|
||||||
controller: _expectedRouteController,
|
'Just an easy tool for writing down IFR clearances without a pen.',
|
||||||
currentFocus: _routeFocusNode,
|
style: TextStyle(fontSize: 16),
|
||||||
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'),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
|
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'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:pocketbase/pocketbase.dart'; // Import the PocketBase package
|
||||||
import 'expectation_input_page.dart';
|
import 'expectation_input_page.dart';
|
||||||
import 'edit_clearance_page.dart'; // Import the edit clearance page
|
|
||||||
import '../widgets/clearance_field.dart';
|
import '../widgets/clearance_field.dart';
|
||||||
|
|
||||||
class FinalClearanceDisplay extends StatefulWidget {
|
class FinalClearanceDisplay extends StatefulWidget {
|
||||||
@@ -12,7 +12,8 @@ class FinalClearanceDisplay extends StatefulWidget {
|
|||||||
final bool isDarkMode;
|
final bool isDarkMode;
|
||||||
final Function toggleDarkMode;
|
final Function toggleDarkMode;
|
||||||
|
|
||||||
const FinalClearanceDisplay({super.key,
|
const FinalClearanceDisplay({
|
||||||
|
super.key,
|
||||||
required this.clearanceLimit,
|
required this.clearanceLimit,
|
||||||
required this.route,
|
required this.route,
|
||||||
required this.altitude,
|
required this.altitude,
|
||||||
@@ -32,6 +33,7 @@ class FinalClearanceDisplayState extends State<FinalClearanceDisplay> {
|
|||||||
late String altitude;
|
late String altitude;
|
||||||
late String squawk;
|
late String squawk;
|
||||||
late String frequency;
|
late String frequency;
|
||||||
|
final PocketBase pb = PocketBase('https://backend.degnedict.de'); // Initialize PocketBase
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@@ -41,36 +43,23 @@ class FinalClearanceDisplayState extends State<FinalClearanceDisplay> {
|
|||||||
altitude = widget.altitude;
|
altitude = widget.altitude;
|
||||||
squawk = widget.squawk;
|
squawk = widget.squawk;
|
||||||
frequency = widget.frequency;
|
frequency = widget.frequency;
|
||||||
|
|
||||||
|
_createPageViewRecord(); // Create a record with the current timestamp
|
||||||
}
|
}
|
||||||
|
|
||||||
// Navigate to Edit Clearance Page and handle returned data
|
// Function to create a new record with a timestamp in epoch milliseconds in your PocketBase collection
|
||||||
Future<void> _navigateToEditClearance() async {
|
Future<void> _createPageViewRecord() async {
|
||||||
final result = await Navigator.push(
|
try {
|
||||||
context,
|
// Get the current time in epoch milliseconds
|
||||||
MaterialPageRoute(
|
int currentTimeInMillis = DateTime.now().millisecondsSinceEpoch;
|
||||||
builder: (context) => EditClearancePage(
|
|
||||||
clearanceLimit: clearanceLimit,
|
|
||||||
route: route,
|
|
||||||
altitude: altitude,
|
|
||||||
squawk: squawk,
|
|
||||||
frequency: frequency,
|
|
||||||
isDarkMode: widget.isDarkMode,
|
|
||||||
toggleDarkMode: widget.toggleDarkMode,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
// If the result contains updated data, update the state with the new values
|
// Create a new record in the collection with the current epoch time
|
||||||
if (result != null && mounted) {
|
await pb.collection('IFRbuddyUsage').create(body: {
|
||||||
setState(() {
|
'timestamp': currentTimeInMillis, // Save current timestamp as epoch time (milliseconds)
|
||||||
clearanceLimit = result['clearanceLimit'];
|
});
|
||||||
route = result['route'];
|
} catch (e) {
|
||||||
altitude = result['altitude'];
|
|
||||||
squawk = result['squawk'];
|
|
||||||
frequency = result['frequency'];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Navigate back to the first page (ExpectationInputPage)
|
// Navigate back to the first page (ExpectationInputPage)
|
||||||
void _navigateHome(BuildContext context) {
|
void _navigateHome(BuildContext context) {
|
||||||
@@ -97,18 +86,7 @@ class FinalClearanceDisplayState extends State<FinalClearanceDisplay> {
|
|||||||
_navigateHome(context);
|
_navigateHome(context);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
actions: [
|
actions: const [],
|
||||||
IconButton(
|
|
||||||
icon: const Icon(Icons.edit), // Edit button
|
|
||||||
onPressed: _navigateToEditClearance,
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
icon: Icon(widget.isDarkMode ? Icons.dark_mode : Icons.light_mode),
|
|
||||||
onPressed: () {
|
|
||||||
widget.toggleDarkMode();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
body: Center(
|
body: Center(
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
@@ -129,8 +107,8 @@ class FinalClearanceDisplayState extends State<FinalClearanceDisplay> {
|
|||||||
buildClearanceField('Clearance Limit', clearanceLimit),
|
buildClearanceField('Clearance Limit', clearanceLimit),
|
||||||
buildClearanceField('Route', route),
|
buildClearanceField('Route', route),
|
||||||
buildClearanceField('Altitude', altitude),
|
buildClearanceField('Altitude', altitude),
|
||||||
buildClearanceField('Transponder (Squawk)', squawk),
|
|
||||||
buildClearanceField('Frequency', frequency),
|
buildClearanceField('Frequency', frequency),
|
||||||
|
buildClearanceField('Transponder (Squawk)', squawk),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -139,4 +117,4 @@ class FinalClearanceDisplayState extends State<FinalClearanceDisplay> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -8,5 +8,7 @@
|
|||||||
<true/>
|
<true/>
|
||||||
<key>com.apple.security.network.server</key>
|
<key>com.apple.security.network.server</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
<key>com.apple.security.network.client</key>
|
||||||
|
<true/>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -4,5 +4,8 @@
|
|||||||
<dict>
|
<dict>
|
||||||
<key>com.apple.security.app-sandbox</key>
|
<key>com.apple.security.app-sandbox</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
<key>com.apple.security.network.client</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.network.server</key>w
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
40
pubspec.lock
40
pubspec.lock
@@ -176,30 +176,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.9.0"
|
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:
|
leak_tracker:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: leak_tracker
|
name: leak_tracker
|
||||||
sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a"
|
sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "10.0.4"
|
version: "10.0.5"
|
||||||
leak_tracker_flutter_testing:
|
leak_tracker_flutter_testing:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: leak_tracker_flutter_testing
|
name: leak_tracker_flutter_testing
|
||||||
sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8"
|
sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.3"
|
version: "3.0.5"
|
||||||
leak_tracker_testing:
|
leak_tracker_testing:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -228,18 +220,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: material_color_utilities
|
name: material_color_utilities
|
||||||
sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
|
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.8.0"
|
version: "0.11.1"
|
||||||
meta:
|
meta:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: meta
|
name: meta
|
||||||
sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136"
|
sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.12.0"
|
version: "1.15.0"
|
||||||
path:
|
path:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -296,6 +288,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.8"
|
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:
|
shared_preferences:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -401,10 +401,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_api
|
name: test_api
|
||||||
sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f"
|
sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.0"
|
version: "0.7.2"
|
||||||
typed_data:
|
typed_data:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -425,10 +425,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: vm_service
|
name: vm_service
|
||||||
sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec"
|
sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "14.2.1"
|
version: "14.2.5"
|
||||||
web:
|
web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -36,9 +36,8 @@ dependencies:
|
|||||||
cupertino_icons: ^1.0.6
|
cupertino_icons: ^1.0.6
|
||||||
http: ^1.2.2
|
http: ^1.2.2
|
||||||
shared_preferences: ^2.3.2
|
shared_preferences: ^2.3.2
|
||||||
keyboard_actions: ^4.2.0
|
|
||||||
flutter_launcher_icons: ^0.14.1
|
flutter_launcher_icons: ^0.14.1
|
||||||
|
pocketbase: ^0.18.1
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
@@ -50,6 +49,12 @@ dev_dependencies:
|
|||||||
# rules and activating additional ones.
|
# rules and activating additional ones.
|
||||||
flutter_lints: ^3.0.0
|
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
|
# For information on the generic Dart part of this file, see the
|
||||||
# following page: https://dart.dev/tools/pub/pubspec
|
# following page: https://dart.dev/tools/pub/pubspec
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 403 B |
@@ -18,7 +18,7 @@
|
|||||||
|
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
|
<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 -->
|
<!-- No cache tag -->
|
||||||
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
|
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
<!-- iOS meta tags & icons -->
|
<!-- iOS meta tags & icons -->
|
||||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
<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-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">
|
<link rel="apple-touch-icon" href="icons/Icon-192.png">
|
||||||
|
|
||||||
<!-- Favicon -->
|
<!-- Favicon -->
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
{
|
{
|
||||||
"name": "ifrbuddy",
|
"name": "IFR Buddy",
|
||||||
"short_name": "ifrbuddy",
|
"short_name": "IFR Buddy",
|
||||||
"start_url": ".",
|
"start_url": ".",
|
||||||
"display": "standalone",
|
"display": "standalone",
|
||||||
"background_color": "#hexcode",
|
"background_color": "#hexcode",
|
||||||
"theme_color": "#hexcode",
|
"theme_color": "#hexcode",
|
||||||
"description": "A new Flutter project.",
|
"description": "Write down IFR Clearances easily",
|
||||||
"orientation": "portrait-primary",
|
"orientation": "portrait-primary",
|
||||||
"prefer_related_applications": false,
|
"prefer_related_applications": false,
|
||||||
"icons": [
|
"icons": [
|
||||||
|
|||||||
Reference in New Issue
Block a user