commit 170825 daham Rechner

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

4
.envExample Normal file
View File

@ -0,0 +1,4 @@
APPWRITE_PROJECT_ID=<APPWRITE_PROJECT_ID>
APPWRITE_PROJECT_NAME=<Flutter Projects Name>
APPWRITE_PUBLIC_ENDPOINT=<YOUR_API_ENDPOINT/v1>
PTV_GEOLINK_API_KEY=<YOUR_GEOLINK_API_KEY>

View File

@ -10,6 +10,7 @@
analyzer: analyzer:
errors: errors:
unused_field: ignore unused_field: ignore
avoid_print: ignore
include: package:flutter_lints/flutter.yaml include: package:flutter_lints/flutter.yaml
linter: linter:

View File

@ -5,8 +5,8 @@ plugins {
} }
android { android {
ndkVersion = "25.1.8937393" ndkVersion = "27.0.12077973"
namespace = "io.appwrite.flutter" namespace = "com.example.flutter_new_tank_app310725"
compileSdk = flutter.compileSdkVersion compileSdk = flutter.compileSdkVersion
compileOptions { compileOptions {
@ -19,7 +19,7 @@ android {
} }
defaultConfig { defaultConfig {
applicationId = "io.appwrite.flutter" applicationId = "com.example.flutter_new_tank_app310725"
minSdk = flutter.minSdkVersion minSdk = flutter.minSdkVersion
targetSdk = flutter.targetSdkVersion targetSdk = flutter.targetSdkVersion

View File

@ -11,6 +11,8 @@
android:name="${applicationName}" android:name="${applicationName}"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="Tank Guru"> android:label="Tank Guru">
<service android:name="com.baseflow.geolocator.GeolocatorLocationService"
android:foregroundServiceType="location" />
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"

View File

@ -1,4 +1,4 @@
package io.appwrite.flutter package com.example.flutter_new_tank_app310725
import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.android.FlutterActivity

View File

@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip

View File

@ -479,7 +479,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
PRODUCT_BUNDLE_IDENTIFIER = io.appwrite.flutter; PRODUCT_BUNDLE_IDENTIFIER = com.example.flutter_new_tank_app310725;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
@ -497,7 +497,7 @@
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = io.appwrite.flutter.RunnerTests; PRODUCT_BUNDLE_IDENTIFIER = com.example.flutter_new_tank_app310725.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
@ -515,7 +515,7 @@
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = io.appwrite.flutter.RunnerTests; PRODUCT_BUNDLE_IDENTIFIER = com.example.flutter_new_tank_app310725.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
@ -531,7 +531,7 @@
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = io.appwrite.flutter.RunnerTests; PRODUCT_BUNDLE_IDENTIFIER = com.example.flutter_new_tank_app310725.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
@ -665,7 +665,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
PRODUCT_BUNDLE_IDENTIFIER = io.appwrite.flutter; PRODUCT_BUNDLE_IDENTIFIER = com.example.flutter_new_tank_app310725;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
@ -691,7 +691,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
PRODUCT_BUNDLE_IDENTIFIER = io.appwrite.flutter; PRODUCT_BUNDLE_IDENTIFIER = com.example.flutter_new_tank_app310725;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";

View File

@ -1,19 +1,24 @@
import 'pages/appwritetest/home.dart'; //import 'pages/appwritetest/home.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart';
import './utils/extensions/sample_routes.dart';
import './pages/login/login_view.dart';
class AppwriteApp extends StatelessWidget { class AppwriteApp extends StatelessWidget {
const AppwriteApp({super.key}); const AppwriteApp({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MaterialApp( return GetMaterialApp(
title: 'Appwrite StarterKit', title: 'Tank Guru AppWrite',
debugShowCheckedModeBanner: false, debugShowCheckedModeBanner: false,
theme: ThemeData( theme: ThemeData(
useMaterial3: true, useMaterial3: true,
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), colorScheme: ColorScheme.dark(),
), ),
home: const AppwriteStarterKit(), initialRoute: LoginPage.namedRoute,
getPages: SampleRouts.samplePages,
); );
} }
} }

View File

@ -1,3 +1,4 @@
import 'package:appwrite/models.dart' as models;
import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:appwrite/appwrite.dart'; import 'package:appwrite/appwrite.dart';
@ -8,13 +9,11 @@ import '../../data/models/project_info.dart';
/// ///
/// It provides a helper method to ping the server. /// It provides a helper method to ping the server.
class AppwriteRepository { class AppwriteRepository {
static const String pingPath = "/ping"; static const String pingPath = "/ping";
// static const String appwriteProjectId = String.fromEnvironment('APPWRITE_PROJECT_ID'); // static const String appwriteProjectId = String.fromEnvironment('APPWRITE_PROJECT_ID');
// static const String appwriteProjectName = String.fromEnvironment('APPWRITE_PROJECT_NAME'); // static const String appwriteProjectName = String.fromEnvironment('APPWRITE_PROJECT_NAME');
// static const String appwritePublicEndpoint = String.fromEnvironment('APPWRITE_PUBLIC_ENDPOINT'); // static const String appwritePublicEndpoint = String.fromEnvironment('APPWRITE_PUBLIC_ENDPOINT');
final Client _client = Client() final Client _client = Client()
.setProject(dotenv.get('APPWRITE_PROJECT_ID')) .setProject(dotenv.get('APPWRITE_PROJECT_ID'))
.setEndpoint(dotenv.get('APPWRITE_PUBLIC_ENDPOINT')); .setEndpoint(dotenv.get('APPWRITE_PUBLIC_ENDPOINT'));
@ -71,4 +70,27 @@ class AppwriteRepository {
String _getCurrentDate() { String _getCurrentDate() {
return DateFormat("MMM dd, HH:mm").format(DateTime.now()); return DateFormat("MMM dd, HH:mm").format(DateTime.now());
} }
Future<dynamic> logout() async =>
await _account.deleteSession(sessionId: 'current');
Future<models.Session> login(Map map) async =>
await _account.createEmailPasswordSession(
email: map['email'],
password: map['password'],
);
Future<models.Session> signUpAnonymus() async =>
await _account.createAnonymousSession();
Future<models.User> signup(Map map) async => _account.create(
userId: ID.unique(),
email: map['email'],
password: map['password'],
name: map['name'],
);
Future<models.User> get getCurrentUser => _account.get();
createTankStop(Map<String, dynamic> map) {}
} }

View File

@ -0,0 +1,97 @@
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:geolocator/geolocator.dart';
import 'package:http/http.dart' as http;
class LocationRepository {
static final LocationRepository _instance = LocationRepository._internal();
/// Singleton instance getter
factory LocationRepository() => _instance;
//Constructor???
LocationRepository._internal() {
//init for something
}
/// Überprüft, ob der Standortdienst aktiviert ist.
Future<bool> isLocationServiceEnabled() async {
return await Geolocator.isLocationServiceEnabled();
}
/// Fragt die Berechtigung für den Standort ab.
Future<LocationPermission> checkPermission() async {
return await Geolocator.checkPermission();
}
/// Fordert die Berechtigung für den Standort an.
Future<LocationPermission> requestPermission() async {
return await Geolocator.requestPermission();
}
/// Liefert die aktuelle Position des Geräts.
/// Wirft eine Exception, wenn der Dienst nicht aktiviert ist oder keine Berechtigung vorliegt.
Future<Position> getCurrentPosition() async {
bool serviceEnabled = await isLocationServiceEnabled();
if (!serviceEnabled) {
// Standortdienste sind nicht aktiviert.
return Future.error('Location services are disabled.');
}
LocationPermission permission = await checkPermission();
if (permission == LocationPermission.denied) {
permission = await requestPermission();
if (permission == LocationPermission.denied) {
// Berechtigungen sind verweigert.
return Future.error('Location permissions are denied');
}
}
if (permission == LocationPermission.deniedForever) {
// Berechtigungen sind dauerhaft verweigert.
return Future.error(
'Location permissions are permanently denied, we cannot request permissions.',
);
}
// Wenn alles in Ordnung ist, die Position zurückgeben.
return await Geolocator.getCurrentPosition();
}
Future<String> getNearbyLocation(Map map) async {
String locationOrt = '?';
var lat = map['lat'];
var lng = map['lng'];
// Hier kannst du die Logik hinzufügen, um den Standort zu verwenden, z.B.
String ptvGeoLink =
'https://api.myptv.com/geocoding/v1/locations/by-position/$lat/$lng?language=de&apiKey=${dotenv.get('PTV_GEOLINK_API_KEY')}';
final client = http.Client();
var response = await client.get(
Uri.parse(ptvGeoLink),
headers: {'Content-Type': 'application/json', 'charset': 'utf-8'},
);
//Response Data status
if (response.statusCode == 200) {
//Response is succsessful
Map<String, dynamic> data = json.decode(
utf8.decode(response.bodyBytes),
); //get response data
Map<String, dynamic> mapOfAddressfromPosition =
data['locations'][0]['address']; //get response address of position
if (mapOfAddressfromPosition.isNotEmpty) {
locationOrt =
'${mapOfAddressfromPosition['street'].toString()} ${mapOfAddressfromPosition['houseNumber'].toString()}, ${mapOfAddressfromPosition['postalCode'].toString()} ${mapOfAddressfromPosition['city'].toString()}';
}
} else {
debugPrint(response.statusCode.toString());
}
client.close();
return locationOrt;
}
}

View File

@ -1,10 +1,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import './app.dart'; import './app.dart';
import './utils/app_initializer.dart'; import './utils/app_initializer.dart';
void main() async { void main() async {
await dotenv.load(fileName: '.env');
await AppInitializer.initialize(); await AppInitializer.initialize();
runApp(AppwriteApp()); runApp(AppwriteApp());
} }

View File

@ -0,0 +1,211 @@
import 'package:appwrite/appwrite.dart';
import 'package:appwrite/models.dart' as models;
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../data/repository/appwrite_repository.dart';
import '../../utils/extensions/static_helper.dart';
import '../tank/tank_view.dart';
class LoginController extends GetxController {
final AppwriteRepository _authRepository = AppwriteRepository();
final isVisible = false.obs;
//Form Key
final formKey = GlobalKey<FormState>();
bool isFormValid = false;
final emailController = TextEditingController();
final passwordController = TextEditingController();
final nameController = TextEditingController();
@override
void onInit() {
super.onInit();
// Initialize any necessary data or state here
print('LoginController initialized');
}
@override
void onReady() {
super.onReady();
// This method is called when the controller is ready
print('LoginController is ready');
if (StaticHelper.hasData()) {
// If session ID exists, navigate to TankPage
print('Session ID found, navigating to TankPage');
goToTankPage();
} else {
// If no session ID, initialize the login controller
print('No session ID found, initializing LoginController');
}
}
@override
void onClose() {
// Clean up any resources or listeners here
emailController.dispose();
passwordController.dispose();
nameController.dispose();
// emailFocusNode.dispose();
// passwordFocusNode.dispose();
// nameFocusNode.dispose();
super.onClose();
}
void clearTextEditingController() {
emailController.clear();
passwordController.clear();
nameController.clear();
}
String? validateEmail(String? value) {
if (value == null || value.isEmpty) {
return 'Bitte geben Sie eine E-Mail-Adresse ein';
}
if (!GetUtils.isEmail(value)) {
return 'Bitte geben Sie eine gültige E-Mail-Adresse ein';
}
return null;
}
String? validatePassword(String? value) {
if (value == null || value.isEmpty) {
return 'Bitte geben Sie ein Passwort ein';
}
if (value.length < 8) {
return 'Das Passwort muss mindestens 8 Zeichen lang sein';
}
return null;
}
String? validateName(String? value) {
if (value == null || value.isEmpty) {
return 'Bitte geben Sie Ihren Namen ein';
}
if (value.length < 3) {
return 'Der Name muss mindestens 3 Zeichen lang sein';
}
return null;
}
var message = 'NoMessage!';
var isError = false;
// Registrierung
Future<void> register() async {
isError = false;
message = 'NoMessage!';
isFormValid = formKey.currentState!.validate();
if (!isFormValid) {
print('Formular ist ungültig');
return;
} else {
print('Formular ist gültig');
formKey.currentState!.save();
try {
await _authRepository.signup({
'email': emailController.text,
'password': passwordController.text,
'name': nameController.text,
}).then((models.User userValue) {
// GetStorage data storage
isVisible(false);
print(
'User was stored and Loggedin: ${userValue.name}, ${userValue.$id}, ${userValue.email}',
);
// Store session ID in GetStorage
StaticHelper.dataBox.write('userId', userValue.$id);
StaticHelper.dataBox.write('userName', userValue.name);
StaticHelper.dataBox.write('userEmail', userValue.email);
// Go to TankPage
goToTankPage();
message = 'Sie wurden registriert und Angemeldet!!';
StaticHelper.getMySnackeBar('Erfolg', message, Colors.green);
}).catchError((error) {
if (error is AppwriteException) {
// Handle specific Appwrite exceptions
print('Appwrite Fehler: ${error.message}');
message = 'Appwrite Fehler: ${error.message}';
isError = true;
} else {
// Handle other types of errors
message = 'Allgemeiner Fehler: $error';
print('Allgemeiner Fehler: $error');
isError = true;
}
message = 'Fehler bei der Registrierung: $error';
print('Fehler bei der Registrierung: $error');
//Clear GetStorage session ID
StaticHelper.removeFromStorage();
isError = true;
});
} catch (e) {
message = 'Fehler bei Registrierung: $e';
print('Fehler bei Registrierung: $e');
//Clear GetStorage session ID
StaticHelper.removeFromStorage();
isError = true;
}
if (isError) StaticHelper.getMySnackeBar('Fehler', message, Colors.red);
}
}
// Login
Future<void> login() async {
message = 'NoMessage!';
isError = false;
isFormValid = formKey.currentState!.validate();
if (!isFormValid) {
print('Formular ist ungültig');
return;
} else {
print('Formular ist gültig');
formKey.currentState!.save();
try {
await _authRepository.login({
'email': emailController.text,
'password': passwordController.text,
}).then((models.Session session) {
// Store session ID in GetStorage
StaticHelper.dataBox.write('userId', session.$id);
print('Session ID stored: ${StaticHelper.dataBox.read('sessinId')}');
print(
'Erfolgreich eingeloggt: ${session.$id}\n${session.providerUid}',
);
// Go to TankPage
goToTankPage();
StaticHelper.getMySnackeBar(
'Erfolg', 'Sie wurden Angemeldet!!', Colors.green);
}).catchError((error) {
if (error is AppwriteException) {
// Handle specific Appwrite exceptions
isError = true;
message = 'Appwrite Fehler: ${error.message}';
print('Appwrite Fehler: ${error.message}');
} else {
// Handle other types of errors
print('Allgemeiner Fehler: $error');
isError = true;
message = 'Allgemeiner Fehler: $error';
}
print('Fehler beim Login: $error');
isError = true;
message = 'Fehler beim Login: $error';
_authRepository.logout();
});
} catch (e) {
print('Fehler beim Login: $e');
isError = true;
message = 'Fehler beim Login: $e';
_authRepository.logout();
}
if (isError) StaticHelper.getMySnackeBar('Fehler', message, Colors.red);
}
}
void goToTankPage() {
Get.offAndToNamed(TankPage.namedRoute);
}
}

View File

@ -0,0 +1,162 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'login_controller.dart';
class LoginPage extends GetView<LoginController> {
static const namedRoute = '/login-page';
const LoginPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Container(
alignment: Alignment.center,
width: double.infinity,
height: double.infinity,
child: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
headerTextTankGuru(),
const SizedBox(height: 5),
headerTextDescription(),
const SizedBox(height: 10),
loadingImage(),
inputFields(),
],
),
),
),
),
);
}
//the structure of the login page
SizedBox loadingImage() {
return SizedBox(
width: 300,
height: 300,
child: ClipRRect(
borderRadius: BorderRadius.circular(40),
child: Image.asset('lib/images/gasolineGuru.jpg', fit: BoxFit.cover),
),
);
}
Text headerTextTankGuru() {
return Text(
'Tank GURU',
style: TextStyle(
fontSize: 35,
fontWeight: FontWeight.bold,
color: Colors.teal[600],
letterSpacing: 2,
),
);
}
Text headerTextDescription() {
return Text(
'Das ultimative Tanken',
style: TextStyle(fontSize: 20, color: Colors.grey[300]),
);
}
Widget inputFields() {
return Obx(
() => SizedBox(
width: 300,
child: Form(
key: controller.formKey,
autovalidateMode: AutovalidateMode.onUserInteraction,
child: Column(
children: [
SizedBox(height: 20),
TextFormField(
validator: (value) => controller.validateEmail(value),
keyboardType: TextInputType.emailAddress,
controller: controller.emailController,
decoration: InputDecoration(
labelText: 'Email',
border: OutlineInputBorder(),
),
),
SizedBox(height: 20),
TextFormField(
validator: (value) => controller.validatePassword(value),
keyboardType: TextInputType.visiblePassword,
controller: controller.passwordController,
decoration: InputDecoration(
labelText: 'Password',
border: OutlineInputBorder(),
),
obscureText: true,
),
if (controller.isVisible.value) ...[
SizedBox(height: 20),
TextFormField(
validator: (value) => controller.validateName(value),
keyboardType: TextInputType.name,
controller: controller.nameController,
decoration: InputDecoration(
labelText: 'Name',
border: OutlineInputBorder(),
),
),
],
SizedBox(height: 20),
SizedBox(
width: 300,
child: ElevatedButton(
onPressed: () {
controller.isVisible.value == false
? controller.login()
: controller.register();
},
child: controller.isVisible.value == false
? Text('Login')
: Text('Registrieren'),
),
),
SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Noch kein Konto? '),
GestureDetector(
onTap: () {
if (!controller.isVisible.value) {
controller.isVisible.value = true;
} else {
controller.isVisible.value = false;
}
},
child: controller.isVisible.value == false
? Text(
'Sign Up',
style: TextStyle(
color: Colors.teal,
fontWeight: FontWeight.bold,
decoration: TextDecoration.underline,
),
)
: Text(
'Sign In',
style: TextStyle(
color: Colors.teal,
fontWeight: FontWeight.bold,
decoration: TextDecoration.underline,
),
),
),
],
),
],
),
),
),
);
}
}

