Compare commits

..

11 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
9 changed files with 223 additions and 51 deletions

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

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

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

@@ -184,7 +184,7 @@ class ComparisonPageState extends State<ComparisonPage> {
child: Padding( child: Padding(
padding: EdgeInsets.all(16.0), padding: EdgeInsets.all(16.0),
child: Text( child: Text(
'Use this Page while receiving your clearance. The arrow button copies the expected value. Same if left empty.', '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), style: TextStyle(fontSize: 16),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
@@ -214,7 +214,7 @@ class ComparisonPageState extends State<ComparisonPage> {
buildExpectedClearanceField('Route', widget.expectedRoute), buildExpectedClearanceField('Route', widget.expectedRoute),
buildExpectedClearanceField('Altitude', widget.expectedAltitude), buildExpectedClearanceField('Altitude', widget.expectedAltitude),
buildExpectedClearanceField('Frequency', widget.expectedFrequency), buildExpectedClearanceField('Frequency', widget.expectedFrequency),
buildExpectedClearanceField('Transponder (Squawk)', widget.expectedSquawk), // buildExpectedClearanceField('Transponder (Squawk)', widget.expectedSquawk),
], ],
), ),
), ),
@@ -279,7 +279,7 @@ class ComparisonPageState extends State<ComparisonPage> {
// Proceed Button // Proceed Button
ElevatedButton( ElevatedButton(
onPressed: _navigateToFinalClearance, onPressed: _navigateToFinalClearance,
child: const Text('Proceed to Clearance Display'), child: const Text('Proceed to Readback page'),
), ),
], ],
), ),

View File

@@ -1,8 +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 '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'; // Ensure this path is correct 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;
@@ -30,13 +35,46 @@ class ExpectationInputPageState extends State<ExpectationInputPage> {
TextEditingController(); TextEditingController();
final TextEditingController _expectedFrequencyController = final TextEditingController _expectedFrequencyController =
TextEditingController(); TextEditingController();
final TextEditingController _simbriefIdController = TextEditingController();
// FocusNodes for each field // 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() {
@@ -48,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(
@@ -181,27 +257,86 @@ class ExpectationInputPageState extends State<ExpectationInputPage> {
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16.0),
child: Column( child: Column(
children: [ children: [
const Card( // IntrinsicHeight Widget hinzufügen
elevation: 2, IntrinsicHeight(
child: Padding( child: Row(
padding: EdgeInsets.all(16.0), crossAxisAlignment: CrossAxisAlignment.stretch, // Stretch hinzufügen
child: Column( children: [
crossAxisAlignment: CrossAxisAlignment.start, // Welcome Card - nimmt 60% der Breite
children: [ Expanded(
Text( flex: 3,
'Welcome to IFR Buddy!', child: Card(
style: TextStyle( elevation: 2,
fontSize: 18, fontWeight: FontWeight.bold), child: Padding(
textAlign: TextAlign.left, padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: const [
Text(
'Welcome to IFR Buddy!',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
SizedBox(height: 10),
Text(
'Just an easy tool for writing down IFR clearances without a pen.',
style: TextStyle(fontSize: 16),
),
],
),
),
), ),
SizedBox(height: 10), ),
Text( const SizedBox(width: 16), // Abstand zwischen den Cards
'Just an easy tool for writing down IFR clearances without a pen.', // SimBrief Card - nimmt 40% der Breite
style: TextStyle(fontSize: 16), Expanded(
textAlign: TextAlign.left, 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), const SizedBox(height: 16),
@@ -219,12 +354,15 @@ class ExpectationInputPageState extends State<ExpectationInputPage> {
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
const Text( const Text(
'Enter your expected clearance information below. ' 'Enter your expected clearance information below (Or use the SimBrief import). '
'Use the listening page or skip to the readback page.', 'Use the listening page or skip to the readback page.',
style: TextStyle(fontSize: 16), style: TextStyle(fontSize: 16),
textAlign: TextAlign.left, textAlign: TextAlign.left,
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
Padding(
padding: const EdgeInsets.all(8.0),
),
Form( Form(
key: _formKey, key: _formKey,
child: Column( child: Column(
@@ -265,7 +403,7 @@ class ExpectationInputPageState extends State<ExpectationInputPage> {
label: 'Altitude', label: 'Altitude',
controller: _expectedAltitudeController, controller: _expectedAltitudeController,
currentFocus: _altitudeFocusNode, currentFocus: _altitudeFocusNode,
nextFocus: _squawkFocusNode, nextFocus: _frequencyFocusNode, // Updated next focus
keyboardType: TextInputType.text, // Standard keyboard keyboardType: TextInputType.text, // Standard keyboard
inputFormatters: null, // No restrictions inputFormatters: null, // No restrictions
enableAutocorrect: false, // Disable autocorrect enableAutocorrect: false, // Disable autocorrect
@@ -274,27 +412,10 @@ class ExpectationInputPageState extends State<ExpectationInputPage> {
false, // iOS-specific false, // iOS-specific
), ),
buildTextField( buildTextField(
label: 'Transponder (Squawk)', label: 'Departure Frequency', // Changed label
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, controller: _expectedFrequencyController,
currentFocus: _frequencyFocusNode, currentFocus: _frequencyFocusNode,
isLastField: true, nextFocus: _squawkFocusNode, // Next focus to Squawk
keyboardType: TextInputType.text, // Standard keyboard keyboardType: TextInputType.text, // Standard keyboard
inputFormatters: [ inputFormatters: [
FrequencyInputFormatter(), // Handles decimal inputs FrequencyInputFormatter(), // Handles decimal inputs
@@ -304,6 +425,23 @@ class ExpectationInputPageState extends State<ExpectationInputPage> {
enableIMEPersonalizedLearning: enableIMEPersonalizedLearning:
false, // iOS-specific 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), const SizedBox(height: 20),
ElevatedButton( ElevatedButton(
onPressed: _navigateToComparisonPage, onPressed: _navigateToComparisonPage,
@@ -327,4 +465,4 @@ class ExpectationInputPageState extends State<ExpectationInputPage> {
), ),
); );
} }
} }

View File

@@ -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 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,9 +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> _createPageViewRecord() async {
try {
// Get the current time in epoch milliseconds
int currentTimeInMillis = DateTime.now().millisecondsSinceEpoch;
// 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) // Navigate back to the first page (ExpectationInputPage)
void _navigateHome(BuildContext context) { void _navigateHome(BuildContext context) {
@@ -91,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),
], ],
), ),
), ),
@@ -101,4 +117,4 @@ class FinalClearanceDisplayState extends State<FinalClearanceDisplay> {
), ),
); );
} }
} }

View File

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

View File

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

View File

@@ -288,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:

View File

@@ -37,6 +37,7 @@ dependencies:
http: ^1.2.2 http: ^1.2.2
shared_preferences: ^2.3.2 shared_preferences: ^2.3.2
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