From c0960cbd9fe4f96a0b403c7104347f3cf38ac716 Mon Sep 17 00:00:00 2001 From: atseirjo Date: Wed, 22 Oct 2025 08:43:20 +0200 Subject: [PATCH] add login --- .envExample | 6 + analysis_options.yaml | 3 + lib/bindings/login_binding.dart | 18 + lib/controllers/geolocation_controller.dart | 14 +- lib/controllers/login_controller.dart | 65 ++++ lib/main.dart | 54 +-- lib/main_with_getx.dart | 75 ---- lib/models/login_model.dart | 7 + lib/models/ptv_logistic_model.dart | 168 +++++++++ lib/pages/examples/geolocation_example.dart | 192 +++++----- lib/pages/login/login_view.dart | 14 + lib/routes/app_routes.dart | 42 ++- lib/services/ptv_api_simple.dart | 354 ++++++++++++++++++ linux/flutter/generated_plugin_registrant.cc | 12 + linux/flutter/generated_plugins.cmake | 3 + macos/Flutter/GeneratedPluginRegistrant.swift | 12 + pubspec.lock | 234 +++++++++++- pubspec.yaml | 3 + test/ptv_api_simple_test.dart | 61 +++ .../flutter/generated_plugin_registrant.cc | 9 + windows/flutter/generated_plugins.cmake | 3 + 21 files changed, 1128 insertions(+), 221 deletions(-) create mode 100644 .envExample create mode 100644 lib/bindings/login_binding.dart create mode 100644 lib/controllers/login_controller.dart delete mode 100644 lib/main_with_getx.dart create mode 100644 lib/models/login_model.dart create mode 100644 lib/models/ptv_logistic_model.dart create mode 100644 lib/pages/login/login_view.dart create mode 100644 lib/services/ptv_api_simple.dart create mode 100644 test/ptv_api_simple_test.dart diff --git a/.envExample b/.envExample new file mode 100644 index 0000000..524e619 --- /dev/null +++ b/.envExample @@ -0,0 +1,6 @@ +APPWRITE_ENDPOINT_URL= +APPWRITE_PROJECT_NAME= +APPWRITE_PROJECT_ID= +APPWRITE_DATABASE_ID= +APPWRITE_COLLECTION_ID= +PTVE_API_KEY= \ No newline at end of file diff --git a/analysis_options.yaml b/analysis_options.yaml index 0d29021..ace0476 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -7,6 +7,9 @@ # The following line activates a set of recommended lints for Flutter apps, # packages, and plugins designed to encourage good coding practices. +analyzer: + errors: + avoid_print: ignore include: package:flutter_lints/flutter.yaml linter: diff --git a/lib/bindings/login_binding.dart b/lib/bindings/login_binding.dart new file mode 100644 index 0000000..e3373b0 --- /dev/null +++ b/lib/bindings/login_binding.dart @@ -0,0 +1,18 @@ +import 'package:get/get.dart'; +import '../controllers/login_controller.dart'; + +class LoginBinding extends Bindings { + @override + void dependencies() { + Get.lazyPut(() => LoginController()); + } +} + +/// Alternative: Permanent Binding für App-weite Nutzung +class LoginPermanentBinding extends Bindings { + @override + void dependencies() { + // Permanent - Controller bleibt im Speicher + Get.put(LoginController(), permanent: true); + } +} diff --git a/lib/controllers/geolocation_controller.dart b/lib/controllers/geolocation_controller.dart index cb057c8..e00578e 100644 --- a/lib/controllers/geolocation_controller.dart +++ b/lib/controllers/geolocation_controller.dart @@ -1,6 +1,8 @@ import 'package:get/get.dart'; import 'package:geolocator/geolocator.dart'; import '../../services/geolocation.dart'; +import '../models/ptv_logistic_model.dart'; +import '../services/ptv_api_simple.dart'; /// GetX Controller für Geolocation Funktionalität class GeolocationController extends GetxController { @@ -9,12 +11,14 @@ class GeolocationController extends GetxController { final _statusText = 'Noch keine Position ermittelt'.obs; final _isTracking = false.obs; final _isLoading = false.obs; + final _ptvModel = Rxn(); // Getter für reactive Variablen Position? get currentPosition => _currentPosition.value; String get statusText => _statusText.value; bool get isTracking => _isTracking.value; bool get isLoading => _isLoading.value; + PTVModel? get ptvModel => _ptvModel.value; @override void onClose() { @@ -34,7 +38,15 @@ class GeolocationController extends GetxController { if (position != null) { _currentPosition.value = position; _statusText.value = GeolocationService.positionToString(position); - + var resultData = await PtvApiServiceSimple.getLocationsByPosition( + latitude: position.latitude, + longitude: position.longitude, + ); + if(resultData != null) { + _ptvModel.value = PTVModel.fromJson(resultData); + } else { + _ptvModel.value = null; + } // Erfolgs-Snackbar anzeigen Get.snackbar( 'Position ermittelt', diff --git a/lib/controllers/login_controller.dart b/lib/controllers/login_controller.dart new file mode 100644 index 0000000..e53f4d2 --- /dev/null +++ b/lib/controllers/login_controller.dart @@ -0,0 +1,65 @@ +import 'package:appwrite/appwrite.dart' as account_models; +import 'package:appwrite/models.dart' as user_models; +import 'package:flutter/cupertino.dart'; +import 'package:get/get.dart'; +import 'package:appwrite/appwrite.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; + +class LoginController extends GetxController { + final _account = Rxn(); + final mailController = TextEditingController(); + final passwordController = TextEditingController(); + final nameController = TextEditingController(); + final _endpoint = dotenv.env['APPWRITE_ENDPOINT_URL'] ?? ''; + final _projectId = dotenv.env['APPWRITE_PROJECT_ID'] ?? ''; + final _logedInUser = Rxn(); + + account_models.Account? get account => _account.value; + user_models.User? get logedInUser => _logedInUser.value; + + + @override + void onInit() { + Client client = Client() + .setEndpoint(_endpoint) + .setProject(_projectId); + _account.value = Account(client); + super.onInit(); + } + + Future login(String email, String password) async { + await _account.value!.createEmailPasswordSession( + email: email, + password: password, + ); + final user = await _account.value!.get(); + + _logedInUser.value = user; + + } + + Future register(String email, String password, String name) async { + await _account.value!.create( + userId: ID.unique(), + email: email, + password: password, + name: name, + ); + await login(email, password); + } + + Future logout() async { + await _account.value!.deleteSession(sessionId: 'current'); + _logedInUser.value = null; + } + + + + @override + void onClose() { + mailController.dispose(); + passwordController.dispose(); + nameController.dispose(); + super.onClose(); + } +} diff --git a/lib/main.dart b/lib/main.dart index 0fa59df..0fca2b5 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,10 +1,12 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import 'bindings/login_binding.dart'; import 'routes/app_routes.dart'; -import 'bindings/geolocation_binding.dart'; -import 'pages/examples/geolocation_example.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; -void main() { +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + await dotenv.load(fileName: ".env"); runApp(const MyApp()); } @@ -21,11 +23,9 @@ class MyApp extends StatelessWidget { useMaterial3: true, ), // GetX Konfiguration - initialRoute: AppRoutes.geolocation, + initialRoute: AppRoutes.login, getPages: AppRoutes.pages, - initialBinding: GeolocationBinding(), // Optional: Globale Bindings - // Alternative: Direkte Navigation ohne Routen - home: const GeolocationExample(), + initialBinding: LoginBinding(), // GetX Optionen enableLog: true, // Debugging @@ -33,42 +33,4 @@ class MyApp extends StatelessWidget { transitionDuration: const Duration(milliseconds: 300), ); } -} - -/// Alternative: Home Page mit Navigation zu Geolocation -class HomePage extends StatelessWidget { - const HomePage({super.key}); - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: const Text('Tank App'), - backgroundColor: Theme.of(context).colorScheme.inversePrimary, - ), - body: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text( - 'Tank Appwrite App', - style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), - ), - const SizedBox(height: 32), - ElevatedButton.icon( - onPressed: () => Get.to(() => const GeolocationExample()), - icon: const Icon(Icons.location_on), - label: const Text('Geolocation Beispiel'), - style: ElevatedButton.styleFrom( - padding: const EdgeInsets.symmetric( - horizontal: 24, - vertical: 12, - ), - ), - ), - ], - ), - ), - ); - } -} +} \ No newline at end of file diff --git a/lib/main_with_getx.dart b/lib/main_with_getx.dart deleted file mode 100644 index 53a0582..0000000 --- a/lib/main_with_getx.dart +++ /dev/null @@ -1,75 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -import 'routes/app_routes.dart'; -import 'bindings/geolocation_binding.dart'; -import 'pages/examples/geolocation_example.dart'; - -void main() { - runApp(const MyApp()); -} - -class MyApp extends StatelessWidget { - const MyApp({super.key}); - - @override - Widget build(BuildContext context) { - return GetMaterialApp( - debugShowCheckedModeBanner: false, - title: 'Web Flutter Tank Appwrite App', - theme: ThemeData( - colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), - useMaterial3: true, - ), - // GetX Konfiguration - initialRoute: AppRoutes.geolocation, - getPages: AppRoutes.pages, - initialBinding: GeolocationBinding(), // Optional: Globale Bindings - - // Alternative: Direkte Navigation ohne Routen - //home: const GeolocationExample(), - - // GetX Optionen - enableLog: true, // Debugging - defaultTransition: Transition.fade, - transitionDuration: const Duration(milliseconds: 300), - ); - } -} - -/// Alternative: Home Page mit Navigation zu Geolocation -class HomePage extends StatelessWidget { - const HomePage({super.key}); - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: const Text('Tank App'), - backgroundColor: Theme.of(context).colorScheme.inversePrimary, - ), - body: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text( - 'Tank Appwrite App', - style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), - ), - const SizedBox(height: 32), - ElevatedButton.icon( - onPressed: () => Get.to(() => const GeolocationExample()), - icon: const Icon(Icons.location_on), - label: const Text('Geolocation Beispiel'), - style: ElevatedButton.styleFrom( - padding: const EdgeInsets.symmetric( - horizontal: 24, - vertical: 12, - ), - ), - ), - ], - ), - ), - ); - } -} \ No newline at end of file diff --git a/lib/models/login_model.dart b/lib/models/login_model.dart new file mode 100644 index 0000000..002c828 --- /dev/null +++ b/lib/models/login_model.dart @@ -0,0 +1,7 @@ +class LoginSigninModel { + String email; + String password; + String name; + + LoginSigninModel({this.email='test@test.at', this.password='123PassWd', this.name='TestUser'}); +} diff --git a/lib/models/ptv_logistic_model.dart b/lib/models/ptv_logistic_model.dart new file mode 100644 index 0000000..0e23cb8 --- /dev/null +++ b/lib/models/ptv_logistic_model.dart @@ -0,0 +1,168 @@ + + + +/// Hauptklasse für die PTV Location Response +class PTVModel { + List? locations; + String? noMatchFeedbackId; + + PTVModel({this.locations, this.noMatchFeedbackId}); + + PTVModel.fromJson(Map json) { + if (json['locations'] != null) { + locations = []; + json['locations'].forEach((v) { + locations!.add(Locations.fromJson(v)); + }); + } + noMatchFeedbackId = json['noMatchFeedbackId']; + } + + Map toJson() { + final Map data = {}; + if (locations != null) { + data['locations'] = locations!.map((v) => v.toJson()).toList(); + } + data['noMatchFeedbackId'] = noMatchFeedbackId; + return data; + } +} + +class Locations { + ReferencePosition? referencePosition; + Address? address; + String? locationType; + Quality? quality; + String? formattedAddress; + String? feedbackId; + + Locations( + {this.referencePosition, + this.address, + this.locationType, + this.quality, + this.formattedAddress, + this.feedbackId}); + + Locations.fromJson(Map json) { + referencePosition = json['referencePosition'] != null + ? ReferencePosition.fromJson(json['referencePosition']) + : null; + address = + json['address'] != null ? Address.fromJson(json['address']) : null; + locationType = json['locationType']; + quality = + json['quality'] != null ? Quality.fromJson(json['quality']) : null; + formattedAddress = json['formattedAddress']; + feedbackId = json['feedbackId']; + } + + Map toJson() { + final Map data = {}; + if (referencePosition != null) { + data['referencePosition'] = referencePosition!.toJson(); + } + if (address != null) { + data['address'] = address!.toJson(); + } + data['locationType'] = locationType; + if (quality != null) { + data['quality'] = quality!.toJson(); + } + data['formattedAddress'] = formattedAddress; + data['feedbackId'] = feedbackId; + return data; + } +} + +class ReferencePosition { + double? latitude; + double? longitude; + + ReferencePosition({this.latitude, this.longitude}); + + ReferencePosition.fromJson(Map json) { + latitude = json['latitude']; + longitude = json['longitude']; + } + + Map toJson() { + final Map data = {}; + data['latitude'] = latitude; + data['longitude'] = longitude; + return data; + } +} + +class Address { + String? countryName; + String? state; + String? province; + String? city; + String? district; + String? subdistrict; + String? street; + String? houseNumber; + String? countryCodeIsoAlpha2; + String? countryCodeIsoAlpha3; + String? countryCode; + + Address( + {this.countryName, + this.state, + this.province, + this.city, + this.district, + this.subdistrict, + this.street, + this.houseNumber, + this.countryCodeIsoAlpha2, + this.countryCodeIsoAlpha3, + this.countryCode}); + + Address.fromJson(Map json) { + countryName = json['countryName']; + state = json['state']; + province = json['province']; + city = json['city']; + district = json['district']; + subdistrict = json['subdistrict']; + street = json['street']; + houseNumber = json['houseNumber']; + countryCodeIsoAlpha2 = json['countryCodeIsoAlpha2']; + countryCodeIsoAlpha3 = json['countryCodeIsoAlpha3']; + countryCode = json['countryCode']; + } + + Map toJson() { + final Map data = {}; + data['countryName'] = countryName; + data['state'] = state; + data['province'] = province; + data['city'] = city; + data['district'] = district; + data['subdistrict'] = subdistrict; + data['street'] = street; + data['houseNumber'] = houseNumber; + data['countryCodeIsoAlpha2'] = countryCodeIsoAlpha2; + data['countryCodeIsoAlpha3'] = countryCodeIsoAlpha3; + data['countryCode'] = countryCode; + return data; + } +} + +class Quality { + int? distance; + + Quality({this.distance}); + + Quality.fromJson(Map json) { + distance = json['distance']; + } + + Map toJson() { + final Map data = {}; + data['distance'] = distance; + return data; + } +} \ No newline at end of file diff --git a/lib/pages/examples/geolocation_example.dart b/lib/pages/examples/geolocation_example.dart index 1d2148e..d303a06 100644 --- a/lib/pages/examples/geolocation_example.dart +++ b/lib/pages/examples/geolocation_example.dart @@ -24,11 +24,12 @@ class GeolocationExample extends StatelessWidget { ), ], ), - body: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ + body: SafeArea( + child: SingleChildScrollView( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ // Status Card Obx(() => Card( child: Padding( @@ -107,8 +108,16 @@ class GeolocationExample extends StatelessWidget { 'Längengrad:', controller.currentPosition!.longitude.toStringAsFixed(6), Icons.location_on, - ), - _buildPositionDetail( + ), + if (controller.ptvModel != null && + controller.ptvModel!.locations != null && + controller.ptvModel!.locations!.isNotEmpty) + _buildPositionDetail( + 'Adresse:', + '${controller.ptvModel!.locations!.first.formattedAddress}', + Icons.add_home_rounded, + ), + _buildPositionDetail( 'Genauigkeit:', '${controller.currentPosition!.accuracy.toStringAsFixed(1)} m', Icons.gps_fixed, @@ -137,92 +146,94 @@ class GeolocationExample extends StatelessWidget { const SizedBox(height: 16), // Action Buttons - Expanded( - child: Column( - children: [ - // Aktuelle Position Button - Obx(() => SizedBox( - width: double.infinity, - child: ElevatedButton.icon( - onPressed: controller.isLoading ? null : controller.getCurrentPosition, - icon: controller.isLoading - ? const SizedBox( - width: 20, - height: 20, - child: CircularProgressIndicator(strokeWidth: 2), - ) - : const Icon(Icons.my_location), - label: const Text('Aktuelle Position ermitteln'), - style: ElevatedButton.styleFrom( - padding: const EdgeInsets.symmetric(vertical: 12), - ), + Column( + children: [ + // Aktuelle Position Button + Obx(() => SizedBox( + width: double.infinity, + child: ElevatedButton.icon( + onPressed: controller.isLoading ? null : controller.getCurrentPosition, + icon: controller.isLoading + ? const SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator(strokeWidth: 2), + ) + : const Icon(Icons.my_location), + label: const Text('Aktuelle Position ermitteln'), + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 12), ), - )), - - const SizedBox(height: 12), - - // Tracking Toggle Button - Obx(() => SizedBox( - width: double.infinity, - child: ElevatedButton.icon( - onPressed: controller.isLoading ? null : controller.toggleTracking, - icon: Icon(controller.isTracking ? Icons.stop : Icons.play_arrow), - label: Text(controller.isTracking ? 'Tracking stoppen' : 'Tracking starten'), - style: ElevatedButton.styleFrom( - backgroundColor: controller.isTracking ? Colors.red : Colors.green, - foregroundColor: Colors.white, - padding: const EdgeInsets.symmetric(vertical: 12), - ), - ), - )), - - const SizedBox(height: 12), - - // Permissions Button - Obx(() => SizedBox( - width: double.infinity, - child: ElevatedButton.icon( - onPressed: controller.isLoading ? null : controller.checkPermissions, - icon: const Icon(Icons.security), - label: const Text('Berechtigungen prüfen'), - style: ElevatedButton.styleFrom( - padding: const EdgeInsets.symmetric(vertical: 12), - ), - ), - )), - - const SizedBox(height: 12), - - // Settings Buttons Row - Row( - children: [ - Expanded( - child: ElevatedButton.icon( - onPressed: controller.openLocationSettings, - icon: const Icon(Icons.location_city), - label: const Text('Location'), - style: ElevatedButton.styleFrom( - padding: const EdgeInsets.symmetric(vertical: 12), - ), - ), - ), - const SizedBox(width: 8), - Expanded( - child: ElevatedButton.icon( - onPressed: controller.openAppSettings, - icon: const Icon(Icons.settings), - label: const Text('App'), - style: ElevatedButton.styleFrom( - padding: const EdgeInsets.symmetric(vertical: 12), - ), - ), - ), - ], ), - ], - ), + )), + + const SizedBox(height: 12), + + // Tracking Toggle Button + Obx(() => SizedBox( + width: double.infinity, + child: ElevatedButton.icon( + onPressed: controller.isLoading ? null : controller.toggleTracking, + icon: Icon(controller.isTracking ? Icons.stop : Icons.play_arrow), + label: Text(controller.isTracking ? 'Tracking stoppen' : 'Tracking starten'), + style: ElevatedButton.styleFrom( + backgroundColor: controller.isTracking ? Colors.red : Colors.green, + foregroundColor: Colors.white, + padding: const EdgeInsets.symmetric(vertical: 12), + ), + ), + )), + + const SizedBox(height: 12), + + // Permissions Button + Obx(() => SizedBox( + width: double.infinity, + child: ElevatedButton.icon( + onPressed: controller.isLoading ? null : controller.checkPermissions, + icon: const Icon(Icons.security), + label: const Text('Berechtigungen prüfen'), + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 12), + ), + ), + )), + + const SizedBox(height: 12), + + // Settings Buttons Row + Row( + children: [ + Expanded( + child: ElevatedButton.icon( + onPressed: controller.openLocationSettings, + icon: const Icon(Icons.location_city), + label: const Text('Location'), + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 12), + ), + ), + ), + const SizedBox(width: 8), + Expanded( + child: ElevatedButton.icon( + onPressed: controller.openAppSettings, + icon: const Icon(Icons.settings), + label: const Text('App'), + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 12), + ), + ), + ), + ], + ), + ], ), - ], + + // Bottom Spacing + const SizedBox(height: 24), + ], + ), ), ), ); @@ -245,6 +256,7 @@ class GeolocationExample extends StatelessWidget { child: Text( value, style: const TextStyle(fontFamily: 'monospace'), + overflow: TextOverflow.ellipsis, ), ), ], diff --git a/lib/pages/login/login_view.dart b/lib/pages/login/login_view.dart new file mode 100644 index 0000000..d674dd1 --- /dev/null +++ b/lib/pages/login/login_view.dart @@ -0,0 +1,14 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import '../../controllers/login_controller.dart'; + +class LoginPage extends GetView { + const LoginPage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Container(color: Colors.red.shade300, child: Center(child: Text('Login Page')),), + ); + } +} diff --git a/lib/routes/app_routes.dart b/lib/routes/app_routes.dart index f57765f..32be94a 100644 --- a/lib/routes/app_routes.dart +++ b/lib/routes/app_routes.dart @@ -1,11 +1,14 @@ import 'package:get/get.dart'; +import '../bindings/login_binding.dart'; import '../pages/examples/geolocation_example.dart'; import '../bindings/geolocation_binding.dart'; +import '../pages/login/login_view.dart'; /// App Routes Konfiguration class AppRoutes { static const String home = '/'; static const String geolocation = '/geolocation'; + static const String login = '/login'; /// Route Pages Definition static List pages = [ @@ -16,13 +19,20 @@ class AppRoutes { transition: Transition.cupertino, transitionDuration: const Duration(milliseconds: 300), ), + GetPage( + name: login, + page: () => const LoginPage(), + binding: LoginBinding(), // Dependency Injection + transition: Transition.cupertino, + transitionDuration: const Duration(milliseconds: 300), + ), ]; } /// Route Namen als Konstanten für typsichere Navigation -class Routes { - static const String geolocationExample = '/geolocation-example'; -} +// class Routes { +// static const String geolocationExample = '/geolocation-example'; +// } /// Navigation Helper Klasse class AppNavigation { @@ -31,11 +41,6 @@ class AppNavigation { Get.toNamed(AppRoutes.geolocation); } - /// Navigate back - static void back() { - Get.back(); - } - /// Navigate and replace current route static void offGeolocation() { Get.offNamed(AppRoutes.geolocation); @@ -45,4 +50,25 @@ class AppNavigation { static void offAllToGeolocation() { Get.offAllNamed(AppRoutes.geolocation); } + + /// Navigate to Login Page + static void toLogin() { + Get.toNamed(AppRoutes.login); + } + /// Navigate and replace current route + static void offLogin() { + Get.offNamed(AppRoutes.login); + } + /// Navigate and clear all previous routes + static void offAllToLogin() { + Get.offAllNamed(AppRoutes.login); + } + + + + /// Navigate back + static void back() { + Get.back(); + } + } \ No newline at end of file diff --git a/lib/services/ptv_api_simple.dart b/lib/services/ptv_api_simple.dart new file mode 100644 index 0000000..ea92fc3 --- /dev/null +++ b/lib/services/ptv_api_simple.dart @@ -0,0 +1,354 @@ +import 'dart:convert'; +import 'package:http/http.dart' as http; +import 'package:flutter/foundation.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; + +/// Einfacher PTV API Service mit statischen Methoden +class PtvApiServiceSimple { + /// Private Konstruktor + PtvApiServiceSimple._(); + + /// Base URL der PTV API + static const String _baseUrl = 'https://api.myptv.com/geocoding/v1/locations/by-position'; + + /// Standard Sprache + static const String _defaultLanguage = 'de'; + + /// Timeout in Sekunden + static const int _timeoutSeconds = 15; + + /// API Key aus .env Datei + static String get _apiKey => dotenv.env['PTVE_API_KEY'] ?? ''; + + /// HTTP Headers + static Map get _headers => { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + 'ApiKey': _apiKey, + }; + + /// Hauptmethode: Holt Location-Daten für gegebene Koordinaten + /// + /// Beispiel URL: https://api.myptv.com/geocoding/v1/locations/by-position/48.208282/14.214758?language=de + /// + /// [latitude] - Breitengrad (z.B. 48.208282) + /// [longitude] - Längengrad (z.B. 14.214758) + /// [language] - Sprache für Antwort (default: 'de') + /// + /// Returns Map mit API-Response oder null bei Fehler + static Future?> getLocationsByPosition({ + required double latitude, + required double longitude, + String language = _defaultLanguage, + }) async { + try { + // Validiere Koordinaten + if (!_isValidCoordinate(latitude, longitude)) { + if (kDebugMode) { + print('❌ Ungültige Koordinaten: lat=$latitude, lng=$longitude'); + } + return null; + } + + // Lade .env falls API Key leer + if (_apiKey.isEmpty) { + await dotenv.load(fileName: ".env"); + if (_apiKey.isEmpty) { + if (kDebugMode) { + print('❌ PTVE_API_KEY nicht in .env gefunden'); + } + return null; + } + } + + // Baue URL + final url = '$_baseUrl/$latitude/$longitude?language=$language'; + + if (kDebugMode) { + print('🌐 PTV API Request: $url'); + print('🔑 API Key vorhanden: ${_apiKey.isNotEmpty}'); + } + + // HTTP GET Request + final response = await http + .get( + Uri.parse(url), + headers: _headers, + ) + .timeout(Duration(seconds: _timeoutSeconds)); + + if (kDebugMode) { + print('📡 Response Status: ${response.statusCode}'); + } + + // Verarbeite Response + if (response.statusCode == 200) { + final jsonData = json.decode(response.body) as Map; + + if (kDebugMode) { + print('✅ API Request erfolgreich'); + print('📍 Locations gefunden: ${(jsonData['locations'] as List?)?.length ?? 0}'); + } + + return jsonData; + } else { + if (kDebugMode) { + print('❌ API Fehler: ${response.statusCode}'); + print('📄 Response Body: ${response.body}'); + } + return null; + } + } catch (e) { + if (kDebugMode) { + print('❌ Exception beim API Aufruf: $e'); + } + return null; + } + } + + /// Erweiterte Methode mit zusätzlichen Parametern + static Future?> getLocationsByPositionAdvanced({ + required double latitude, + required double longitude, + String language = _defaultLanguage, + int? maxResults, + int? radius, + }) async { + try { + // Baue URL mit zusätzlichen Query-Parametern + String url = '$_baseUrl/$latitude/$longitude?language=$language'; + + if (maxResults != null && maxResults > 0) { + url += '&maxResults=$maxResults'; + } + + if (radius != null && radius > 0) { + url += '&radius=$radius'; + } + + if (kDebugMode) { + print('🌐 PTV API Advanced Request: $url'); + } + + final response = await http + .get( + Uri.parse(url), + headers: _headers, + ) + .timeout(Duration(seconds: _timeoutSeconds)); + + if (response.statusCode == 200) { + return json.decode(response.body) as Map; + } else { + if (kDebugMode) { + print('❌ Advanced API Fehler: ${response.statusCode}'); + } + return null; + } + } catch (e) { + if (kDebugMode) { + print('❌ Exception bei erweiterter API Abfrage: $e'); + } + return null; + } + } + + /// Hilfsmethoden + + /// Validiert GPS-Koordinaten + static bool _isValidCoordinate(double latitude, double longitude) { + return latitude >= -90 && + latitude <= 90 && + longitude >= -180 && + longitude <= 180; + } + + /// Prüft API-Verfügbarkeit mit Test-Request + static Future testApiConnection() async { + try { + if (kDebugMode) { + print('\n🧪 PTV API CONNECTION TEST 🧪'); + } + + // Test mit Wien Koordinaten + final result = await getLocationsByPosition( + latitude: 48.2082, + longitude: 16.3738, + ); + + final success = result != null; + + if (kDebugMode) { + print(success ? '✅ API Test erfolgreich' : '❌ API Test fehlgeschlagen'); + print('🧪 TEST ABGESCHLOSSEN\n'); + } + + return success; + } catch (e) { + if (kDebugMode) { + print('❌ Test Exception: $e'); + print('🧪 TEST ABGESCHLOSSEN\n'); + } + return false; + } + } + + /// Debugging: Zeigt Service-Informationen + static void printServiceInfo() { + if (kDebugMode) { + print('\n📋 PTV API SERVICE INFO 📋'); + print('Base URL: $_baseUrl'); + print('Default Language: $_defaultLanguage'); + print('Timeout: ${_timeoutSeconds}s'); + print('API Key verfügbar: ${_apiKey.isNotEmpty}'); + print('API Key Length: ${_apiKey.length}'); + print('📋 SERVICE INFO END 📋\n'); + } + } + + /// Demonstration der API-Verwendung + static Future runDemo() async { + if (kDebugMode) { + print('\n🚀 PTV API DEMO START 🚀'); + + // Service Info + printServiceInfo(); + + // Test Connection + await testApiConnection(); + + // Beispiel 1: Traun, Österreich + print('📍 Beispiel 1: Traun, Österreich'); + final traunResult = await getLocationsByPosition( + latitude: 48.208282, + longitude: 14.214758, + ); + + if (traunResult != null) { + final locations = traunResult['locations'] as List?; + if (locations != null && locations.isNotEmpty) { + final firstLocation = locations.first; + print('✅ Gefunden: ${firstLocation['formattedAddress']}'); + print('📍 Koordinaten: ${firstLocation['referencePosition']['latitude']}, ${firstLocation['referencePosition']['longitude']}'); + + final quality = firstLocation['quality']; + if (quality != null) { + print('🎯 Genauigkeit: ${quality['distance']}m'); + } + } + } else { + print('❌ Keine Daten für Traun erhalten'); + } + + // Beispiel 2: Wien mit erweiterten Parametern + print('\n📍 Beispiel 2: Wien (erweitert)'); + final wienResult = await getLocationsByPositionAdvanced( + latitude: 48.2082, + longitude: 16.3738, + maxResults: 3, + radius: 500, + ); + + if (wienResult != null) { + final locations = wienResult['locations'] as List?; + print('✅ Wien Locations gefunden: ${locations?.length ?? 0}'); + + if (locations != null) { + for (int i = 0; i < locations.length && i < 3; i++) { + final location = locations[i]; + print(' ${i + 1}. ${location['formattedAddress']}'); + } + } + } + + print('🚀 DEMO ABGESCHLOSSEN 🚀\n'); + } + } +} + +/// Utility-Klasse für häufige PTV-Operationen +class PtvApiUtils { + /// Extrahiert die beste Location aus der API-Response + static Map? getBestLocation(Map apiResponse) { + final locations = apiResponse['locations'] as List?; + if (locations == null || locations.isEmpty) return null; + + // Suche Location mit geringster Distanz + Map? bestLocation; + double? bestDistance; + + for (final location in locations) { + final quality = location['quality']; + if (quality != null) { + final distance = (quality['distance'] as num?)?.toDouble(); + if (distance != null && (bestDistance == null || distance < bestDistance)) { + bestDistance = distance; + bestLocation = location; + } + } + } + + return bestLocation ?? locations.first; + } + + /// Extrahiert formatierte Adresse + static String? getFormattedAddress(Map apiResponse) { + final bestLocation = getBestLocation(apiResponse); + return bestLocation?['formattedAddress'] as String?; + } + + /// Extrahiert Koordinaten + static Map? getCoordinates(Map apiResponse) { + final bestLocation = getBestLocation(apiResponse); + final refPos = bestLocation?['referencePosition']; + + if (refPos != null) { + return { + 'latitude': (refPos['latitude'] as num?)?.toDouble() ?? 0.0, + 'longitude': (refPos['longitude'] as num?)?.toDouble() ?? 0.0, + }; + } + + return null; + } + + /// Prüft Qualität der Location + static String getQualityLevel(Map apiResponse) { + final bestLocation = getBestLocation(apiResponse); + final quality = bestLocation?['quality']; + + if (quality != null) { + final distance = (quality['distance'] as num?)?.toDouble() ?? 999999; + + if (distance <= 10) return 'Sehr hoch'; + if (distance <= 50) return 'Hoch'; + if (distance <= 100) return 'Mittel'; + if (distance <= 500) return 'Niedrig'; + return 'Sehr niedrig'; + } + + return 'Unbekannt'; + } + + /// Konvertiert API-Response zu lesbarem String + static String formatLocationInfo(Map apiResponse) { + final bestLocation = getBestLocation(apiResponse); + if (bestLocation == null) return 'Keine Location gefunden'; + + final address = bestLocation['formattedAddress'] ?? 'Unbekannte Adresse'; + final refPos = bestLocation['referencePosition']; + final quality = bestLocation['quality']; + + String info = 'Adresse: $address\n'; + + if (refPos != null) { + info += 'Koordinaten: ${refPos['latitude']}, ${refPos['longitude']}\n'; + } + + if (quality != null) { + info += 'Genauigkeit: ${quality['distance']}m (${getQualityLevel(apiResponse)})\n'; + } + + return info; + } +} \ No newline at end of file diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index e71a16d..9f3151b 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -6,6 +6,18 @@ #include "generated_plugin_registrant.h" +#include +#include +#include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) desktop_webview_window_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "DesktopWebviewWindowPlugin"); + desktop_webview_window_plugin_register_with_registrar(desktop_webview_window_registrar); + g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); + url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); + g_autoptr(FlPluginRegistrar) window_to_front_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "WindowToFrontPlugin"); + window_to_front_plugin_register_with_registrar(window_to_front_registrar); } diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 2e1de87..9bc27dc 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -3,6 +3,9 @@ # list(APPEND FLUTTER_PLUGIN_LIST + desktop_webview_window + url_launcher_linux + window_to_front ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index d7ee0bc..18bc7ea 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,10 +5,22 @@ import FlutterMacOS import Foundation +import desktop_webview_window +import device_info_plus +import flutter_web_auth_2 import geolocator_apple import package_info_plus +import path_provider_foundation +import url_launcher_macos +import window_to_front func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + DesktopWebviewWindowPlugin.register(with: registry.registrar(forPlugin: "DesktopWebviewWindowPlugin")) + DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) + FlutterWebAuth2Plugin.register(with: registry.registrar(forPlugin: "FlutterWebAuth2Plugin")) GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin")) FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) + PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) + UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) + WindowToFrontPlugin.register(with: registry.registrar(forPlugin: "WindowToFrontPlugin")) } diff --git a/pubspec.lock b/pubspec.lock index 6a0dae3..61fd0e8 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,6 +1,14 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + appwrite: + dependency: "direct main" + description: + name: appwrite + sha256: "358a443ba8366d48fc8cdec88645b97eb8f3941ea4244c2e6d524603e4d7f27e" + url: "https://pub.dev" + source: hosted + version: "17.0.0" args: dependency: transitive description: @@ -49,6 +57,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.19.1" + cookie_jar: + dependency: transitive + description: + name: cookie_jar + sha256: a6ac027d3ed6ed756bfce8f3ff60cb479e266f3b0fdabd6242b804b6765e52de + url: "https://pub.dev" + source: hosted + version: "4.0.8" crypto: dependency: transitive description: @@ -73,6 +89,30 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.11" + desktop_webview_window: + dependency: transitive + description: + name: desktop_webview_window + sha256: "57cf20d81689d5cbb1adfd0017e96b669398a669d927906073b0e42fc64111c0" + url: "https://pub.dev" + source: hosted + version: "0.2.3" + device_info_plus: + dependency: transitive + description: + name: device_info_plus + sha256: a7fd703482b391a87d60b6061d04dfdeab07826b96f9abd8f5ed98068acc0074 + url: "https://pub.dev" + source: hosted + version: "10.1.2" + device_info_plus_platform_interface: + dependency: transitive + description: + name: device_info_plus_platform_interface + sha256: e1ea89119e34903dca74b883d0dd78eb762814f97fb6c76f35e9ff74d261a18f + url: "https://pub.dev" + source: hosted + version: "7.0.3" fake_async: dependency: transitive description: @@ -89,6 +129,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" fixnum: dependency: transitive description: @@ -102,6 +150,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_dotenv: + dependency: "direct main" + description: + name: flutter_dotenv + sha256: d4130c4a43e0b13fefc593bc3961f2cb46e30cb79e253d4a526b1b5d24ae1ce4 + url: "https://pub.dev" + source: hosted + version: "6.0.0" flutter_lints: dependency: "direct dev" description: @@ -115,6 +171,22 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_web_auth_2: + dependency: transitive + description: + name: flutter_web_auth_2 + sha256: "3c14babeaa066c371f3a743f204dd0d348b7d42ffa6fae7a9847a521aff33696" + url: "https://pub.dev" + source: hosted + version: "4.1.0" + flutter_web_auth_2_platform_interface: + dependency: transitive + description: + name: flutter_web_auth_2_platform_interface + sha256: c63a472c8070998e4e422f6b34a17070e60782ac442107c70000dd1bed645f4d + url: "https://pub.dev" + source: hosted + version: "4.1.0" flutter_web_plugins: dependency: transitive description: flutter @@ -296,6 +368,54 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.1" + path_provider: + dependency: transitive + description: + name: path_provider + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" + url: "https://pub.dev" + source: hosted + version: "2.1.5" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: e122c5ea805bb6773bb12ce667611265980940145be920cd09a4b0ec0285cb16 + url: "https://pub.dev" + source: hosted + version: "2.2.20" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: efaec349ddfc181528345c56f8eda9d6cccd71c177511b132c6a0ddaefaa2738 + url: "https://pub.dev" + source: hosted + version: "2.4.3" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" petitparser: dependency: transitive description: @@ -304,6 +424,14 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.1" + platform: + dependency: transitive + description: + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.dev" + source: hosted + version: "3.1.6" plugin_platform_interface: dependency: transitive description: @@ -381,6 +509,78 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" + universal_io: + dependency: transitive + description: + name: universal_io + sha256: "1722b2dcc462b4b2f3ee7d188dad008b6eb4c40bbd03a3de451d82c78bba9aad" + url: "https://pub.dev" + source: hosted + version: "2.2.2" + url_launcher: + dependency: transitive + description: + name: url_launcher + sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8 + url: "https://pub.dev" + source: hosted + version: "6.3.2" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + sha256: "5c8b6c2d89a78f5a1cca70a73d9d5f86c701b36b42f9c9dac7bad592113c28e9" + url: "https://pub.dev" + source: hosted + version: "6.3.24" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + sha256: "6b63f1441e4f653ae799166a72b50b1767321ecc263a57aadf825a7a2a5477d9" + url: "https://pub.dev" + source: hosted + version: "6.3.5" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935" + url: "https://pub.dev" + source: hosted + version: "3.2.1" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + sha256: "8262208506252a3ed4ff5c0dc1e973d2c0e0ef337d0a074d35634da5d44397c9" + url: "https://pub.dev" + source: hosted + version: "3.2.4" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77" + url: "https://pub.dev" + source: hosted + version: "3.1.4" uuid: dependency: transitive description: @@ -413,6 +613,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.1" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 + url: "https://pub.dev" + source: hosted + version: "3.0.3" win32: dependency: transitive description: @@ -421,6 +637,22 @@ packages: url: "https://pub.dev" source: hosted version: "5.15.0" + win32_registry: + dependency: transitive + description: + name: win32_registry + sha256: "21ec76dfc731550fd3e2ce7a33a9ea90b828fdf19a5c3bcf556fa992cfa99852" + url: "https://pub.dev" + source: hosted + version: "1.1.5" + window_to_front: + dependency: transitive + description: + name: window_to_front + sha256: "7aef379752b7190c10479e12b5fd7c0b9d92adc96817d9e96c59937929512aee" + url: "https://pub.dev" + source: hosted + version: "0.0.3" xdg_directories: dependency: transitive description: @@ -439,4 +671,4 @@ packages: version: "6.6.1" sdks: dart: ">=3.9.2 <4.0.0" - flutter: ">=3.19.0" + flutter: ">=3.35.0" diff --git a/pubspec.yaml b/pubspec.yaml index 0b5e10f..0d02305 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,6 +16,8 @@ dependencies: get: ^4.7.2 http: ^1.5.0 geolocator: ^14.0.2 + flutter_dotenv: ^6.0.0 + appwrite: 17.0.0 dev_dependencies: flutter_test: @@ -28,3 +30,4 @@ flutter: assets: - assets/img/ + - .env diff --git a/test/ptv_api_simple_test.dart b/test/ptv_api_simple_test.dart new file mode 100644 index 0000000..e8d1a48 --- /dev/null +++ b/test/ptv_api_simple_test.dart @@ -0,0 +1,61 @@ +// ignore_for_file: avoid_print + +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:web_flutter_tank_appwrite_app/services/ptv_api_simple.dart'; + +/// Quick Test der PTV API Simple Integration +void main() async { + print('🚀 PTV API SIMPLE TEST START 🚀'); + + try { + // Lade .env + await dotenv.load(fileName: ".env"); + print('✅ .env Datei geladen'); + + // Test Service Info + PtvApiServiceSimple.printServiceInfo(); + + // Connection Test + print('\n🧪 CONNECTION TEST 🧪'); + final connectionOk = await PtvApiServiceSimple.testApiConnection(); + print('🔗 Verbindung: ${connectionOk ? "OK" : "FEHLER"}'); + + if (connectionOk) { + print('\n📍 API TEST MIT BEISPIELKOORDINATEN 📍'); + + // Test mit Traun Koordinaten + final result = await PtvApiServiceSimple.getLocationsByPosition( + latitude: 48.208282, + longitude: 14.214758, + ); + + if (result != null) { + print('✅ API Response erhalten'); + + // Verwende Utility-Funktionen + final address = PtvApiUtils.getFormattedAddress(result); + final coords = PtvApiUtils.getCoordinates(result); + final quality = PtvApiUtils.getQualityLevel(result); + + print('📝 Adresse: $address'); + print('📍 Koordinaten: ${coords?['latitude']}, ${coords?['longitude']}'); + print('⭐ Qualität: $quality'); + + print('\n📄 Komplette Info:'); + print(PtvApiUtils.formatLocationInfo(result)); + + } else { + print('❌ Keine API Response - prüfe API Key in .env'); + } + + } else { + print('❌ API Connection Test fehlgeschlagen'); + print('⚠️ Prüfe PTVE_API_KEY in .env Datei'); + } + + } catch (e) { + print('❌ Test Exception: $e'); + } + + print('\n🚀 TEST ABGESCHLOSSEN 🚀'); +} \ No newline at end of file diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 1ece8f2..bab28d6 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -6,9 +6,18 @@ #include "generated_plugin_registrant.h" +#include #include +#include +#include void RegisterPlugins(flutter::PluginRegistry* registry) { + DesktopWebviewWindowPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("DesktopWebviewWindowPlugin")); GeolocatorWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("GeolocatorWindows")); + UrlLauncherWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("UrlLauncherWindows")); + WindowToFrontPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("WindowToFrontPlugin")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 7f101a7..136b8e4 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -3,7 +3,10 @@ # list(APPEND FLUTTER_PLUGIN_LIST + desktop_webview_window geolocator_windows + url_launcher_windows + window_to_front ) list(APPEND FLUTTER_FFI_PLUGIN_LIST