commit 170825 daham Rechner

This commit is contained in:
2025-08-17 21:28:59 +02:00
parent 503f66756e
commit 3df1be39a8
24 changed files with 1273 additions and 22 deletions

View File

@@ -0,0 +1,266 @@
import 'package:appwrite/appwrite.dart';
import 'package:appwrite/models.dart' as models;
import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';
import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart';
import 'package:intl/intl.dart';
import 'package:tankguru_flutter_app_appwrite/utils/extensions/static_helper.dart';
import '../../data/repository/location_repository.dart';
import '../../data/repository/appwrite_repository.dart';
import '../login/login_view.dart';
class TankController extends GetxController {
final _dataBox = GetStorage('MyUserStorage');
//AppWrite API-REST get Data
final AppwriteRepository _authRepository = AppwriteRepository();
// GEOLOCATING Services
final LocationRepository _locationRepository = LocationRepository();
// Rx-Variablen für die UI, um auf Änderungen zu reagieren
final circleAvatarUserChar = 'A'.obs;
final userNameToDisplay = 'Test User'.obs;
final Rx<Position?> currentPosition = Rx<Position?>(null);
final Rx<bool> isLoading = false.obs;
final Rx<String?> errorMessage = Rx<String?>(null);
final rxOrtString = '?'.obs;
final rxSessionIdString = '?'.obs;
final rxSummePreisString = '0.00'.obs;
// TextEditingController für die Formulareingaben
final formKeyTank = GlobalKey<FormState>();
bool isFormValid = false;
DateTime? _selectedDateTime;
final dateController = TextEditingController();
final f = DateFormat('yyyy-MM-dd');
final kilometerStandEdittingController = TextEditingController();
final mengeController = TextEditingController();
final pricePerLiterController = TextEditingController();
final FocusNode firstFocusNode = FocusNode(); // Deklariere den FocusNode
// Methode für das ausgewählte Datum
Future<void> selectDateTime(BuildContext context) async {
// 1. Datum auswählen
final DateTime? pickedDate = await showDatePicker(
context: context,
initialDate: _selectedDateTime ?? DateTime.now(),
firstDate: DateTime(2000),
lastDate: DateTime(2101),
);
_selectedDateTime = pickedDate;
if (_selectedDateTime != null) {
dateController.text = _selectedDateTime!.toIso8601String().substring(
0,
10,
);
}
}
/// Methode zum Abrufen des Standorts.
Future<void> fetchCurrentLocation() async {
isLoading.value = true;
errorMessage.value = null;
try {
final Position position = await _locationRepository.getCurrentPosition();
currentPosition.value = position;
final double latitude = position.latitude;
final double longitude = position.longitude;
// Hier kannst du die Logik hinzufügen, um den Standort zu verwenden, z.B.
// den Standort in der UI anzuzeigen oder an einen Server zu senden.
var map = {'lat': latitude, 'lng': longitude};
rxOrtString.value = await _locationRepository.getNearbyLocation(map);
// Print Standortinformationen in der Konsole
print('Nearby Location: ${rxOrtString.value}');
print('Current Position: Latitude: $latitude, Longitude: $longitude');
} catch (e) {
// Hier fängst du die Fehler aus dem Repository auf
errorMessage.value = e.toString();
} finally {
isLoading.value = false;
}
update();
}
void clearTextEditingController() {
formKeyTank.currentState!.reset();
// Den Fokus wieder auf das erste Feld legen
FocusScope.of(Get.context!).requestFocus(firstFocusNode);
// TextEditingController zurücksetzen
_selectedDateTime = null; // Datum zurücksetzen
dateController.clear();
kilometerStandEdittingController.clear();
mengeController.clear();
pricePerLiterController.clear();
}
String? validateKilometerStand(String? value) {
if (value == null || value.isEmpty) {
return 'Bitte Kilometerstand eingeben';
}
if (!GetUtils.isNum(value)) {
return 'Bitte geben Sie eine gültige Zahl ein';
}
return null;
}
String? validateMenge(String? value) {
if (value == null || value.isEmpty) {
return 'Bitte Menge eingeben';
}
if (!GetUtils.isNum(value)) {
return 'Bitte geben Sie eine gültige Zahl ein';
}
return null;
}
String? validatePricePerLiter(String? value) {
if (value == null || value.isEmpty) {
return 'Bitte Preis pro Liter eingeben';
}
if (!GetUtils.isNum(value)) {
return 'Bitte geben Sie eine gültige Zahl ein';
}
return null;
}
//Go to Login Page
void logoutSessionAndGoToLoginPage() async {
// Handle logout logic here
print('Logout session and go to login page');
// Clear GetStorage session ID
StaticHelper.removeFromStorage();
print('Session ID removed from GetStorage');
await _authRepository.logout();
Get.offAndToNamed(LoginPage.namedRoute);
StaticHelper.getMySnackeBar(
'Logout', 'Sie wurden abgemeldet!', Colors.blue);
}
@override
void onInit() {
super.onInit();
if (_dataBox.hasData('sessinId')) {
rxSessionIdString(_dataBox.read('sessinId').toString());
}
// Rufe den Standort direkt beim Initialisieren des Controllers ab, falls gewünscht.
fetchCurrentLocation();
}
@override
void onReady() async {
super.onReady();
await getCurrentLoggedinUser();
FocusScope.of(Get.context!).requestFocus(firstFocusNode);
print('TankController is ready');
}
Future<void> getCurrentLoggedinUser() async {
await _authRepository.getCurrentUser.then((models.User user) {
// Hier kannst du den Benutzernamen und das Avatar-Zeichen setzen
userNameToDisplay.value = user.name;
circleAvatarUserChar.value = user.name.substring(0, 1).toUpperCase();
}).catchError((error) {
print('Fehler beim Abrufen des Benutzers: $error');
});
}
@override
void onClose() {
dateController.dispose();
kilometerStandEdittingController.dispose();
mengeController.dispose();
pricePerLiterController.dispose();
firstFocusNode.dispose(); // Dispose den FocusNode
}
void updateSumPrice() {
final double menge = double.tryParse(mengeController.text) ?? 0.0;
final double pricePerLiter =
double.tryParse(pricePerLiterController.text) ?? 0.0;
final double sumPrice = menge * pricePerLiter;
rxSummePreisString.value = sumPrice.toStringAsFixed(
2,
); // Formatieren auf 2 Dezimalstellen
}
void saveTankstopp() async {
bool isErrorBySaving = false;
String messageToUser = '';
isFormValid = formKeyTank.currentState!.validate();
if (!isFormValid) {
print('Formular ist ungültig');
messageToUser = 'Bitte überprüfen Sie Ihre Eingaben.';
Get.snackbar(
'Fehler',
messageToUser,
snackPosition: SnackPosition.BOTTOM,
duration: const Duration(seconds: 2),
);
return;
} else {
print('Formular ist gültig');
formKeyTank.currentState!.save();
try {
//creatiung a tank stop
await _authRepository.createTankStop({
'userId': _dataBox.read('userId'),
'date': f.format(DateTime.parse(dateController.text)),
'odometer': double.parse(
double.parse(
kilometerStandEdittingController.text,
).toStringAsFixed(0),
),
'liters': double.parse(
double.parse(mengeController.text).toStringAsFixed(2),
),
'pricePerLiter': double.parse(
double.parse(pricePerLiterController.text).toStringAsFixed(2),
),
'location': rxOrtString.value,
}).then((models.Document document) {
print('Tankstopp erfolgreich gespeichert: ${document.data}');
messageToUser = 'Tankstopp erfolgreich gespeichert!';
isErrorBySaving = false;
})
// Handle specific Appwrite exceptions
.catchError((error) {
isErrorBySaving = true;
if (error is AppwriteException) {
// Handle specific Appwrite exceptions
messageToUser = 'Appwrite Fehler: ${error.message}';
print('Appwrite Fehler: ${error.message}');
} else {
// Handle other types of errors
messageToUser = 'Allgemeiner Fehler: $error';
print('Allgemeiner Fehler: $error');
}
print('Fehler bei der speicherung: $error');
});
}
// Handle any other exceptions that might occur
catch (e) {
isErrorBySaving = true;
messageToUser = 'Fehler beim Speichern des Tankstopps: $e';
print('Fehler beim speichern: $e');
}
if (!isErrorBySaving) {
clearTextEditingController();
}
// Handle button press logic here
String title = isErrorBySaving ? 'Fehler' : 'Erfolg';
Get.snackbar(
title,
messageToUser,
backgroundColor: isErrorBySaving ? Colors.red : Colors.green,
snackPosition: SnackPosition.BOTTOM,
duration: const Duration(seconds: 4),
);
}
}
goToTankStopsView() async {
clearTextEditingController();
//await Get.offAndToNamed(TanklistPage.namedRoute);
}
}