View File

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

View File

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

View File

@ -3,7 +3,9 @@ import 'dart:io';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:window_manager/window_manager.dart'; import 'package:window_manager/window_manager.dart';
import './extensions/http_overrides.dart';
/// A utility class for initializing the Flutter application. /// A utility class for initializing the Flutter application.
/// ///
@ -19,10 +21,15 @@ class AppInitializer {
_ensureInitialized(); _ensureInitialized();
await _setupWindowDimensions(); await _setupWindowDimensions();
await _setupDeviceOrientation(); await _setupDeviceOrientation();
//dotENV initial
await dotenv.load(fileName: '.env');
//Overrides initial
HttpOverrides.global = MyHttpOverrides();
} }
/// Ensures that Flutter bindings are initialized. /// Ensures that Flutter bindings are initialized.
static _ensureInitialized() { static _ensureInitialized() {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
} }

View File

@ -0,0 +1,29 @@
import 'package:flutter/material.dart';
var kTextStyle = TextStyle(fontSize: 20, color: Colors.grey.shade100);
var kTextStyleSub = TextStyle(fontSize: 16, color: Colors.grey.shade400);
var kColorEuroChart = Colors.red.shade500;
var kColorBenzinChart = Colors.yellow.shade500;
var kColorPerLiterChart = Colors.blueGrey.shade300;
var kChartDescriptionFontStyle = TextStyle(
color: Colors.grey.shade900,
fontWeight: FontWeight.bold,
fontSize: 16,
);
var kBarTitleStyle = TextStyle(
color: Colors.blue.shade900,
fontWeight: FontWeight.bold,
fontSize: 14,
);
var kInputDecorationDropDownMenueYear = const InputDecoration(
prefixIcon: Icon(Icons.date_range),
hintText: 'Jahresauswahl',
filled: true,
fillColor: Colors.black,
errorStyle: TextStyle(color: Colors.yellow),
);

