add login
This commit is contained in:
18
lib/bindings/login_binding.dart
Normal file
18
lib/bindings/login_binding.dart
Normal file
@@ -0,0 +1,18 @@
|
||||
import 'package:get/get.dart';
|
||||
import '../controllers/login_controller.dart';
|
||||
|
||||
class LoginBinding extends Bindings {
|
||||
@override
|
||||
void dependencies() {
|
||||
Get.lazyPut<LoginController>(() => LoginController());
|
||||
}
|
||||
}
|
||||
|
||||
/// Alternative: Permanent Binding für App-weite Nutzung
|
||||
class LoginPermanentBinding extends Bindings {
|
||||
@override
|
||||
void dependencies() {
|
||||
// Permanent - Controller bleibt im Speicher
|
||||
Get.put<LoginController>(LoginController(), permanent: true);
|
||||
}
|
||||
}
|
||||
@@ -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<PTVModel>();
|
||||
|
||||
// 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',
|
||||
|
||||
65
lib/controllers/login_controller.dart
Normal file
65
lib/controllers/login_controller.dart
Normal file
@@ -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<account_models.Account>();
|
||||
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<user_models.User>();
|
||||
|
||||
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<void> login(String email, String password) async {
|
||||
await _account.value!.createEmailPasswordSession(
|
||||
email: email,
|
||||
password: password,
|
||||
);
|
||||
final user = await _account.value!.get();
|
||||
|
||||
_logedInUser.value = user;
|
||||
|
||||
}
|
||||
|
||||
Future<void> 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<void> logout() async {
|
||||
await _account.value!.deleteSession(sessionId: 'current');
|
||||
_logedInUser.value = null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
mailController.dispose();
|
||||
passwordController.dispose();
|
||||
nameController.dispose();
|
||||
super.onClose();
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
7
lib/models/login_model.dart
Normal file
7
lib/models/login_model.dart
Normal file
@@ -0,0 +1,7 @@
|
||||
class LoginSigninModel {
|
||||
String email;
|
||||
String password;
|
||||
String name;
|
||||
|
||||
LoginSigninModel({this.email='test@test.at', this.password='123PassWd', this.name='TestUser'});
|
||||
}
|
||||
168
lib/models/ptv_logistic_model.dart
Normal file
168
lib/models/ptv_logistic_model.dart
Normal file
@@ -0,0 +1,168 @@
|
||||
|
||||
|
||||
|
||||
/// Hauptklasse für die PTV Location Response
|
||||
class PTVModel {
|
||||
List<Locations>? locations;
|
||||
String? noMatchFeedbackId;
|
||||
|
||||
PTVModel({this.locations, this.noMatchFeedbackId});
|
||||
|
||||
PTVModel.fromJson(Map<String, dynamic> json) {
|
||||
if (json['locations'] != null) {
|
||||
locations = <Locations>[];
|
||||
json['locations'].forEach((v) {
|
||||
locations!.add(Locations.fromJson(v));
|
||||
});
|
||||
}
|
||||
noMatchFeedbackId = json['noMatchFeedbackId'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
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<String, dynamic> 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<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
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<String, dynamic> json) {
|
||||
latitude = json['latitude'];
|
||||
longitude = json['longitude'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
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<String, dynamic> 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<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
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<String, dynamic> json) {
|
||||
distance = json['distance'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data['distance'] = distance;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
14
lib/pages/login/login_view.dart
Normal file
14
lib/pages/login/login_view.dart
Normal file
@@ -0,0 +1,14 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import '../../controllers/login_controller.dart';
|
||||
|
||||
class LoginPage extends GetView<LoginController> {
|
||||
const LoginPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: Container(color: Colors.red.shade300, child: Center(child: Text('Login Page')),),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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<GetPage> 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();
|
||||
}
|
||||
|
||||
}
|
||||
354
lib/services/ptv_api_simple.dart
Normal file
354
lib/services/ptv_api_simple.dart
Normal file
@@ -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<String, String> 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<Map<String, dynamic>?> 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<String, dynamic>;
|
||||
|
||||
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<Map<String, dynamic>?> 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<String, dynamic>;
|
||||
} 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<bool> 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<void> 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<String, dynamic>? getBestLocation(Map<String, dynamic> apiResponse) {
|
||||
final locations = apiResponse['locations'] as List?;
|
||||
if (locations == null || locations.isEmpty) return null;
|
||||
|
||||
// Suche Location mit geringster Distanz
|
||||
Map<String, dynamic>? 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<String, dynamic> apiResponse) {
|
||||
final bestLocation = getBestLocation(apiResponse);
|
||||
return bestLocation?['formattedAddress'] as String?;
|
||||
}
|
||||
|
||||
/// Extrahiert Koordinaten
|
||||
static Map<String, double>? getCoordinates(Map<String, dynamic> 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<String, dynamic> 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<String, dynamic> 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user