View File

@@ -0,0 +1,277 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import './tank_controller.dart';
class TankPage extends GetView<TankController> {
static const namedRoute = '/tank-stop-page';
const TankPage({super.key});
@override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
appBar: AppBar(
title: Text(
'Tankstopp erfassen',
style: TextStyle(color: Colors.grey.shade300),
),
backgroundColor: Colors.transparent, // Mach die AppBar transparent
elevation: 0, // Entferne den Schatten unter der AppBar
centerTitle: true,
actions: [
IconButton(
icon: Icon(Icons.list, color: Colors.grey.shade300),
onPressed: () async {
// Handle logout logic here
controller.goToTankStopsView();
},
),
IconButton(
icon: Icon(Icons.logout, color: Colors.grey.shade300),
onPressed: () async {
// Handle logout logic here
controller.logoutSessionAndGoToLoginPage();
},
),
],
),
extendBodyBehindAppBar: true, // Erweitere den Body hinter die AppBar
body: Stack(
children: [
Container(
height: double.infinity,
width: double.infinity,
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage('lib/images/backgroundPitstopBlack.png'),
fit: BoxFit
.cover, // Skaliert das Bild, um den Container zu füllen
alignment: Alignment
.bottomCenter, // Richte das Bild am unteren Rand aus
),
),
),
Obx(
() => SingleChildScrollView(
padding: EdgeInsets.only(
top: MediaQuery.of(context).padding.top + kToolbarHeight + 20,
left: 20,
right: 20,
bottom: 20,
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
CircleAvatar(
backgroundColor: Colors.blue,
radius: 40,
child: Text(
controller.circleAvatarUserChar.value,
style: const TextStyle(
color: Colors.black,
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
),
const SizedBox(height: 5),
Text(
controller.userNameToDisplay.value,
style: const TextStyle(
fontSize: 20,
backgroundColor: Colors.black87,
color: Colors.white,
fontWeight: FontWeight.bold,
letterSpacing: 3,
),
),
controller.isLoading.value
? Text('Location is loading...')
: Text(
controller.rxOrtString.value,
style: const TextStyle(
fontSize: 15,
backgroundColor: Colors.black87,
color: Colors.blue,
),
),
const SizedBox(height: 20),
inputFields(),
const SizedBox(height: 20),
Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
color: Colors.black.withValues(alpha: 0.9),
),
child: Text.rich(
TextSpan(
text: 'Summe: ',
style: TextStyle(
fontSize: 18,
color: Colors.grey.shade300,
),
children: <TextSpan>[
TextSpan(
// Hier kommt der Wert als separater TextSpan
text: controller.rxSummePreisString.value,
// Doppelt so groß wie der Standardtext',
style: TextStyle(
fontSize: 30, // Doppelt so groß wie 18
color: Colors.blue, // Blaue Farbe
fontWeight: FontWeight
.bold, // Optional: Fetter Text, um ihn hervorzuheben
),
),
],
),
),
),
const SizedBox(height: 20),
SizedBox(
width: 350,
height: 50,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue.withValues(
alpha: 0.9,
), // Button-Farbe
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(
10,
), // Abgerundete Ecken
),
),
onPressed: () {
controller.saveTankstopp();
},
child: Text(
'Tankstop erfassen',
style: TextStyle(color: Colors.white, fontSize: 20),
),
),
),
Container(
margin: EdgeInsets.only(top: 20, bottom: 50),
child: Text(
'Hinweis: Alle Felder sind Pflichtfelder.',
style: TextStyle(
fontSize: 16,
color: Colors.red.shade300,
),
),
),
],
),
),
),
],
),
),
);
}
Widget inputFields() {
return SizedBox(
width: 320,
child: Form(
key: controller.formKeyTank,
autovalidateMode: AutovalidateMode.onUserInteraction,
child: Column(
children: [
TextFormField(
focusNode: controller.firstFocusNode, // Setze den FocusNode
readOnly: true,
onTap: () => controller.selectDateTime(Get.context!),
controller: controller.dateController,
decoration: InputDecoration(
labelText: 'Datum',
labelStyle: const TextStyle(color: Colors.white, fontSize: 20),
errorStyle: const TextStyle(color: Colors.red, fontSize: 16),
filled: true,
fillColor: Colors.black.withValues(alpha: 0.9),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: const BorderSide(color: Colors.white, width: 2),
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: BorderSide.none, // Entferne den Rand
),
),
style: const TextStyle(color: Colors.white),
),
SizedBox(height: 20),
TextFormField(
validator: (value) => controller.validateKilometerStand(value),
keyboardType: TextInputType.number,
controller: controller.kilometerStandEdittingController,
decoration: InputDecoration(
labelText: 'Kilometerstand',
labelStyle: const TextStyle(color: Colors.white, fontSize: 20),
errorStyle: const TextStyle(color: Colors.red, fontSize: 16),
filled: true,
fillColor: Colors.black.withValues(alpha: 0.9),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: const BorderSide(color: Colors.white, width: 2),
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: BorderSide.none, // Entferne den Rand
),
),
),
SizedBox(height: 20),
TextFormField(
validator: (value) => controller.validateMenge(value),
keyboardType: TextInputType.number,
controller: controller.mengeController,
decoration: InputDecoration(
labelText: 'Menge (in Litern)',
labelStyle: const TextStyle(color: Colors.white, fontSize: 20),
errorStyle: const TextStyle(color: Colors.red, fontSize: 16),
filled: true,
fillColor: Colors.black.withValues(alpha: 0.9),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: const BorderSide(color: Colors.white, width: 2),
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: BorderSide.none, // Entferne den Rand
),
),
),
SizedBox(height: 20),
TextFormField(
onChanged: (value) {
// Update the sum price when the price per liter changes
controller.updateSumPrice();
},
validator: (value) => controller.validatePricePerLiter(value),
keyboardType: TextInputType.number,
controller: controller.pricePerLiterController,
decoration: InputDecoration(
labelText: 'Preis pro Liter',
labelStyle: const TextStyle(color: Colors.white, fontSize: 20),
errorStyle: const TextStyle(color: Colors.red, fontSize: 16),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: const BorderSide(color: Colors.white, width: 2),
),
filled: true,
fillColor: Colors.black.withValues(alpha: 0.9),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
//borderSide: BorderSide.none, // Entferne den Rand
),
),
),
],
),
),
);
}
}