This commit is contained in:
2024-10-19 14:17:37 +02:00
parent 9f98189983
commit ec76a028cb
53 changed files with 495 additions and 607 deletions

View File

@@ -1,14 +1,16 @@
# IFR Buddy # ifrbuddy
**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. A new Flutter project.
## Features ## Getting Started
- **Seamless Navigation:** Move between input fields using keyboard buttons. This project is a starting point for a Flutter application.
- **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 A few resources to get you started if this is your first Flutter project:
This project is licensed under the MIT License. - [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.

View File

@@ -2,7 +2,7 @@
<application <application
android:label="ifrbuddy" android:label="ifrbuddy"
android:name="${applicationName}" android:name="${applicationName}"
android:icon="@mipmap/launcher_icon"> android:icon="@mipmap/ic_launcher">
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:exported="true" android:exported="true"

View File

@@ -1,3 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

View File

@@ -539,7 +539,7 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO; ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++"; CLANG_CXX_LIBRARY = "libc++";
@@ -596,7 +596,7 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO; ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++"; CLANG_CXX_LIBRARY = "libc++";

View File

@@ -1 +1,122 @@
{"images":[{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@3x.png","scale":"3x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@3x.png","scale":"3x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@3x.png","scale":"3x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@1x.png","scale":"1x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@3x.png","scale":"3x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@1x.png","scale":"1x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@1x.png","scale":"1x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@1x.png","scale":"1x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@2x.png","scale":"2x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@1x.png","scale":"1x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@2x.png","scale":"2x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@1x.png","scale":"1x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@2x.png","scale":"2x"},{"size":"83.5x83.5","idiom":"ipad","filename":"Icon-App-83.5x83.5@2x.png","scale":"2x"},{"size":"1024x1024","idiom":"ios-marketing","filename":"Icon-App-1024x1024@1x.png","scale":"1x"}],"info":{"version":1,"author":"xcode"}} {
"images" : [
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@3x.png",
"scale" : "3x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@3x.png",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@3x.png",
"scale" : "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@3x.png",
"scale" : "3x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@1x.png",
"scale" : "1x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@1x.png",
"scale" : "1x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@1x.png",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@2x.png",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "Icon-App-83.5x83.5@2x.png",
"scale" : "2x"
},
{
"size" : "1024x1024",
"idiom" : "ios-marketing",
"filename" : "Icon-App-1024x1024@1x.png",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 509 B

After

Width:  |  Height:  |  Size: 295 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 450 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 798 B

After

Width:  |  Height:  |  Size: 282 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 462 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 704 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 586 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 862 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 862 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 762 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -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. The arrow button copies the expected 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('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 Clearance Display'),
),
],
),
),
);
}
} }

View File

