From 46f741678189a85b8554aaa1e48481153b62677d Mon Sep 17 00:00:00 2001 From: josiadmin Date: Fri, 20 Feb 2026 09:51:03 +0100 Subject: [PATCH] pre final ad services and correct call the services async --- lib/controller/edit_controller.dart | 52 ++--- lib/controller/gasstations_controller.dart | 65 ++---- lib/pages/gasstations_view.dart | 179 +++++++++++++--- lib/services/econtrol_service.dart | 75 +------ lib/services/geolocation_service.dart | 54 +++++ lib/services/location_iq_service.dart | 26 --- lib/widgets/gasstation_card_widget.dart | 235 +++++++++++++++++++++ 7 files changed, 479 insertions(+), 207 deletions(-) create mode 100644 lib/services/geolocation_service.dart create mode 100644 lib/widgets/gasstation_card_widget.dart diff --git a/lib/controller/edit_controller.dart b/lib/controller/edit_controller.dart index 9d73bf9..187489c 100644 --- a/lib/controller/edit_controller.dart +++ b/lib/controller/edit_controller.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter_tank_web_app/services/geolocation_service.dart'; import 'package:get/get.dart'; import 'package:geolocator/geolocator.dart'; import '../models/locationiq_model.dart'; @@ -62,43 +63,26 @@ class EditController extends GetxController { } Future _requestLocationIQ() async { - bool serviceEnabled; - LocationPermission permission; - + var geolocationService = GeolocationService(); + var locationIQService = LocationIQService(); + isLoadingLocation.value = true; try { - isLoadingLocation.value = true; + await geolocationService.getCurrentLocation(); + if (geolocationService.hasLocation) { + print( + 'Aktuelle Position: ${geolocationService.latitude}, ${geolocationService.longitude}', + ); + await locationIQService.fetchLocationIQ( + geolocationService.latitude, + geolocationService.longitude, + ); - // 1. Prüfen, ob Standortdienste aktiviert sind - serviceEnabled = await Geolocator.isLocationServiceEnabled(); - if (!serviceEnabled) { - return Future.error('Standortdienste sind deaktiviert.'); + currentLocationIQ = locationIQService.locationIQ; + locationController.text = + currentLocationIQ?.address?.shortAddress ?? ''; + } else { + throw Exception('Standort nicht verfügbar'); } - - // 2. Berechtigungen prüfen - permission = await Geolocator.checkPermission(); - if (permission == LocationPermission.denied) { - permission = await Geolocator.requestPermission(); - if (permission == LocationPermission.denied) { - return Future.error('Berechtigung verweigert.'); - } - } - - // 3. Position abrufen - Position position = await Geolocator.getCurrentPosition( - locationSettings: const LocationSettings( - accuracy: LocationAccuracy.high, - ), - ); - - // 4. Standort über Backend-Proxy abrufen - var lat = position.latitude; - var lon = position.longitude; - - print('📍 Verwende LocationIQ für Geocoding...'); - final LocationIQService locationIQService = LocationIQService(); - await locationIQService.fetchLocationIQ(lat, lon); - currentLocationIQ = locationIQService.locationIQ; - locationController.text = currentLocationIQ?.address?.shortAddress ?? ''; } catch (e) { Get.snackbar( "Fehler", diff --git a/lib/controller/gasstations_controller.dart b/lib/controller/gasstations_controller.dart index fb85828..34e4889 100644 --- a/lib/controller/gasstations_controller.dart +++ b/lib/controller/gasstations_controller.dart @@ -1,7 +1,6 @@ -import 'package:flutter/material.dart'; -import 'package:geolocator/geolocator.dart'; import 'package:get/get.dart'; import '../models/econtrol_model.dart'; +import '../services/geolocation_service.dart'; import '../services/econtrol_service.dart'; class GasstationsController extends GetxController { @@ -23,59 +22,19 @@ class GasstationsController extends GetxController { Future _loadStationInfosList() async { isLoading.value = true; - bool serviceEnabled; - if (eControlData.isNotEmpty) { - eControlData.clear(); - } - final eControlService = EControlService(); - LocationPermission permission; - - try { - isLoadingLocation.value = true; - - // 1. Prüfen, ob Standortdienste aktiviert sind - serviceEnabled = await Geolocator.isLocationServiceEnabled(); - if (!serviceEnabled) { - return Future.error('Standortdienste sind deaktiviert.'); - } - - // 2. Berechtigungen prüfen - permission = await Geolocator.checkPermission(); - if (permission == LocationPermission.denied) { - permission = await Geolocator.requestPermission(); - if (permission == LocationPermission.denied) { - return Future.error('Berechtigung verweigert.'); - } - } - - // 3. Position abrufen - Position position = await Geolocator.getCurrentPosition( - locationSettings: const LocationSettings( - accuracy: LocationAccuracy.high, - ), - ); - - // 4. Standort über Backend-Proxy abrufen - var lat = position.latitude; - var lon = position.longitude; - // Simulate fetching data from an API or database - await eControlService.getEControlData( - lat, - lon, + var gelocationService = GeolocationService(); + var eControlService = EControlService(); + // check if location is available + await gelocationService.getCurrentLocation(); + if (gelocationService.hasLocation) { + var listResult = await eControlService.getEControlData( + gelocationService.latitude, + gelocationService.longitude, 'DIE', ); - eControlData.addAll(eControlService.eControlData); - } catch (e) { - Get.snackbar( - "Fehler", - "Standort konnte nicht abgerufen werden: $e", - snackPosition: SnackPosition.BOTTOM, - backgroundColor: Colors.red[100], - colorText: Colors.red[900], - ); - print("Fehler beim Abrufen des Standorts: $e"); - } finally { - isLoadingLocation.value = false; + eControlData.value = listResult + .map((json) => EControlModel.fromJson(json)) + .toList(); } isLoading.value = false; update(); diff --git a/lib/pages/gasstations_view.dart b/lib/pages/gasstations_view.dart index c634743..afe10db 100644 --- a/lib/pages/gasstations_view.dart +++ b/lib/pages/gasstations_view.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import '../controller/gasstations_controller.dart'; +import '../widgets/gasstation_card_widget.dart'; class GasstationsPage extends GetView { static const String namedRoute = '/gasstations-page'; @@ -9,43 +10,161 @@ class GasstationsPage extends GetView { @override Widget build(BuildContext context) { var staCtrl = controller; - return Scaffold( - appBar: AppBar( - toolbarHeight: 100, - backgroundColor: Colors.blueGrey, - foregroundColor: Colors.white, - title: const Text('Gas Stations'), - ), - body: Container( - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [ - Colors.blueGrey[800]!, - Colors.blueGrey[600]!, - Colors.blueGrey[300]!, - Colors.blue[100]!, + return SafeArea( + child: Scaffold( + appBar: AppBar( + backgroundColor: Colors.blueGrey[800], + foregroundColor: Colors.white, + elevation: 0, + title: const Row( + children: [ + Icon(Icons.local_gas_station, size: 28), + SizedBox(width: 12), + Text( + 'Tankstellen', + style: TextStyle(fontWeight: FontWeight.bold, fontSize: 22), + ), ], ), ), - child: Center( + body: Container( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Colors.blueGrey[800]!, + Colors.blueGrey[600]!, + Colors.blueGrey[400]!, + Colors.blueGrey[200]!, + ], + ), + ), child: Obx(() { if (staCtrl.isLoading.value) { - return const CircularProgressIndicator(); + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const CircularProgressIndicator( + valueColor: AlwaysStoppedAnimation(Colors.white), + strokeWidth: 3, + ), + const SizedBox(height: 20), + Text( + 'Lade Tankstellen...', + style: TextStyle( + color: Colors.white, + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ); } else if (staCtrl.eControlData.isEmpty) { - return const Text('No gas stations found.'); + return Center( + child: Container( + margin: const EdgeInsets.all(24), + padding: const EdgeInsets.all(32), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(20), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.1), + blurRadius: 10, + offset: const Offset(0, 5), + ), + ], + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.search_off, + size: 64, + color: Colors.blueGrey[300], + ), + const SizedBox(height: 16), + Text( + 'Keine Tankstellen gefunden', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Colors.blueGrey[800], + ), + ), + const SizedBox(height: 8), + Text( + 'Bitte überprüfen Sie Ihre Standortfreigabe', + style: TextStyle(fontSize: 14, color: Colors.grey[600]), + textAlign: TextAlign.center, + ), + ], + ), + ), + ); } else { - return ListView.builder( - itemCount: staCtrl.eControlData.length, - itemBuilder: (context, index) { - final station = staCtrl.eControlData[index]; - return ListTile( - title: Text(station.name ?? 'Unknown Station'), - subtitle: Text(station.location?.address ?? 'No address'), - trailing: Text(station.open == true ? 'Open' : 'Closed'), - ); - }, + return Column( + children: [ + // Info Header + Container( + padding: const EdgeInsets.all(16), + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 20, + vertical: 12, + ), + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.95), + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.1), + blurRadius: 8, + offset: const Offset(0, 2), + ), + ], + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.info_outline, + color: Colors.blueGrey[700], + size: 20, + ), + const SizedBox(width: 8), + Text( + 'Top 5 von ${staCtrl.eControlData.length} Tankstellen', + style: TextStyle( + fontSize: 15, + fontWeight: FontWeight.w600, + color: Colors.blueGrey[800], + ), + ), + ], + ), + ), + ), + + // List + Expanded( + child: ListView.builder( + padding: const EdgeInsets.only(bottom: 16), + itemCount: staCtrl.eControlData.length > 5 + ? 5 + : staCtrl.eControlData.length, + itemBuilder: (context, index) { + return GasStationCard( + station: staCtrl.eControlData[index], + index: index, + ); + }, + ), + ), + ], ); } }), diff --git a/lib/services/econtrol_service.dart b/lib/services/econtrol_service.dart index 78a7919..e6fb06b 100644 --- a/lib/services/econtrol_service.dart +++ b/lib/services/econtrol_service.dart @@ -1,70 +1,17 @@ - - -import '../models/econtrol_model.dart'; +import 'dart:convert'; import 'package:http/http.dart' as http; class EControlService { - late List eControlData; - - Future getEControlData(double latitude, double longitude, String fuelType) async { - // Simulate fetching data from an API or database - await Future.delayed(Duration(seconds: 2)); // Simulate network delay - eControlData = [ - EControlModel( - id: 1, - name: 'E-Control Station 1', - location: Location( - latitude: 47.93875449671056, - longitude: 13.762706553431048, - ), - contact: Contact( - telephone: '+43 123 456789', - mail: '', - website: 'https://www.econtrol.at', - ), - openingHours: [ - OpeningHours( - day: 'Tuesday', - label: '08:00', - order: 1, - from: '08:00', - to: '18:00', - ), - ], - offerInformation: OfferInformation( - service: true, - selfService: true, - unattended: true, - ), - paymentMethods: PaymentMethods( - cash: true, - debitCard: true, - creditCard: false, - others: '', - ), - paymentArrangements: PaymentArrangements( - cooperative: false, - clubCard: true, - ), - position: 1, - open: true, - distance: 0.5, - prices: [Prices(fuelType: 'DIE', amount: 1.445, label: 'Diesel')], - ), - ]; + Future> getEControlData( + double latitude, + double longitude, + String fuelType, + ) async { // REST Service... URL - String apiUrl = 'https://api.e-control.at/sprit/1.0/search/gas-stations/by-address?latitude=$latitude&longitude=$longitude&fuelType=$fuelType&includeClosed=false'; - try { - var response = await http.get(Uri.parse(apiUrl)); - if (response.statusCode == 200) { - eControlData.clear(); // Clear existing data before adding new results - // Parse the response and update eControlData - print('E-Control API response: ${response.body}'); - eControlData = (response.body as List).map((json) => EControlModel.fromJson(json)).toList(); - print('E-Control data parsed successfully: ${eControlData.length} stations found'); - } - } catch (e) { - print('Error fetching E-Control data: $e'); - } + String apiUrl = + 'https://api.e-control.at/sprit/1.0/search/gas-stations/by-address?latitude=$latitude&longitude=$longitude&fuelType=$fuelType&includeClosed=false'; + var response = await http.get(Uri.parse(apiUrl)); + print('${response.statusCode}'); + return jsonDecode(response.body); } } diff --git a/lib/services/geolocation_service.dart b/lib/services/geolocation_service.dart new file mode 100644 index 0000000..8f1a24b --- /dev/null +++ b/lib/services/geolocation_service.dart @@ -0,0 +1,54 @@ + +import 'package:geolocator/geolocator.dart'; + +class GeolocationService { + late double latitude; + late double longitude; + late bool hasLocation; + + + + Future getCurrentLocation() async { + + bool serviceEnabled = false; + LocationPermission permission; + + try { + // 1. Prüfen, ob Standortdienste aktiviert sind + serviceEnabled = await Geolocator.isLocationServiceEnabled(); + if (!serviceEnabled) { + return Future.error('Standortdienste sind deaktiviert.'); + } + + hasLocation = serviceEnabled; + + // 2. Berechtigungen prüfen + permission = await Geolocator.checkPermission(); + if (permission == LocationPermission.denied) { + permission = await Geolocator.requestPermission(); + hasLocation = true; + if (permission == LocationPermission.denied) { + hasLocation = false; + return Future.error('Berechtigung verweigert.'); + } + } + + // 3. Position abrufen + Position position = await Geolocator.getCurrentPosition( + locationSettings: const LocationSettings( + accuracy: LocationAccuracy.high, + ), + ); + + // 4. Standort über Backend-Proxy abrufen + latitude = position.latitude; + longitude = position.longitude; + if(latitude > 0 && longitude > 0) { + hasLocation = true; + } + } catch (e) { + print("Fehler beim Abrufen des Standorts: $e"); + hasLocation = false; + } + } +} diff --git a/lib/services/location_iq_service.dart b/lib/services/location_iq_service.dart index e3babd3..c9f4621 100644 --- a/lib/services/location_iq_service.dart +++ b/lib/services/location_iq_service.dart @@ -11,32 +11,6 @@ class LocationIQService { Future fetchLocationIQ(double lat, double lon) async { // https://eu1.locationiq.com/v1/reverse?key=$locationIQKey&lat=47.93875449671056&lon=13.762706553431048&format=json - // Simulierte Antwort (für Testzwecke) - locationIQ = LocationIQ( - placeId: '12345', - licence: 'Data © OpenStreetMap contributors', - osmType: 'node', - osmId: '67890', - lat: lat.toString(), - lon: lon.toString(), - displayName: 'Test Location', - address: Address( - houseNumber: '123', - road: 'Test Street', - village: 'Test Village', - county: 'Test County', - state: 'Test State', - postcode: '12345', - country: 'Test Country', - countryCode: 'TC', - ), - boundingbox: [ - '47.93875449671056', - '47.93875449671056', - '13.762706553431048', - '13.762706553431048', - ], - ); // Http Request var httpClient = http.Client(); diff --git a/lib/widgets/gasstation_card_widget.dart b/lib/widgets/gasstation_card_widget.dart new file mode 100644 index 0000000..e857941 --- /dev/null +++ b/lib/widgets/gasstation_card_widget.dart @@ -0,0 +1,235 @@ +import 'package:flutter/material.dart'; +import '../models/econtrol_model.dart'; + +class GasStationCard extends StatelessWidget { + final EControlModel station; + final int index; + + const GasStationCard({super.key, required this.station, required this.index}); + + Color _getPriceColor(double? price) { + if (price == null) return Colors.grey; + if (price < 1.50) return Colors.green; + if (price < 1.70) return Colors.orange; + return Colors.red; + } + + String _formatDistance(double? distance) { + if (distance == null) return 'N/A'; + if (distance < 1) { + return '${(distance * 1000).toStringAsFixed(0)} m'; + } else { + return '${distance.toStringAsFixed(2)} km'; + } + } + + @override + Widget build(BuildContext context) { + final price = station.prices?.isNotEmpty == true + ? station.prices!.first.amount + : null; + final priceLabel = station.prices?.isNotEmpty == true + ? station.prices!.first.label + : ''; + + return Card( + margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + elevation: 4, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(16), + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [Colors.white, Colors.grey[50]!], + ), + ), + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Header Row with Position and Status + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 6, + ), + decoration: BoxDecoration( + color: Colors.blueGrey[700], + borderRadius: BorderRadius.circular(20), + ), + child: Text( + '#${index + 1}', + style: const TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + fontSize: 12, + ), + ), + ), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 6, + ), + decoration: BoxDecoration( + color: station.open == true + ? Colors.green[400] + : Colors.red[400], + borderRadius: BorderRadius.circular(20), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + station.open == true + ? Icons.check_circle + : Icons.cancel, + color: Colors.white, + size: 16, + ), + const SizedBox(width: 4), + Text( + station.open == true ? 'Geöffnet' : 'Geschlossen', + style: const TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + fontSize: 12, + ), + ), + ], + ), + ), + ], + ), + const SizedBox(height: 12), + + // Station Name + Text( + station.name ?? 'Unbekannte Tankstelle', + style: const TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + color: Colors.black87, + ), + ), + const SizedBox(height: 8), + + // Address + Row( + children: [ + Icon( + Icons.location_on, + size: 16, + color: Colors.blueGrey[600], + ), + const SizedBox(width: 4), + Expanded( + child: Text( + '${station.location?.address ?? ''}, ' + '${station.location?.postalCode ?? ''} ' + '${station.location?.city ?? ''}', + style: TextStyle(fontSize: 14, color: Colors.grey[700]), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + const SizedBox(height: 12), + + // Price and Distance Row + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + // Price + if (price != null) + Container( + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 10, + ), + decoration: BoxDecoration( + color: _getPriceColor(price).withAlpha(50), + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: _getPriceColor(price), + width: 2, + ), + ), + child: Row( + children: [ + Icon( + Icons.local_gas_station, + color: _getPriceColor(price), + size: 24, + ), + const SizedBox(width: 8), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '€ ${price.toStringAsFixed(3)}', + style: TextStyle( + fontSize: 22, + fontWeight: FontWeight.bold, + color: _getPriceColor(price), + ), + ), + if (priceLabel?.isNotEmpty == true) + Text( + priceLabel!, + style: TextStyle( + fontSize: 10, + color: Colors.grey[600], + ), + ), + ], + ), + ], + ), + ), + + // Distance + Container( + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 10, + ), + decoration: BoxDecoration( + color: Colors.blue[50], + borderRadius: BorderRadius.circular(12), + ), + child: Row( + children: [ + Icon( + Icons.navigation, + color: Colors.blue[700], + size: 20, + ), + const SizedBox(width: 8), + Text( + _formatDistance(station.distance), + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.blue[700], + ), + ), + ], + ), + ), + ], + ), + ], + ), + ), + ), + ); + } +}