View File

@ -0,0 +1,9 @@
import 'dart:io';
class MyHttpOverrides extends HttpOverrides{
@override
HttpClient createHttpClient(SecurityContext? context){
return super.createHttpClient(context)
..badCertificateCallback = (X509Certificate cert, String host, int port)=> true;
}
}

View File

@ -0,0 +1,20 @@
import 'package:get/get.dart';
import '../../pages/login/login_controller.dart';
import '../../pages/tank/tank_controller.dart';
class SampleBindings extends Bindings {
@override
void dependencies() {
// Define your dependencies here
Get.lazyPut<LoginController>(() => LoginController());
Get.lazyPut<TankController>(() => TankController());
// Get.lazyPut<TanklistController>(() => TanklistController(AuthRepository(AppWriteProvider())));
// Get.lazyPut<GraphController>(() => GraphController(AuthRepository(AppWriteProvider())));
// Get.lazyPut<TrackingController>(() => TrackingController(AuthRepository(AppWriteProvider())));
// Get.lazyPut<MapController>(() => MapController(AuthRepository(AppWriteProvider()), LocationRepository(LocationProvider())));
}
}

View File

@ -0,0 +1,44 @@
import 'package:get/get.dart';
import '../../pages/login/login_view.dart';
import '../../pages/tank/tank_view.dart';
import './sample_bindings.dart';
class SampleRouts {
static final sampleBindings = SampleBindings();
static List<GetPage<dynamic>> samplePages = [
GetPage(
name: LoginPage.namedRoute,
page: () => const LoginPage(),
binding: sampleBindings,
),
GetPage(
name: TankPage.namedRoute,
page: () => const TankPage(),
binding: sampleBindings,
),
// GetPage(
// name: TanklistPage.namedRoute,
// page: () => const TanklistPage(),
// binding: sampleBindings,
// ),
// GetPage(
// name: GraphPage.namedRoute,
// page: () => const GraphPage(),
// binding: sampleBindings,
// ),
// GetPage(
// name: TrackingPage.namedRoute,
// page: () => const TrackingPage(),
// binding: sampleBindings,
// ),
// GetPage(
// name: MapPage.namedRoute,
// page: () => const MapPage(),
// binding: SampleBindings(),
// ),
];
}