@@ -1,9 +1,8 @@
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
class ExpectationInputPage extends StatefulWidget { class ExpectationInputPage extends StatefulWidget {
final bool isDarkMode; final bool isDarkMode;
@@ -32,7 +31,7 @@ class ExpectationInputPageState extends State<ExpectationInputPage> {
final TextEditingController _expectedFrequencyController = final TextEditingController _expectedFrequencyController =
TextEditingController(); 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();
@@ -93,7 +92,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 +119,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 +138,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 +159,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,142 +177,151 @@ 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( const Card(
children: [ elevation: 2,
const Card( child: Padding(
elevation: 2, padding: EdgeInsets.all(16.0),
child: Padding( child: Column(
padding: EdgeInsets.all(16.0), crossAxisAlignment: CrossAxisAlignment.start,
child: Column( children: [
crossAxisAlignment: CrossAxisAlignment.start, Text(
children: [ 'Welcome to IFR Buddy!',
Text( style: TextStyle(
'Welcome to IFR Buddy!', fontSize: 18, fontWeight: FontWeight.bold),
style: TextStyle( textAlign: TextAlign.left,
fontSize: 18, fontWeight: FontWeight.bold), ),
textAlign: TextAlign.left, SizedBox(height: 10),
), Text(
SizedBox(height: 10), 'Just an easy tool for writing down IFR clearances without a pen.',
Text( style: TextStyle(fontSize: 16),
'Just an easy tool for writing down IFR clearances without a pen.', textAlign: TextAlign.left,
style: TextStyle(fontSize: 16), ),
textAlign: TextAlign.left, ],
),
],
),
), ),
), ),
const SizedBox(height: 16), ),
Card( const SizedBox(height: 16),
elevation: 2, Card(
child: Padding( elevation: 2,
padding: const EdgeInsets.all(16.0), child: Padding(
child: Column( padding: const EdgeInsets.all(16.0),
crossAxisAlignment: CrossAxisAlignment.start, child: Column(
children: [ crossAxisAlignment: CrossAxisAlignment.start,
const Text( children: [
'Expected Clearance', const Text(
style: TextStyle( 'Expected Clearance',
fontSize: 22, fontWeight: FontWeight.bold), 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,
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: _squawkFocusNode,
keyboardType: TextInputType.text, // Standard keyboard
inputFormatters: null, // No restrictions
enableAutocorrect: false, // Disable autocorrect
enableSuggestions: false, // Disable suggestions
enableIMEPersonalizedLearning:
false, // iOS-specific
),
buildTextField(
label: 'Transponder (Squawk)',
controller: _expectedSquawkController,
currentFocus: _squawkFocusNode,
nextFocus: _frequencyFocusNode,
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
),
buildTextField(
label: 'Departure Frequency',
controller: _expectedFrequencyController,
currentFocus: _frequencyFocusNode,
isLastField: true,
keyboardType: TextInputType.text, // Standard keyboard
inputFormatters: [
FrequencyInputFormatter(), // Handles decimal inputs
],
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'),
),
],
), ),
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'),
),
],
),
),
],
),
), ),
), ),
], ),
), ],
), ),
), ),
), ),

View File

