Compare commits
2
Commits
366d74cd82
...
ca49bc7c0b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ca49bc7c0b | ||
|
|
84724ef10f |
Vendored
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Flutter Web (Chrome, Port 5000)",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"deviceId": "chrome",
|
||||
"toolArgs": [
|
||||
"--web-port",
|
||||
"5000"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
class EnvDirectus {
|
||||
static const String directusPublicEndpoint = 'https://directus.joshihomeserver.ipv64.net';
|
||||
static const String directusApiTokenBearer = '6mqGYbOCQAg7UAhIubb87wXcJVHTAsv9';
|
||||
static const String directusTankUsersRest = 'TankUsers';
|
||||
static const String directusTankStopRest = 'TankStopGuru01';
|
||||
}
|
||||
@@ -1,21 +1,20 @@
|
||||
import 'package:get/get.dart';
|
||||
import '../models/tank_model.dart';
|
||||
import '../models/tank_guru_directus_model.dart';
|
||||
import '../pages/edit_view.dart';
|
||||
import '../services/appwrite_service.dart';
|
||||
import '../services/directus_service.dart';
|
||||
|
||||
class DetailController extends GetxController {
|
||||
late TankModel tank;
|
||||
final appwriteService = AppwriteService();
|
||||
late TankGuruDirectusModel tank;
|
||||
final directusService = DirectusService();
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
tank = Get.arguments as TankModel;
|
||||
tank = Get.arguments as TankGuruDirectusModel;
|
||||
super.onInit();
|
||||
}
|
||||
|
||||
void deleteEntry() {
|
||||
appwriteService
|
||||
.deleteDocumentFromCollection(tank.szDocumentId)
|
||||
void deleteEntry() async {
|
||||
await directusService.deleteDocumentFromTable(tank.iD!)
|
||||
.then((_) {
|
||||
Get.back(
|
||||
result: 'deleted',
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import '../helper/helper.dart' as helper;
|
||||
import '../models/locationiq_model.dart';
|
||||
import '../models/tank_model.dart';
|
||||
import '../services/appwrite_service.dart';
|
||||
//import '../models/tank_model.dart';
|
||||
//import '../services/appwrite_service.dart';
|
||||
import '../models/tank_guru_directus_model.dart';
|
||||
import '../services/directus_service.dart';
|
||||
import '../services/geolocation_service.dart';
|
||||
import '../services/location_iq_service.dart';
|
||||
import '../pages/home_view.dart';
|
||||
|
||||
class EditController extends GetxController {
|
||||
final AppwriteService appwriteService = AppwriteService();
|
||||
final DirectusService directusService = DirectusService();
|
||||
|
||||
// Form controllers
|
||||
final dateController = TextEditingController();
|
||||
@@ -23,8 +26,9 @@ class EditController extends GetxController {
|
||||
final calculatedTotal = '0.00'.obs;
|
||||
final isLoadingLocation = false.obs;
|
||||
|
||||
TankModel? editingTankModel;
|
||||
TankGuruDirectusModel? editingTankModel;
|
||||
LocationIQ? currentLocationIQ;
|
||||
String currentUserIdentId = helper.kCurrentUserIdentId; // Use the current user identId from helper
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
@@ -32,7 +36,7 @@ class EditController extends GetxController {
|
||||
|
||||
// Check if we're editing or creating new
|
||||
if (Get.arguments != null) {
|
||||
editingTankModel = Get.arguments as TankModel;
|
||||
editingTankModel = Get.arguments as TankGuruDirectusModel;
|
||||
isNewEntry.value = false;
|
||||
_loadExistingData();
|
||||
} else {
|
||||
@@ -47,12 +51,12 @@ class EditController extends GetxController {
|
||||
}
|
||||
|
||||
void _loadExistingData() {
|
||||
dateController.text = editingTankModel!.szDate;
|
||||
odometerController.text = editingTankModel!.szOdometer;
|
||||
litersController.text = editingTankModel!.szLiters;
|
||||
pricePerLiterController.text = editingTankModel!.szPricePerLiter;
|
||||
locationController.text = editingTankModel!.szLocation;
|
||||
calculatedTotal.value = editingTankModel!.szPriceTotal;
|
||||
dateController.text = editingTankModel!.tankDate!;
|
||||
odometerController.text = editingTankModel!.odometer!;
|
||||
litersController.text = editingTankModel!.liter!;
|
||||
pricePerLiterController.text = editingTankModel!.pricePerLiter!;
|
||||
locationController.text = editingTankModel!.standort!;
|
||||
calculatedTotal.value = editingTankModel!.szPriceTotal!;
|
||||
}
|
||||
|
||||
void _setDefaultDate() {
|
||||
@@ -147,26 +151,26 @@ class EditController extends GetxController {
|
||||
isLoading.value = true;
|
||||
|
||||
try {
|
||||
final userId = await appwriteService.getCurrentUserId();
|
||||
if (userId == null) {
|
||||
final userId = currentUserIdentId;
|
||||
if (userId == '') {
|
||||
throw Exception('Benutzer nicht eingeloggt');
|
||||
}
|
||||
|
||||
final data = {
|
||||
'userId': userId,
|
||||
'date': dateController.text,
|
||||
'odometer': odometerController.text,
|
||||
'liters': litersController.text.replaceAll(',', '.'),
|
||||
'pricePerLiter': pricePerLiterController.text.replaceAll(',', '.'),
|
||||
'location': locationController.text,
|
||||
'UserIdentId': userId,
|
||||
'TankDate': dateController.text,
|
||||
'Odometer': odometerController.text,
|
||||
'Liter': litersController.text.replaceAll(',', '.'),
|
||||
'PricePerLiter': pricePerLiterController.text.replaceAll(',', '.'),
|
||||
'Standort': locationController.text,
|
||||
};
|
||||
|
||||
bool success;
|
||||
if (isNewEntry.value) {
|
||||
success = await appwriteService.createDocumentInCollection(data);
|
||||
success = await directusService.createDocumentInTable(data);
|
||||
} else {
|
||||
success = await appwriteService.updateDocumentInCollection(
|
||||
editingTankModel!.szDocumentId,
|
||||
success = await directusService.updateDocumentInTable(
|
||||
editingTankModel!.iD,
|
||||
data,
|
||||
);
|
||||
}
|
||||
@@ -181,7 +185,7 @@ class EditController extends GetxController {
|
||||
backgroundColor: Colors.green[100],
|
||||
colorText: Colors.green[900],
|
||||
);
|
||||
Get.offAllNamed(HomePage.namedRoute);
|
||||
Get.offAllNamed(HomePage.namedRoute, arguments: {'userIdentId': userId});
|
||||
} else {
|
||||
throw Exception('Speichern fehlgeschlagen');
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import 'package:get/get.dart';
|
||||
import '../models/tank_model.dart';
|
||||
//import '../models/tank_model.dart';
|
||||
import '../models/tank_guru_directus_model.dart';
|
||||
|
||||
class GraphController extends GetxController {
|
||||
final listTankModel = <TankModel>[].obs;
|
||||
final listTankModel = <TankGuruDirectusModel>[].obs;
|
||||
final sumYearKm = 0.0.obs;
|
||||
final sumYearLiters = 0.0.obs;
|
||||
final sumYearPrice = 0.0.obs;
|
||||
@@ -12,7 +13,7 @@ class GraphController extends GetxController {
|
||||
@override
|
||||
void onInit() {
|
||||
if (Get.arguments != null) {
|
||||
var args = Get.arguments as List<TankModel>;
|
||||
var args = Get.arguments as List<TankGuruDirectusModel>;
|
||||
listTankModel.addAll(args);
|
||||
_calcSumms();
|
||||
}
|
||||
@@ -36,23 +37,23 @@ class GraphController extends GetxController {
|
||||
|
||||
// Sort by date to calculate km correctly
|
||||
listTankModel.sort((a, b) {
|
||||
var dateA = DateTime.tryParse(a.szDate)!;
|
||||
var dateB = DateTime.tryParse(b.szDate)!;
|
||||
var dateA = DateTime.tryParse(a.tankDate!)!;
|
||||
var dateB = DateTime.tryParse(b.tankDate!)!;
|
||||
return dateA.compareTo(dateB);
|
||||
});
|
||||
|
||||
var firstOdometer = double.tryParse(listTankModel.first.szOdometer) ?? 0.0;
|
||||
var lastOdometer = double.tryParse(listTankModel.last.szOdometer) ?? 0.0;
|
||||
var firstOdometer = double.tryParse(listTankModel.first.odometer!) ?? 0.0;
|
||||
var lastOdometer = double.tryParse(listTankModel.last.odometer!) ?? 0.0;
|
||||
|
||||
sumYearKm.value = lastOdometer - firstOdometer;
|
||||
szTachostand.value = listTankModel.last.szOdometer;
|
||||
szTachostand.value = listTankModel.last.odometer!;
|
||||
|
||||
double totalLiters = 0.0;
|
||||
double totalPrice = 0.0;
|
||||
|
||||
for (var tank in listTankModel) {
|
||||
totalLiters += double.tryParse(tank.szLiters) ?? 0.0;
|
||||
totalPrice += double.tryParse(tank.szPriceTotal) ?? 0.0;
|
||||
totalLiters += double.tryParse(tank.liter!) ?? 0.0;
|
||||
totalPrice += double.tryParse(tank.szPriceTotal!) ?? 0.0;
|
||||
}
|
||||
|
||||
sumYearLiters.value = totalLiters;
|
||||
|
||||
@@ -1,21 +1,24 @@
|
||||
import 'package:flutter_tank_web_app/pages/gasstations_view.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../models/tank_model.dart';
|
||||
import '../models/tank_guru_directus_model.dart';
|
||||
import '../pages/detail_view.dart';
|
||||
import '../pages/edit_view.dart';
|
||||
import '../services/appwrite_service.dart';
|
||||
import '../pages/graph_view.dart';
|
||||
import '../pages/gasstations_view.dart';
|
||||
import '../services/directus_service.dart';
|
||||
|
||||
class HomeController extends GetxController {
|
||||
final isLoading = false.obs;
|
||||
final listTankModel = <TankModel>[].obs;
|
||||
final appwriteService = AppwriteService();
|
||||
//final listTankModel = <TankModel>[].obs;
|
||||
final listTankModel = <TankGuruDirectusModel>[].obs;
|
||||
//final appwriteService = AppwriteService();
|
||||
final directusService = DirectusService();
|
||||
final currentYear = DateTime.now().year.obs;
|
||||
final argumentMap = Get.arguments as Map<String, dynamic>?;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
_loadListDocument();
|
||||
_loadListDirectus();
|
||||
super.onInit();
|
||||
}
|
||||
|
||||
@@ -27,45 +30,46 @@ class HomeController extends GetxController {
|
||||
|
||||
void changeYear(int year) {
|
||||
currentYear.value = year;
|
||||
_loadListDocument();
|
||||
_loadListDirectus();
|
||||
update();
|
||||
}
|
||||
|
||||
Future<void> _loadListDocument() async {
|
||||
Future<void> _loadListDirectus() async {
|
||||
isLoading.value = true;
|
||||
if (listTankModel.isNotEmpty) {
|
||||
listTankModel.clear();
|
||||
}
|
||||
var userId = await appwriteService.getCurrentUserId();
|
||||
var userId = argumentMap?['userIdentId'] as String?;
|
||||
if (userId == null) {
|
||||
//User nicht eingeloggt, evtl. zur Login-Seite navigieren
|
||||
update();
|
||||
return;
|
||||
}
|
||||
var resultList = await appwriteService.getDocumentsFromCollection(userId);
|
||||
var resultList = await directusService.getTankGuruDataFromTable(userId);
|
||||
if (resultList.isEmpty) {
|
||||
//Dokumente erfolgreich geladen, hier können Sie die Liste verarbeiten
|
||||
print('Dokumente wurden nicht geladen: ${resultList.length}');
|
||||
} else {
|
||||
for (var doc in resultList) {
|
||||
var tankModel = TankModel.fromMap(doc.data);
|
||||
listTankModel.add(tankModel);
|
||||
}
|
||||
listTankModel(
|
||||
resultList
|
||||
.map((tankStopJson) => TankGuruDirectusModel.fromJson(tankStopJson))
|
||||
.toList(),
|
||||
);
|
||||
if (listTankModel.isNotEmpty) {
|
||||
var sortList = listTankModel.where((tank) {
|
||||
var year = (DateTime.tryParse(tank.szDate)!).year;
|
||||
var year = (DateTime.tryParse(tank.tankDate!)!).year;
|
||||
return year == currentYear.value;
|
||||
}).toList();
|
||||
listTankModel.clear();
|
||||
listTankModel.addAll(sortList);
|
||||
listTankModel.sort((a, b) {
|
||||
var dateA = DateTime.tryParse(a.szDate)!;
|
||||
var dateB = DateTime.tryParse(b.szDate)!;
|
||||
var dateA = DateTime.tryParse(a.tankDate!)!;
|
||||
var dateB = DateTime.tryParse(b.tankDate!)!;
|
||||
return dateB.compareTo(dateA);
|
||||
});
|
||||
for (var tank in listTankModel) {
|
||||
print(
|
||||
'SortTankModel: ${tank.szDate} - ${tank.szLiters}L - ${tank.szPricePerLiter}€/L - Total: ${tank.szPriceTotal}€',
|
||||
'SortTankModel: ${tank.tankDate!} - ${tank.liter}L - ${tank.pricePerLiter}€/L - Total: ${tank.szPriceTotal}€',
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -75,27 +79,18 @@ class HomeController extends GetxController {
|
||||
}
|
||||
|
||||
Future<void> logout() async {
|
||||
var logoutSuccess = await appwriteService.logout();
|
||||
if (logoutSuccess) {
|
||||
Get.snackbar(
|
||||
'Logout erfolgreich',
|
||||
'Sie wurden abgemeldet.',
|
||||
snackPosition: SnackPosition.BOTTOM,
|
||||
);
|
||||
Get.offAllNamed('/login-page');
|
||||
} else {
|
||||
Get.snackbar(
|
||||
'Logout fehlgeschlagen',
|
||||
'Bitte versuchen Sie es erneut.',
|
||||
snackPosition: SnackPosition.BOTTOM,
|
||||
);
|
||||
}
|
||||
Get.snackbar(
|
||||
'Logout erfolgreich',
|
||||
'Sie wurden abgemeldet.',
|
||||
snackPosition: SnackPosition.BOTTOM,
|
||||
);
|
||||
Get.offAllNamed('/login-page');
|
||||
}
|
||||
|
||||
Future<void> viewTankDetails(TankModel tank) async {
|
||||
Future<void> viewTankDetails(TankGuruDirectusModel tank) async {
|
||||
var result = await Get.toNamed(DetailPage.namedRoute, arguments: tank);
|
||||
if (result == 'deleted') {
|
||||
_loadListDocument();
|
||||
_loadListDirectus();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import '../helper/helper.dart' as helper;
|
||||
import '../models/tank_user_model.dart';
|
||||
import '../pages/home_view.dart';
|
||||
import '../pages/signin_view.dart';
|
||||
import '../services/appwrite_service.dart';
|
||||
import '../services/directus_service.dart';
|
||||
import '../helper/password_helper.dart';
|
||||
|
||||
class LoginController extends GetxController {
|
||||
final emailController = TextEditingController();
|
||||
final passwordController = TextEditingController();
|
||||
final appwriteService = AppwriteService();
|
||||
final directusService = DirectusService();
|
||||
|
||||
@override
|
||||
void onReady() {}
|
||||
@@ -18,29 +21,57 @@ class LoginController extends GetxController {
|
||||
passwordController.dispose();
|
||||
}
|
||||
|
||||
void login() async {
|
||||
void newLoginDirectus() async {
|
||||
var email = emailController.text;
|
||||
var password = passwordController.text;
|
||||
// Hier können Sie die Login-Logik implementieren
|
||||
print('Login mit E-Mail: $email, Passwort: $password');
|
||||
var loginSuccess = await appwriteService.login(email, password);
|
||||
if (loginSuccess) {
|
||||
var result = await directusService.getTankUserList();
|
||||
if(result.isEmpty) {
|
||||
Get.snackbar(
|
||||
'Login fehlgeschlagen',
|
||||
'Fehler beim Abrufen der Benutzerdaten. Bitte versuchen Sie es später erneut.',
|
||||
snackPosition: SnackPosition.BOTTOM,
|
||||
);
|
||||
return;
|
||||
}
|
||||
var tankUserList = result.map((userJson) => TankUser.fromJson(userJson)).toList();
|
||||
var identId = tankUserList.firstWhereOrNull((user) => user.email == email)?.userIdentId ?? '';
|
||||
if(identId.isEmpty) {
|
||||
Get.snackbar(
|
||||
'Login fehlgeschlagen',
|
||||
'Benutzer mit dieser E-Mail-Adresse wurde nicht gefunden.',
|
||||
snackPosition: SnackPosition.BOTTOM,
|
||||
);
|
||||
return;
|
||||
}
|
||||
var storedHash = tankUserList.firstWhereOrNull((user) => user.email == email)?.passwordHash ?? '';
|
||||
if(storedHash.isEmpty) {
|
||||
Get.snackbar(
|
||||
'Login fehlgeschlagen',
|
||||
'Kein gespeicherter Passwort-Hash für diesen Benutzer gefunden.',
|
||||
snackPosition: SnackPosition.BOTTOM,
|
||||
);
|
||||
return;
|
||||
}
|
||||
var isPassCorrect = await PasswordHelper.verifyPassword(password, storedHash);
|
||||
if(isPassCorrect && identId.isNotEmpty) {
|
||||
Get.snackbar(
|
||||
'Login erfolgreich',
|
||||
'Willkommen zurück!',
|
||||
snackPosition: SnackPosition.BOTTOM,
|
||||
);
|
||||
Get.offAndToNamed(HomePage.namedRoute);
|
||||
print('Login erfolgreich für Benutzer mit IdentId: $identId');
|
||||
helper.kCurrentUserIdentId = identId; // Set the current user identId
|
||||
Get.offAndToNamed(HomePage.namedRoute, arguments: {'userIdentId': identId});
|
||||
} else {
|
||||
Get.snackbar(
|
||||
'Login fehlgeschlagen',
|
||||
'Bitte überprüfen Sie Ihre E-Mail und Ihr Passwort und versuchen Sie es erneut.',
|
||||
'Bitte überprüfen Sie Ihre E-Mail und Ihr Passwort und versuchen Sie es erneut\noder registrieren Sie sich, wenn Sie noch kein Konto haben.',
|
||||
snackPosition: SnackPosition.BOTTOM,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void goToSignInPage() {
|
||||
Get.offAndToNamed(SigninPage.namedRoute);
|
||||
Get.offAndToNamed(SigninPage.namedRoute, arguments: {'userIdentId': ''});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import '../pages/home_view.dart';
|
||||
import '../pages/login_view.dart';
|
||||
import '../services/appwrite_service.dart';
|
||||
import '../services/directus_service.dart';
|
||||
|
||||
class SigninController extends GetxController {
|
||||
final userNameController = TextEditingController();
|
||||
final emailController = TextEditingController();
|
||||
final passwordController = TextEditingController();
|
||||
final appwriteService = AppwriteService();
|
||||
final cityController = TextEditingController();
|
||||
final countryController = TextEditingController();
|
||||
final directusService = DirectusService();
|
||||
late String userIdentId = Get.arguments['userIdentId'] ?? '';
|
||||
|
||||
@override
|
||||
void onReady() {}
|
||||
@@ -18,6 +23,8 @@ class SigninController extends GetxController {
|
||||
userNameController.dispose();
|
||||
emailController.dispose();
|
||||
passwordController.dispose();
|
||||
cityController.dispose();
|
||||
countryController.dispose();
|
||||
}
|
||||
|
||||
void register() async {
|
||||
@@ -27,16 +34,15 @@ class SigninController extends GetxController {
|
||||
var userName = userNameController.text;
|
||||
var email = emailController.text;
|
||||
var password = passwordController.text;
|
||||
var city = cityController.text; // Hier können Sie die Stadt aus den Eingaben abrufen, falls erforderlich
|
||||
var country = countryController.text; // Hier können Sie das Land aus den Eingaben abrufen, falls erforderlich
|
||||
// Testausgabe
|
||||
print(
|
||||
'Registrieren mit Benutzername: $userName, E-Mail: $email, Passwort: $password',
|
||||
);
|
||||
//appwrite Registrierung und login Logik hier einfügen
|
||||
var registrationSuccess = await appwriteService.register(
|
||||
userName,
|
||||
email,
|
||||
password,
|
||||
'Registrieren mit Benutzername: $userName, E-Mail: $email, Passwort: $password, Stadt: $city, Land: $country',
|
||||
);
|
||||
//directus Registrierung und login Logik hier einfügen
|
||||
bool registrationSuccess = await directusService.register(userName, email, password, city, country);
|
||||
|
||||
//Nach erfolgreicher Registrierung Meldung und zur Startseite navigieren
|
||||
if (registrationSuccess) {
|
||||
Get.snackbar(
|
||||
@@ -44,7 +50,8 @@ class SigninController extends GetxController {
|
||||
'Willkommen, $userName!',
|
||||
snackPosition: SnackPosition.BOTTOM,
|
||||
);
|
||||
Get.offAndToNamed(HomePage.namedRoute);
|
||||
userIdentId = base64Encode(utf8.encode('$email:$password'));
|
||||
Get.offAndToNamed(HomePage.namedRoute, arguments: {'userIdentId': userIdentId});
|
||||
} else {
|
||||
Get.snackbar(
|
||||
'Registrierung fehlgeschlagen',
|
||||
|
||||
@@ -12,3 +12,5 @@ String getCurrentDate(DateTime date) {
|
||||
var kListDropDownYear = <int>[
|
||||
for (var year = DateTime.now().year; year >= 2020; year--) year
|
||||
];
|
||||
|
||||
var kCurrentUserIdentId = '';
|
||||
@@ -0,0 +1,120 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:cryptography/cryptography.dart';
|
||||
|
||||
class PasswordHelper {
|
||||
static const int _saltLength = 16;
|
||||
static const int _hashLength = 32;
|
||||
|
||||
// 19 MiB, laut aktueller cryptography-Doku ein sinnvoller Startwert.
|
||||
static const int _memory = 19 * 1024;
|
||||
static const int _iterations = 2;
|
||||
static const int _parallelism = 1;
|
||||
|
||||
static final Argon2id _algorithm = Argon2id(
|
||||
memory: _memory,
|
||||
iterations: _iterations,
|
||||
parallelism: _parallelism,
|
||||
hashLength: _hashLength,
|
||||
);
|
||||
|
||||
/// Erstellt einen neuen Passwort-Hash inklusive zufälligem Salt.
|
||||
///
|
||||
/// Rückgabeformat:
|
||||
/// argon2id$memory$iterations$parallelism$saltBase64$hashBase64
|
||||
static Future<String> hashPassword(String password) async {
|
||||
final salt = _generateSalt(_saltLength);
|
||||
|
||||
final secretKey = await _algorithm.deriveKeyFromPassword(
|
||||
password: password,
|
||||
nonce: salt,
|
||||
);
|
||||
|
||||
final hashBytes = await secretKey.extractBytes();
|
||||
|
||||
final saltBase64 = base64UrlEncode(salt);
|
||||
final hashBase64 = base64UrlEncode(hashBytes);
|
||||
|
||||
return 'argon2id'
|
||||
'\$$_memory'
|
||||
'\$$_iterations'
|
||||
'\$$_parallelism'
|
||||
'\$$saltBase64'
|
||||
'\$$hashBase64';
|
||||
}
|
||||
|
||||
/// Prüft ein Klartext-Passwort gegen den gespeicherten Hash.
|
||||
static Future<bool> verifyPassword(
|
||||
String password,
|
||||
String storedHash,
|
||||
) async {
|
||||
try {
|
||||
final parts = storedHash.split('\$');
|
||||
|
||||
if (parts.length != 6) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (parts[0] != 'argon2id') {
|
||||
return false;
|
||||
}
|
||||
|
||||
final memory = int.parse(parts[1]);
|
||||
final iterations = int.parse(parts[2]);
|
||||
final parallelism = int.parse(parts[3]);
|
||||
|
||||
final salt = base64Url.decode(parts[4]);
|
||||
final expectedHash = base64Url.decode(parts[5]);
|
||||
|
||||
final algorithm = Argon2id(
|
||||
memory: memory,
|
||||
iterations: iterations,
|
||||
parallelism: parallelism,
|
||||
hashLength: expectedHash.length,
|
||||
);
|
||||
|
||||
final secretKey = await algorithm.deriveKeyFromPassword(
|
||||
password: password,
|
||||
nonce: salt,
|
||||
);
|
||||
|
||||
final calculatedHash = await secretKey.extractBytes();
|
||||
|
||||
return _constantTimeEquals(
|
||||
calculatedHash,
|
||||
expectedHash,
|
||||
);
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Erzeugt kryptographisch zufälligen Salt.
|
||||
static List<int> _generateSalt(int length) {
|
||||
final random = Random.secure();
|
||||
|
||||
return List<int>.generate(
|
||||
length,
|
||||
(_) => random.nextInt(256),
|
||||
);
|
||||
}
|
||||
|
||||
/// Vergleich ohne vorzeitigen Abbruch.
|
||||
static bool _constantTimeEquals(
|
||||
List<int> a,
|
||||
List<int> b,
|
||||
) {
|
||||
if (a.length != b.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var difference = 0;
|
||||
|
||||
for (var i = 0; i < a.length; i++) {
|
||||
difference |= a[i] ^ b[i];
|
||||
}
|
||||
|
||||
return difference == 0;
|
||||
}
|
||||
}
|
||||
@@ -22,9 +22,9 @@ class ChartData {
|
||||
|
||||
for (int i = 0; i < tankList.length; i++) {
|
||||
final tank = tankList[i];
|
||||
final liters = double.tryParse(tank.szLiters) ?? 0.0;
|
||||
final liters = double.tryParse(tank.liter) ?? 0.0;
|
||||
final priceTotal = double.tryParse(tank.szPriceTotal) ?? 0.0;
|
||||
final pricePerLiter = double.tryParse(tank.szPricePerLiter) ?? 0.0;
|
||||
final pricePerLiter = double.tryParse(tank.pricePerLiter) ?? 0.0;
|
||||
|
||||
points.add(
|
||||
ChartDataPoint(
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
|
||||
|
||||
class TankGuruDirectusModel {
|
||||
int? iD;
|
||||
String? userIdentId;
|
||||
String? tankDate;
|
||||
String? odometer;
|
||||
String? liter;
|
||||
String? pricePerLiter;
|
||||
String? standort;
|
||||
String? createdAt;
|
||||
String? szPriceTotal;
|
||||
|
||||
TankGuruDirectusModel(
|
||||
{this.iD,
|
||||
this.userIdentId,
|
||||
this.tankDate,
|
||||
this.odometer,
|
||||
this.liter,
|
||||
this.pricePerLiter,
|
||||
this.standort,
|
||||
this.createdAt,
|
||||
this.szPriceTotal});
|
||||
|
||||
TankGuruDirectusModel.fromJson(Map<String, dynamic> json) {
|
||||
iD = json['ID'];
|
||||
userIdentId = json['UserIdentId'];
|
||||
tankDate = json['TankDate'];
|
||||
odometer = json['Odometer'];
|
||||
liter = json['Liter'];
|
||||
pricePerLiter = json['PricePerLiter'];
|
||||
standort = json['Standort'];
|
||||
createdAt = json['CreatedAt'];
|
||||
// Berechnung des Gesamtpreises
|
||||
if (liter != null && pricePerLiter != null) {
|
||||
double literValue = double.tryParse(liter!) ?? 0.0;
|
||||
double pricePerLiterValue = double.tryParse(pricePerLiter!) ?? 0.0;
|
||||
szPriceTotal = (literValue * pricePerLiterValue).toStringAsFixed(3);
|
||||
} else {
|
||||
szPriceTotal = '0.00';
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data['ID'] = iD;
|
||||
data['UserIdentId'] = userIdentId;
|
||||
data['TankDate'] = tankDate;
|
||||
data['Odometer'] = odometer;
|
||||
data['Liter'] = liter;
|
||||
data['PricePerLiter'] = pricePerLiter;
|
||||
data['Standort'] = standort;
|
||||
data['CreatedAt'] = createdAt;
|
||||
data['SzPriceTotal'] = szPriceTotal;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
class TankUser {
|
||||
int? iD;
|
||||
String? userIdentId;
|
||||
String? userName;
|
||||
String? city;
|
||||
String? country;
|
||||
String? email;
|
||||
String? passwordHash;
|
||||
String? createdAt;
|
||||
|
||||
TankUser(
|
||||
{this.iD,
|
||||
this.userIdentId,
|
||||
this.userName,
|
||||
this.city,
|
||||
this.country,
|
||||
this.email,
|
||||
this.passwordHash,
|
||||
this.createdAt});
|
||||
|
||||
TankUser.fromJson(Map<String, dynamic> json) {
|
||||
iD = json['ID'];
|
||||
userIdentId = json['UserIdentId'];
|
||||
userName = json['UserName'];
|
||||
city = json['City'];
|
||||
country = json['Country'];
|
||||
email = json['Email'];
|
||||
passwordHash = json['PasswordHash'];
|
||||
createdAt = json['CreatedAt'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data['ID'] = iD;
|
||||
data['UserIdentId'] = userIdentId;
|
||||
data['UserName'] = userName;
|
||||
data['City'] = city;
|
||||
data['Country'] = country;
|
||||
data['Email'] = email;
|
||||
data['PasswordHash'] = passwordHash;
|
||||
data['CreatedAt'] = createdAt;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@@ -54,8 +54,8 @@ class DetailPage extends GetView<DetailController> {
|
||||
DetailStatWidget(
|
||||
icon: Icons.location_on,
|
||||
label: 'Standort',
|
||||
value: detailCtrl.tank.szLocation.isNotEmpty
|
||||
? detailCtrl.tank.szLocation
|
||||
value: detailCtrl.tank.standort!.isNotEmpty
|
||||
? detailCtrl.tank.standort!
|
||||
: 'Nicht angegeben',
|
||||
iconColor: Colors.red,
|
||||
),
|
||||
@@ -63,7 +63,9 @@ class DetailPage extends GetView<DetailController> {
|
||||
DetailStatWidget(
|
||||
icon: Icons.calendar_today,
|
||||
label: 'Datum',
|
||||
value: detailCtrl.tank.szDate,
|
||||
value: detailCtrl.tank.tankDate!.isNotEmpty
|
||||
? detailCtrl.tank.tankDate!
|
||||
: 'Nicht angegeben',
|
||||
iconColor: Colors.blueGrey,
|
||||
),
|
||||
],
|
||||
@@ -78,7 +80,7 @@ class DetailPage extends GetView<DetailController> {
|
||||
DetailStatWidget(
|
||||
icon: Icons.local_gas_station,
|
||||
label: 'Liter getankt',
|
||||
value: '${detailCtrl.tank.szLiters} L',
|
||||
value: '${detailCtrl.tank.liter!} L',
|
||||
iconColor: Colors.orange,
|
||||
valueSize: 24,
|
||||
valueWeight: FontWeight.bold,
|
||||
@@ -90,7 +92,7 @@ class DetailPage extends GetView<DetailController> {
|
||||
child: DetailStatWidget(
|
||||
icon: Icons.euro,
|
||||
label: 'Preis pro Liter',
|
||||
value: '${detailCtrl.tank.szPricePerLiter}€',
|
||||
value: '${detailCtrl.tank.pricePerLiter!}€',
|
||||
iconColor: Colors.green,
|
||||
),
|
||||
),
|
||||
@@ -99,7 +101,7 @@ class DetailPage extends GetView<DetailController> {
|
||||
child: DetailStatWidget(
|
||||
icon: Icons.receipt,
|
||||
label: 'Gesamtpreis',
|
||||
value: '${detailCtrl.tank.szPriceTotal}€',
|
||||
value: '${detailCtrl.tank.szPriceTotal!}€',
|
||||
iconColor: Colors.green[700]!,
|
||||
valueWeight: FontWeight.bold,
|
||||
),
|
||||
@@ -118,7 +120,7 @@ class DetailPage extends GetView<DetailController> {
|
||||
DetailStatWidget(
|
||||
icon: Icons.speed,
|
||||
label: 'Kilometerstand',
|
||||
value: '${detailCtrl.tank.szOdometer} km',
|
||||
value: '${detailCtrl.tank.odometer!} km',
|
||||
iconColor: Colors.blue,
|
||||
valueSize: 24,
|
||||
valueWeight: FontWeight.bold,
|
||||
|
||||
@@ -144,7 +144,7 @@ class HomePage extends GetView<HomeController> {
|
||||
CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
tank.szDate,
|
||||
tank.tankDate!,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight:
|
||||
@@ -153,7 +153,7 @@ class HomePage extends GetView<HomeController> {
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
'${tank.szLiters} Liter',
|
||||
'${tank.liter!} Liter',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.grey[600],
|
||||
@@ -198,12 +198,12 @@ class HomePage extends GetView<HomeController> {
|
||||
_buildInfoChip(
|
||||
icon: Icons.euro,
|
||||
label: 'Preis/L',
|
||||
value: '${tank.szPricePerLiter}€',
|
||||
value: '${tank.pricePerLiter!}€',
|
||||
),
|
||||
_buildInfoChip(
|
||||
icon: Icons.speed,
|
||||
label: 'Km-Stand',
|
||||
value: '${tank.szOdometer} km',
|
||||
value: '${tank.odometer!} km',
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -24,7 +24,7 @@ class LoginPage extends GetView<LoginController> {
|
||||
color: Colors.blue.shade100,
|
||||
child: SingleChildScrollView(
|
||||
child: MyLoginWidget(
|
||||
onButtonPressed: () => logCtrl.login(),
|
||||
onButtonPressed: () => logCtrl.newLoginDirectus(),
|
||||
signInOnTab: () => logCtrl.goToSignInPage(),
|
||||
logCtrl: logCtrl,
|
||||
),
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
import 'dart:convert';
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
import '../config/env_directus.dart';
|
||||
import '../helper/password_helper.dart';
|
||||
|
||||
|
||||
class DirectusService {
|
||||
static final String endpoint = EnvDirectus.directusPublicEndpoint;
|
||||
static final String apiTokenBearer = EnvDirectus.directusApiTokenBearer;
|
||||
static final String tankUsersRest = EnvDirectus.directusTankUsersRest;
|
||||
static final String tankStopRest = EnvDirectus.directusTankStopRest;
|
||||
|
||||
static Map<String, String> get headers => {
|
||||
'Authorization': 'Bearer $apiTokenBearer',
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
|
||||
Future<List<dynamic>> getTankUserList() async {
|
||||
var client = http.Client();
|
||||
String apiUrl = '$endpoint/items/$tankUsersRest';
|
||||
var response = await client.get(Uri.parse(apiUrl), headers: headers);
|
||||
if(response.statusCode != 200) {
|
||||
print('Fehler beim Abrufen der TankUser-Liste: ${response.statusCode}');
|
||||
client.close();
|
||||
return [];
|
||||
}
|
||||
final json = jsonDecode(
|
||||
utf8.decode(response.bodyBytes),
|
||||
) as Map<String, dynamic>;
|
||||
//print('$json}');
|
||||
client.close();
|
||||
return json['data'] as List<dynamic>;
|
||||
}
|
||||
|
||||
Future<List<dynamic>> getTankGuruDataFromTable(String userIdentId) async {
|
||||
var client = http.Client();
|
||||
String apiUrl = '$endpoint/items/$tankStopRest?filter[UserIdentId][_eq]=$userIdentId';
|
||||
var response = await client.get(Uri.parse(apiUrl), headers: headers);
|
||||
if(response.statusCode != 200) {
|
||||
print('Fehler beim Abrufen der Dokumente aus der Collection: ${response.statusCode}');
|
||||
client.close();
|
||||
return [];
|
||||
}
|
||||
final json = jsonDecode(
|
||||
utf8.decode(response.bodyBytes),
|
||||
) as Map<String, dynamic>;
|
||||
//print('$json}');
|
||||
client.close();
|
||||
return json['data'] as List<dynamic>;
|
||||
}
|
||||
|
||||
Future<void> deleteDocumentFromTable(int iD) async {
|
||||
var client = http.Client();
|
||||
String apiUrl = '$endpoint/items/$tankStopRest/$iD';
|
||||
var response = await client.delete(Uri.parse(apiUrl), headers: headers);
|
||||
if(response.statusCode < 200 || response.statusCode >= 300) {
|
||||
print('Fehler beim Löschen des Dokuments aus der Collection: ${response.statusCode}');
|
||||
client.close();
|
||||
throw Exception('Fehler beim Löschen des Dokuments aus der Collection: ${response.statusCode}');
|
||||
}
|
||||
client.close();
|
||||
}
|
||||
|
||||
Future<bool> createDocumentInTable(Map<String, String> data) async {
|
||||
return await http.post(
|
||||
Uri.parse('$endpoint/items/$tankStopRest'),
|
||||
headers: headers,
|
||||
body: jsonEncode(data),
|
||||
).then((response) {
|
||||
if (response.statusCode == 200 || response.statusCode == 201) {
|
||||
print('Dokument erfolgreich erstellt: ${response.body}');
|
||||
return true;
|
||||
} else {
|
||||
print('Fehler beim Erstellen des Dokuments: ${response.statusCode} - ${response.body}');
|
||||
return false;
|
||||
}
|
||||
}).catchError((error) {
|
||||
print('Fehler beim Erstellen des Dokuments: $error');
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
Future<bool> updateDocumentInTable(int? iD, Map<String, String> data) async {
|
||||
return iD != null
|
||||
? http.patch(
|
||||
Uri.parse('$endpoint/items/$tankStopRest/$iD'),
|
||||
headers: headers,
|
||||
body: jsonEncode(data),
|
||||
).then((response) {
|
||||
if (response.statusCode == 200) {
|
||||
print('Dokument erfolgreich aktualisiert: ${response.body}');
|
||||
return true;
|
||||
} else {
|
||||
print('Fehler beim Aktualisieren des Dokuments: ${response.statusCode} - ${response.body}');
|
||||
return false;
|
||||
}
|
||||
}).catchError((error) {
|
||||
print('Fehler beim Aktualisieren des Dokuments: $error');
|
||||
return false;
|
||||
})
|
||||
: Future.value(false);
|
||||
}
|
||||
|
||||
Future<bool> register(String userName, String email, String password, String city, String country) async {
|
||||
var client = http.Client();
|
||||
String apiUrl = '$endpoint/items/$tankUsersRest';
|
||||
Map<String, String> data = {
|
||||
'UserIdentId': base64Encode(utf8.encode('$email:$password')),
|
||||
'UserName': userName,
|
||||
'City': city,
|
||||
'Country': country,
|
||||
'Email': email,
|
||||
'PasswordHash': await PasswordHelper.hashPassword(password),
|
||||
};
|
||||
var response = await client.post(
|
||||
Uri.parse(apiUrl),
|
||||
headers: headers,
|
||||
body: jsonEncode(data),
|
||||
);
|
||||
if (response.statusCode == 200 || response.statusCode == 201) {
|
||||
print('Benutzer erfolgreich registriert: ${response.body}');
|
||||
client.close();
|
||||
return true;
|
||||
} else {
|
||||
print('Fehler bei der Registrierung des Benutzers: ${response.statusCode} - ${response.body}');
|
||||
client.close();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../models/tank_model.dart';
|
||||
//import '../models/tank_model.dart';
|
||||
import '../models/tank_guru_directus_model.dart';
|
||||
|
||||
class DetailHeaderWidget extends StatelessWidget {
|
||||
final TankModel tank;
|
||||
final TankGuruDirectusModel tank;
|
||||
|
||||
const DetailHeaderWidget({super.key, required this.tank});
|
||||
|
||||
@@ -37,7 +38,7 @@ class DetailHeaderWidget extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
tank.szDate,
|
||||
tank.tankDate!,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 20,
|
||||
|
||||
@@ -100,6 +100,30 @@ class MySigninWidget extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 50.0),
|
||||
child: TextField(
|
||||
controller: signCtrl.cityController,
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
decoration: InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
labelText: 'City',
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 50.0),
|
||||
child: TextField(
|
||||
controller: signCtrl.countryController,
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
decoration: InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
labelText: 'Country',
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 30),
|
||||
ElevatedButton(
|
||||
style: ButtonStyle(
|
||||
|
||||
+14
-158
@@ -1,14 +1,6 @@
|
||||
# Generated by pub
|
||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||
packages:
|
||||
appwrite:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: appwrite
|
||||
sha256: "3e1f618c8f75bafa49ef7b1b445f64c53cf4620a195443f4d119bbc95a666d0a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "14.0.0"
|
||||
args:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -65,14 +57,6 @@ 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:
|
||||
@@ -81,6 +65,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.7"
|
||||
cryptography:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: cryptography
|
||||
sha256: "3eda3029d34ec9095a27a198ac9785630fe525c0eb6a49f3d575272f8e792ef0"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.9.0"
|
||||
cupertino_icons:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -97,22 +89,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.11"
|
||||
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:
|
||||
@@ -163,22 +139,6 @@ packages:
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_web_auth_2:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_web_auth_2
|
||||
sha256: "4d3d2fd3d26bf1a26b3beafd4b4b899c0ffe10dc99af25abc58ffe24e991133c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.2"
|
||||
flutter_web_auth_2_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_web_auth_2_platform_interface
|
||||
sha256: e8669e262005a8354389ba2971f0fc1c36188481234ff50d013aaf993f30f739
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.0"
|
||||
flutter_web_plugins:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
@@ -356,10 +316,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
sha256: "31bd099b47c10cd1aeb55146a2d46ce0277630ecef3f7dae54ad7873f36696cd"
|
||||
sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.12.20"
|
||||
version: "0.12.18"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -372,10 +332,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: "307249ce4ff29d58a18e97f6345f539382eb9c9c29ecda628900f31de0443dd9"
|
||||
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.19.0"
|
||||
version: "1.17.0"
|
||||
native_toolchain_c:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -545,10 +505,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: "2a122cbe059f8b610d3a5415f42e255b6c17b1f21eee1d960f31080237fb4f11"
|
||||
sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.12"
|
||||
version: "0.7.9"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -557,78 +517,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
universal_io:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: universal_io
|
||||
sha256: f63cbc48103236abf48e345e07a03ce5757ea86285ed313a6a032596ed9301e2
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.1"
|
||||
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: "767344bf3063897b5cf0db830e94f904528e6dd50a6dfaf839f0abf509009611"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.3.28"
|
||||
url_launcher_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_ios
|
||||
sha256: cfde38aa257dae62ffe79c87fab20165dfdf6988c1d31b58ebf59b9106062aad
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.3.6"
|
||||
url_launcher_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_linux
|
||||
sha256: d5e14138b3bc193a0f63c10a53c94b91d399df0512b1f29b94a043db7482384a
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.2"
|
||||
url_launcher_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_macos
|
||||
sha256: "368adf46f71ad3c21b8f06614adb38346f193f3a59ba8fe9a2fd74133070ba18"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.5"
|
||||
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: d0412fcf4c6b31ecfdb7762359b7206ffba3bbffd396c6d9f9c4616ece476c1f
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.2"
|
||||
url_launcher_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_windows
|
||||
sha256: "712c70ab1b99744ff066053cbe3e80c73332b38d46e5e945c98689b2e66fc15f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.5"
|
||||
uuid:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -661,22 +549,6 @@ 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:
|
||||
@@ -685,22 +557,6 @@ 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:
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ environment:
|
||||
sdk: ^3.10.7
|
||||
|
||||
dependencies:
|
||||
appwrite: ^14.0.0
|
||||
cryptography: ^2.9.0
|
||||
cupertino_icons: ^1.0.8
|
||||
flutter:
|
||||
sdk: flutter
|
||||
|
||||
Reference in New Issue
Block a user