View File

@ -0,0 +1,89 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart';
import '../../data/models/tank_model.dart';
class StaticHelper {
static double sumMonatEuro = 0.0;
static double sumMonatLiter = 0.0;
static List staticSearchList = [];
static GetStorage dataBox = GetStorage('MyUserStorage');
static List listMonth = [
{'month': 'Jänner', 'value': '01'},
{'month': 'Februar', 'value': '02'},
{'month': 'März', 'value': '03'},
{'month': 'April', 'value': '04'},
{'month': 'Mai', 'value': '05'},
{'month': 'Juni', 'value': '06'},
{'month': 'Juli', 'value': '07'},
{'month': 'August', 'value': '08'},
{'month': 'September', 'value': '09'},
{'month': 'Oktober', 'value': '10'},
{'month': 'November', 'value': '11'},
{'month': 'Dezember', 'value': '12'},
];
static List staticListGetDaysInBetween(
List<AppWriteTankModel> list,
String monthValue,
int year,
) {
List<AppWriteTankModel> searchList = [];
List<DateTime> days = [];
var dateYear = year;
var auswahlMonat = int.parse(monthValue);
var startDate = DateTime(dateYear, auswahlMonat, 1);
var endDate = DateTime(dateYear, auswahlMonat + 1, 0);
//List of Days
for (int i = 0; i <= endDate.difference(startDate).inDays; i++) {
days.add(DateTime(startDate.year, startDate.month, startDate.day + i));
}
sumMonatEuro = 0.0;
sumMonatLiter = 0.0;
int entryCounter = 0;
for (var day in days) {
for (AppWriteTankModel model in list) {
int milliSecDate = (DateTime.parse(model.date)).microsecondsSinceEpoch;
if (milliSecDate == day.microsecondsSinceEpoch) {
entryCounter = entryCounter + 1;
model.mnIndexCount = entryCounter;
searchList.add(model);
var mnEuroGesamt =
double.parse(model.liters) * double.parse(model.pricePerLiter);
sumMonatEuro += mnEuroGesamt;
sumMonatLiter += double.parse(model.liters);
}
}
}
searchList.sort((a, b) {
final DateTime dateA = DateTime.parse(a.date);
final DateTime dateB = DateTime.parse(b.date);
return dateB.compareTo(dateA);
});
return staticSearchList = searchList;
}
static getMySnackeBar(String title, String message, Color backgroundColor) {
Get.snackbar(
title,
message,
backgroundColor: backgroundColor,
snackPosition: SnackPosition.BOTTOM,
duration: const Duration(seconds: 4),
);
}
static removeFromStorage() {
dataBox.remove('sessionId');
dataBox.remove('userId');
dataBox.remove('userName');
dataBox.remove('userEmail');
}
static bool hasData() {
return dataBox.hasData('sessionId');
}
}

View File

@ -67,7 +67,7 @@
331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; }; 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = "<group>"; }; 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = "<group>"; };
335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = "<group>"; }; 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = "<group>"; };
33CC10ED2044A3C60003C045 /* AppwriteStarterKit.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = appwrite_flutter_starter_kit.app; sourceTree = BUILT_PRODUCTS_DIR; }; 33CC10ED2044A3C60003C045 /* AppwriteStarterKit.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = tankguru_flutter_app_appwrite.app; sourceTree = BUILT_PRODUCTS_DIR; };
33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; }; 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = "<group>"; }; 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = "<group>"; };
33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; }; 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };

View File

@ -2,6 +2,14 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>NSLocationWhenInUseUsageDescription</key>
<string>This app needs access to your location to find nearby gas stations and services.</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>This app needs access to your location to track your position even when the app is in the background.</string>
<key>UIBackgroundModes</key>
<array>
<string>location</string>
</array>
<key>CFBundleDevelopmentRegion</key> <key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string> <string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>

View File

@ -1,4 +1,4 @@
name: appwrite_flutter_starter_kit name: tankguru_flutter_app_appwrite
description: "Appwrite StarterKit in Flutter" description: "Appwrite StarterKit in Flutter"
publish_to: "none" publish_to: "none"