@@ -1,6 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'expectation_input_page.dart'; import 'expectation_input_page.dart';
import 'edit_clearance_page.dart'; // Import the edit clearance page // Import the edit clearance page
import '../widgets/clearance_field.dart'; import '../widgets/clearance_field.dart';
class FinalClearanceDisplay extends StatefulWidget { class FinalClearanceDisplay extends StatefulWidget {
@@ -44,33 +44,6 @@ class FinalClearanceDisplayState extends State<FinalClearanceDisplay> {
} }
// Navigate to Edit Clearance Page and handle returned data // 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,
),
),
);
// 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'];
});
}
}
// Navigate back to the first page (ExpectationInputPage) // Navigate back to the first page (ExpectationInputPage)
void _navigateHome(BuildContext context) { void _navigateHome(BuildContext context) {
@@ -97,18 +70,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(

View File

@@ -1,68 +1,68 @@
{ {
"info": { "images" : [
"version": 1, {
"author": "xcode" "size" : "16x16",
"idiom" : "mac",
"filename" : "app_icon_16.png",
"scale" : "1x"
}, },
"images": [ {
{ "size" : "16x16",
"size": "16x16", "idiom" : "mac",
"idiom": "mac", "filename" : "app_icon_32.png",
"filename": "app_icon_16.png", "scale" : "2x"
"scale": "1x" },
}, {
{ "size" : "32x32",
"size": "16x16", "idiom" : "mac",
"idiom": "mac", "filename" : "app_icon_32.png",
"filename": "app_icon_32.png", "scale" : "1x"
"scale": "2x" },
}, {
{ "size" : "32x32",
"size": "32x32", "idiom" : "mac",
"idiom": "mac", "filename" : "app_icon_64.png",
"filename": "app_icon_32.png", "scale" : "2x"
"scale": "1x" },
}, {
{ "size" : "128x128",
"size": "32x32", "idiom" : "mac",
"idiom": "mac", "filename" : "app_icon_128.png",
"filename": "app_icon_64.png", "scale" : "1x"
"scale": "2x" },
}, {
{ "size" : "128x128",
"size": "128x128", "idiom" : "mac",
"idiom": "mac", "filename" : "app_icon_256.png",
"filename": "app_icon_128.png", "scale" : "2x"
"scale": "1x" },
}, {
{ "size" : "256x256",
"size": "128x128", "idiom" : "mac",
"idiom": "mac", "filename" : "app_icon_256.png",
"filename": "app_icon_256.png", "scale" : "1x"
"scale": "2x" },
}, {
{ "size" : "256x256",
"size": "256x256", "idiom" : "mac",
"idiom": "mac", "filename" : "app_icon_512.png",
"filename": "app_icon_256.png", "scale" : "2x"
"scale": "1x" },
}, {
{ "size" : "512x512",
"size": "256x256", "idiom" : "mac",
"idiom": "mac", "filename" : "app_icon_512.png",
"filename": "app_icon_512.png", "scale" : "1x"
"scale": "2x" },
}, {
{ "size" : "512x512",
"size": "512x512", "idiom" : "mac",
"idiom": "mac", "filename" : "app_icon_1024.png",
"filename": "app_icon_512.png", "scale" : "2x"
"scale": "1x" }
}, ],
{ "info" : {
"size": "512x512", "version" : 1,
"idiom": "mac", "author" : "xcode"
"filename": "app_icon_1024.png", }
"scale": "2x"
}
]
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 403 B

After

Width:  |  Height:  |  Size: 520 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 905 B

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@@ -1,22 +1,6 @@
# Generated by pub # Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile # See https://dart.dev/tools/pub/glossary#lockfile
packages: packages:
archive:
dependency: transitive
description:
name: archive
sha256: cb6a278ef2dbb298455e1a713bda08524a175630ec643a242c399c932a0a1f7d
url: "https://pub.dev"
source: hosted
version: "3.6.1"
args:
dependency: transitive
description:
name: args
sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6
url: "https://pub.dev"
source: hosted
version: "2.6.0"
async: async:
dependency: transitive dependency: transitive
description: description:
@@ -41,22 +25,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.0" version: "1.3.0"
checked_yaml:
dependency: transitive
description:
name: checked_yaml
sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff
url: "https://pub.dev"
source: hosted
version: "2.0.3"
cli_util:
dependency: transitive
description:
name: cli_util
sha256: c05b7406fdabc7a49a3929d4af76bcaccbbffcbcdcf185b082e1ae07da323d19
url: "https://pub.dev"
source: hosted
version: "0.4.1"
clock: clock:
dependency: transitive dependency: transitive
description: description:
@@ -73,14 +41,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.18.0" version: "1.18.0"
crypto:
dependency: transitive
description:
name: crypto
sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855"
url: "https://pub.dev"
source: hosted
version: "3.0.6"
cupertino_icons: cupertino_icons:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -118,14 +78,6 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
flutter_launcher_icons:
dependency: "direct main"
description:
name: flutter_launcher_icons
sha256: "619817c4b65b322b5104b6bb6dfe6cda62d9729bd7ad4303ecc8b4e690a67a77"
url: "https://pub.dev"
source: hosted
version: "0.14.1"
flutter_lints: flutter_lints:
dependency: "direct dev" dependency: "direct dev"
description: description:
@@ -160,46 +112,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.0.2" version: "4.0.2"
image:
dependency: transitive
description:
name: image
sha256: f31d52537dc417fdcde36088fdf11d191026fd5e4fae742491ebd40e5a8bea7d
url: "https://pub.dev"
source: hosted
version: "4.3.0"
json_annotation:
dependency: transitive
description:
name: json_annotation
sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1"
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: 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 +156,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:
@@ -272,14 +200,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.0" version: "2.3.0"
petitparser:
dependency: transitive
description:
name: petitparser
sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27
url: "https://pub.dev"
source: hosted
version: "6.0.2"
platform: platform:
dependency: transitive dependency: transitive
description: description:
@@ -401,10 +321,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 +345,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:
@@ -445,22 +365,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.0" version: "1.1.0"
xml:
dependency: transitive
description:
name: xml
sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226
url: "https://pub.dev"
source: hosted
version: "6.5.0"
yaml:
dependency: transitive
description:
name: yaml
sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5"
url: "https://pub.dev"
source: hosted
version: "3.1.2"
sdks: sdks:
dart: ">=3.4.3 <4.0.0" dart: ">=3.4.3 <4.0.0"
flutter: ">=3.22.0" flutter: ">=3.22.0"

View File

@@ -36,9 +36,6 @@ 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
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter

Binary file not shown.

Before

Width:  |  Height:  |  Size: 403 B

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 403 B

View File

@@ -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 -->

View File

@@ -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": "#0175C2",
"theme_color": "#hexcode", "theme_color": "#0175C2",
"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": [

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 33 KiB