diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..dea54ad --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,12 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Flutter Debug", + "request": "launch", + "type": "dart", + "program": "lib/main.dart", + "flutterMode": "debug" + } + ] +} \ No newline at end of file diff --git a/analysis_options.yaml b/analysis_options.yaml index 0d29021..a59736d 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -7,6 +7,17 @@ # The following line activates a set of recommended lints for Flutter apps, # packages, and plugins designed to encourage good coding practices. +analyzer: + errors: + avoid_print: ignore + exclude: + - build/** + - android/** + - ios/** + - web/** + - windows/** + - macos/** + - linux/** include: package:flutter_lints/flutter.yaml linter: diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 8cfaa6d..f3b2ede 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,4 +1,5 @@ + [].obs; + + @override + void onReady() { + loadFilamentListPerUser(); + } + + @override + void onClose() {} + + void logout() { + Get.offAllNamed(LoginPage.namedRoute); + } + + void addFilament() {} + + Future loadFilamentListPerUser() async { + isLoading.value = true; + if (filamentModelList.isNotEmpty) { + filamentModelList.clear(); + } + if (identId != '') { + var resultList = await directusService.getListByUserId(identId); + if (resultList.isEmpty) { + print('Dokumente wurden nicht geladen: ${resultList.length}'); + } else { + print('Filament List: $resultList'); + filamentModelList.addAll( + resultList.map((json) => FilamentModel.fromJson(json)).toList(), + ); + } + } + isLoading.value = false; + update(); + } +} diff --git a/lib/controller/login_controller.dart b/lib/controller/login_controller.dart new file mode 100644 index 0000000..ec05eae --- /dev/null +++ b/lib/controller/login_controller.dart @@ -0,0 +1,90 @@ +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/directus_service.dart'; +import '../helper/password_helper.dart'; + +class LoginController extends GetxController { + final emailController = TextEditingController(); + final passwordController = TextEditingController(); + final directusService = DirectusService(); + + @override + void onReady() {} + + @override + void onClose() { + emailController.dispose(); + passwordController.dispose(); + } + + void newLoginDirectus() async { + var email = emailController.text.trim().toLowerCase(); + var password = passwordController.text; + 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, + ); + print('Login erfolgreich für Benutzer mit IdentId: $identId'); + helper.kCurrentUserIdentId = identId; // Set the current user identId + Get.offAllNamed(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\noder registrieren Sie sich, wenn Sie noch kein Konto haben.', + snackPosition: SnackPosition.BOTTOM, + ); + } + } + + void goToSignInPage() { + Get.offAllNamed(SigninPage.namedRoute, arguments: {'userIdentId': ''}); + } +} diff --git a/lib/controller/signin_controller.dart b/lib/controller/signin_controller.dart new file mode 100644 index 0000000..45f743a --- /dev/null +++ b/lib/controller/signin_controller.dart @@ -0,0 +1,91 @@ +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/directus_service.dart'; + +class SigninController extends GetxController { + final userNameController = TextEditingController(); + final emailController = TextEditingController(); + final passwordController = TextEditingController(); + final cityController = TextEditingController(); + final countryController = TextEditingController(); + final directusService = DirectusService(); + + @override + void onReady() {} + + @override + void onClose() { + userNameController.dispose(); + emailController.dispose(); + passwordController.dispose(); + cityController.dispose(); + countryController.dispose(); + super.onClose(); + } + + void register() async { + // Eingaben überprüfen + if (!_checkInputs()) return; + + // Registrierungslogik + var userName = userNameController.text.trim(); + var email = emailController.text.trim().toLowerCase(); + var password = passwordController.text; + var city = cityController.text.trim(); + var country = countryController.text.trim(); + + print( + 'Registrieren mit Benutzername: $userName, E-Mail: $email, Stadt: $city, Land: $country', + ); + + bool registrationSuccess = await directusService.register(userName, email, password, city, country); + + if (registrationSuccess) { + Get.snackbar( + 'Registrierung erfolgreich', + 'Willkommen, $userName!', + snackPosition: SnackPosition.BOTTOM, + ); + var userIdentId = base64Encode(utf8.encode('$email:$password')); + Get.offAllNamed(HomePage.namedRoute, arguments: {'userIdentId': userIdentId}); + } else { + Get.snackbar( + 'Registrierung fehlgeschlagen', + 'Bitte überprüfen Sie Ihre Eingaben und versuchen Sie es erneut.', + snackPosition: SnackPosition.BOTTOM, + ); + } + } + + void goToLoginPage() { + FocusManager.instance.primaryFocus?.unfocus(); + Get.offAllNamed(LoginPage.namedRoute); + } + + bool _checkInputs() { + if (userNameController.text.trim().isEmpty || + emailController.text.trim().isEmpty || + passwordController.text.isEmpty) { + Get.snackbar( + 'Fehler', + 'Bitte alle erforderlichen Felder ausfüllen', + snackPosition: SnackPosition.BOTTOM, + ); + return false; + } + + if (passwordController.text.length < 6) { + Get.snackbar( + 'Fehler', + 'Passwort muss mindestens 6 Zeichen lang sein', + snackPosition: SnackPosition.BOTTOM, + ); + return false; + } + + return true; + } +} \ No newline at end of file diff --git a/lib/helper/helper.dart b/lib/helper/helper.dart new file mode 100644 index 0000000..6c72425 --- /dev/null +++ b/lib/helper/helper.dart @@ -0,0 +1,16 @@ + + +import 'package:intl/intl.dart'; + +/// Retrieves the current date in the format "yyyy-MM-dd". +/// +/// @return [String] A formatted date. +String getCurrentDate(DateTime date) { + return DateFormat("yyyy-MM-dd").format(date); +} + +var kListDropDownYear = [ + for (var year = DateTime.now().year; year >= 2020; year--) year +]; + +var kCurrentUserIdentId = ''; \ No newline at end of file diff --git a/lib/helper/http_override.dart b/lib/helper/http_override.dart new file mode 100644 index 0000000..36ccf39 --- /dev/null +++ b/lib/helper/http_override.dart @@ -0,0 +1,10 @@ +import 'dart:io'; + +class MyHttpOverrides extends HttpOverrides { + @override + HttpClient createHttpClient(SecurityContext? context) { + return super.createHttpClient(context) + ..badCertificateCallback = + (X509Certificate cert, String host, int port) => true; + } +} diff --git a/lib/helper/password_helper.dart b/lib/helper/password_helper.dart new file mode 100644 index 0000000..0ac74fb --- /dev/null +++ b/lib/helper/password_helper.dart @@ -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 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 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 _generateSalt(int length) { + final random = Random.secure(); + + return List.generate( + length, + (_) => random.nextInt(256), + ); + } + + /// Vergleich ohne vorzeitigen Abbruch. + static bool _constantTimeEquals( + List a, + List 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; + } +} \ No newline at end of file diff --git a/lib/helper/sample_bindings.dart b/lib/helper/sample_bindings.dart new file mode 100644 index 0000000..5b1c399 --- /dev/null +++ b/lib/helper/sample_bindings.dart @@ -0,0 +1,21 @@ +import 'package:get/get.dart'; + +import '../controller/home_controller.dart'; +import '../controller/login_controller.dart'; +import '../controller/signin_controller.dart'; + + + + + +class SampleBindings extends Bindings { + @override + void dependencies() { + // Define your dependencies here no permanent Binding + Get.lazyPut(() => LoginController()); + Get.lazyPut(() => SigninController()); + Get.lazyPut(() => HomeController()); + + + } +} diff --git a/lib/helper/sample_routes.dart b/lib/helper/sample_routes.dart new file mode 100644 index 0000000..20f9d40 --- /dev/null +++ b/lib/helper/sample_routes.dart @@ -0,0 +1,26 @@ +import 'package:get/get.dart'; +import '../pages/home_view.dart'; +import '../pages/login_view.dart'; +import '../pages/signin_view.dart'; +import 'sample_bindings.dart'; + +class SampleRouts { + static final sampleBindings = SampleBindings(); + static List> samplePages = [ + GetPage( + name: LoginPage.namedRoute, + page: () => const LoginPage(), + binding: sampleBindings, + ), + GetPage( + name: SigninPage.namedRoute, + page: () => const SigninPage(), + binding: sampleBindings, + ), + GetPage( + name: HomePage.namedRoute, + page: () => const HomePage(), + binding: sampleBindings, + ), + ]; +} \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index 244a702..cc08a38 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,122 +1,32 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +import 'helper/http_override.dart'; +//import 'helper/sample_bindings.dart'; +import 'helper/sample_routes.dart'; +import 'pages/login_view.dart'; void main() { + WidgetsFlutterBinding.ensureInitialized(); + HttpOverrides.global = MyHttpOverrides(); runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); - // This widget is the root of your application. @override Widget build(BuildContext context) { - return MaterialApp( - title: 'Flutter Demo', - theme: ThemeData( - // This is the theme of your application. - // - // TRY THIS: Try running your application with "flutter run". You'll see - // the application has a purple toolbar. Then, without quitting the app, - // try changing the seedColor in the colorScheme below to Colors.green - // and then invoke "hot reload" (save your changes or press the "hot - // reload" button in a Flutter-supported IDE, or press "r" if you used - // the command line to start the app). - // - // Notice that the counter didn't reset back to zero; the application - // state is not lost during the reload. To reset the state, use hot - // restart instead. - // - // This works for code too, not just values: Most code changes can be - // tested with just a hot reload. - colorScheme: .fromSeed(seedColor: Colors.deepPurple), - ), - home: const MyHomePage(title: 'Flutter Demo Home Page'), + return GetMaterialApp( + title: 'Flutter Filament', + debugShowCheckedModeBanner: false, + theme: ThemeData(colorScheme: .fromSeed(seedColor: Colors.deepPurple)), + //initialBinding: SampleBindings(), + initialRoute: LoginPage.namedRoute, + getPages: SampleRouts.samplePages, ); } } -class MyHomePage extends StatefulWidget { - const MyHomePage({super.key, required this.title}); - - // This widget is the home page of your application. It is stateful, meaning - // that it has a State object (defined below) that contains fields that affect - // how it looks. - - // This class is the configuration for the state. It holds the values (in this - // case the title) provided by the parent (in this case the App widget) and - // used by the build method of the State. Fields in a Widget subclass are - // always marked "final". - - final String title; - - @override - State createState() => _MyHomePageState(); -} - -class _MyHomePageState extends State { - int _counter = 0; - - void _incrementCounter() { - setState(() { - // This call to setState tells the Flutter framework that something has - // changed in this State, which causes it to rerun the build method below - // so that the display can reflect the updated values. If we changed - // _counter without calling setState(), then the build method would not be - // called again, and so nothing would appear to happen. - _counter++; - }); - } - - @override - Widget build(BuildContext context) { - // This method is rerun every time setState is called, for instance as done - // by the _incrementCounter method above. - // - // The Flutter framework has been optimized to make rerunning build methods - // fast, so that you can just rebuild anything that needs updating rather - // than having to individually change instances of widgets. - return Scaffold( - appBar: AppBar( - // TRY THIS: Try changing the color here to a specific color (to - // Colors.amber, perhaps?) and trigger a hot reload to see the AppBar - // change color while the other colors stay the same. - backgroundColor: Theme.of(context).colorScheme.inversePrimary, - // Here we take the value from the MyHomePage object that was created by - // the App.build method, and use it to set our appbar title. - title: Text(widget.title), - ), - body: Center( - // Center is a layout widget. It takes a single child and positions it - // in the middle of the parent. - child: Column( - // Column is also a layout widget. It takes a list of children and - // arranges them vertically. By default, it sizes itself to fit its - // children horizontally, and tries to be as tall as its parent. - // - // Column has various properties to control how it sizes itself and - // how it positions its children. Here we use mainAxisAlignment to - // center the children vertically; the main axis here is the vertical - // axis because Columns are vertical (the cross axis would be - // horizontal). - // - // TRY THIS: Invoke "debug painting" (choose the "Toggle Debug Paint" - // action in the IDE, or press "p" in the console), to see the - // wireframe for each widget. - mainAxisAlignment: .center, - children: [ - const Text('You have pushed the button this many times:'), - Text( - '$_counter', - style: Theme.of(context).textTheme.headlineMedium, - ), - ], - ), - ), - floatingActionButton: FloatingActionButton( - onPressed: _incrementCounter, - tooltip: 'Increment', - child: const Icon(Icons.add), - ), - ); - } -} diff --git a/lib/models/filament_model.dart b/lib/models/filament_model.dart new file mode 100644 index 0000000..cd28c5c --- /dev/null +++ b/lib/models/filament_model.dart @@ -0,0 +1,56 @@ +class FilamentModel { + int? iD; + String? userIdentId; + String? art; + String? color; + String? duckTemperatur; + String? menge; + String? nozzelTemperatur; + String? bettTemperatur; + String? restMengeAktuelleRolleGramm; + String? updatedAt; + String? createdAt; + + FilamentModel( + {this.iD, + this.userIdentId, + this.art, + this.color, + this.duckTemperatur, + this.menge, + this.nozzelTemperatur, + this.bettTemperatur, + this.restMengeAktuelleRolleGramm, + this.updatedAt, + this.createdAt}); + + FilamentModel.fromJson(Map json) { + iD = json['ID']; + userIdentId = json['UserIdentId']; + art = json['Art']; + color = json['Color']; + duckTemperatur = json['DuckTemperatur']; + menge = json['Menge']; + nozzelTemperatur = json['NozzelTemperatur']; + bettTemperatur = json['BettTemperatur']; + restMengeAktuelleRolleGramm = json['RestMengeAktuelleRolleGramm']; + updatedAt = json['UpdatedAt']; + createdAt = json['CreatedAt']; + } + + Map toJson() { + final Map data = {}; + data['ID'] = iD; + data['UserIdentId'] = userIdentId; + data['Art'] = art; + data['Color'] = color; + data['DuckTemperatur'] = duckTemperatur; + data['Menge'] = menge; + data['NozzelTemperatur'] = nozzelTemperatur; + data['BettTemperatur'] = bettTemperatur; + data['RestMengeAktuelleRolleGramm'] = restMengeAktuelleRolleGramm; + data['UpdatedAt'] = updatedAt; + data['CreatedAt'] = createdAt; + return data; + } +} diff --git a/lib/models/tank_user_model.dart b/lib/models/tank_user_model.dart new file mode 100644 index 0000000..4562c8e --- /dev/null +++ b/lib/models/tank_user_model.dart @@ -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 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 toJson() { + final Map data = {}; + 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; + } +} diff --git a/lib/pages/home_view.dart b/lib/pages/home_view.dart new file mode 100644 index 0000000..d294b46 --- /dev/null +++ b/lib/pages/home_view.dart @@ -0,0 +1,78 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import '../controller/home_controller.dart'; +import '../widgets/filament_item_widget.dart'; + +class HomePage extends GetView { + static const String namedRoute = '/home-page'; + const HomePage({super.key}); + + @override + Widget build(BuildContext context) { + var hCtrl = controller; + return Scaffold( + backgroundColor: Colors.grey.shade50, + appBar: AppBar( + centerTitle: true, + elevation: 0, + backgroundColor: Colors.teal.shade400, + foregroundColor: Colors.white, + title: const Text( + 'Filament Lager', + style: TextStyle(fontWeight: FontWeight.bold), + ), + actions: [ + IconButton( + icon: const Icon(Icons.logout), + onPressed: hCtrl.logout, + ), + ], + ), + floatingActionButton: FloatingActionButton.extended( + backgroundColor: Colors.teal.shade400.withValues(alpha: 0.4), + foregroundColor: Colors.grey.shade200, + onPressed: hCtrl.addFilament, + icon: const Icon(Icons.add), + label: const Text('Filament'), + ), + body: Obx(() { + if (hCtrl.isLoading.value) { + return const Center(child: CircularProgressIndicator()); + } + + if (hCtrl.filamentModelList.isEmpty) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.inventory_2_outlined, size: 64, color: Colors.grey.shade400), + const SizedBox(height: 16), + Text( + 'Keine Filamente vorhanden', + style: TextStyle(fontSize: 16, color: Colors.grey.shade600), + ), + ], + ), + ); + } + + return RefreshIndicator( + onRefresh: hCtrl.loadFilamentListPerUser, + child: ListView.builder( + padding: const EdgeInsets.only(top: 8, bottom: 80), + itemCount: hCtrl.filamentModelList.length, + itemBuilder: (context, index) { + final filament = hCtrl.filamentModelList[index]; + return FilamentItemWidget( + filament: filament, + onTap: () { + // Optionale Detailansicht oder Bearbeitung + }, + ); + }, + ), + ); + }), + ); + } +} \ No newline at end of file diff --git a/lib/pages/login_view.dart b/lib/pages/login_view.dart new file mode 100644 index 0000000..836c8ad --- /dev/null +++ b/lib/pages/login_view.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import '../controller/login_controller.dart'; +import '../widgets/my_login_widget.dart'; + +class LoginPage extends GetView { + static const String namedRoute = '/login-page'; + const LoginPage({super.key}); + + @override + Widget build(BuildContext context) { + var logCtrl = controller; + var displayWidth = MediaQuery.of(context).size.width; + var displayHeight = MediaQuery.of(context).size.height; + return PopScope( + canPop: false, // Verhindert das Zurücknavigieren über die System-Geste / den Back-Button + child: SafeArea( + child: Scaffold( + resizeToAvoidBottomInset: true, + body: Container( + height: displayHeight, + width: displayWidth, + padding: const EdgeInsets.symmetric( + horizontal: 16.0, + vertical: 50.0, + ), + color: Colors.blue.shade100, + child: SingleChildScrollView( + child: MyLoginWidget( + onButtonPressed: () => logCtrl.newLoginDirectus(), + signInOnTab: () => logCtrl.goToSignInPage(), + logCtrl: logCtrl, + ), + ), + ), + ), + ), + ); + } +} diff --git a/lib/pages/signin_view.dart b/lib/pages/signin_view.dart new file mode 100644 index 0000000..ea3fe0d --- /dev/null +++ b/lib/pages/signin_view.dart @@ -0,0 +1,41 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +import '../controller/signin_controller.dart'; +import '../widgets/my_signin_widget.dart'; + +class SigninPage extends GetView { + static const String namedRoute = '/signin-page'; + const SigninPage({super.key}); + + @override + Widget build(BuildContext context) { + var sigCtrl = controller; + var displayWidth = MediaQuery.of(context).size.width; + var displayHeight = MediaQuery.of(context).size.height; + return SafeArea( + child: PopScope( + canPop: false, // Verhindert das Zurücknavigieren über die System-Geste / den Back-Button + child: Scaffold( + resizeToAvoidBottomInset: true, + body: Container( + height: displayHeight, + width: displayWidth, + padding: const EdgeInsets.symmetric( + horizontal: 16.0, + vertical: 90.0, + ), + color: Colors.blue.shade100, + child: SingleChildScrollView( + child: MySigninWidget( + signCtrl: sigCtrl, + onButtonPressed: () => sigCtrl.register(), + logInOnTab: () => sigCtrl.goToLoginPage(), + ), + ), + ), + ), + ), + ); + } +} diff --git a/lib/services/directus_service.dart b/lib/services/directus_service.dart new file mode 100644 index 0000000..2622113 --- /dev/null +++ b/lib/services/directus_service.dart @@ -0,0 +1,134 @@ +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 filamentRest = EnvDirectus.directusFilamentRest; + + static Map get headers => { + 'Authorization': 'Bearer $apiTokenBearer', + 'Content-Type': 'application/json', + }; + + Future> 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; + //print('$json}'); + client.close(); + return json['data'] as List; + } + + Future> getListByUserId(String identId) async { + var client = http.Client(); + //String apiUrl = '$endpoint/items/$filamentRest?filter[UserIdentId][_eq]=$userIdentId'; + String apiUrl = '$endpoint/items/$filamentRest?filter[UserIdentId][_eq]=$identId'; + 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; + //print('$json}'); + client.close(); + return json['data'] as List; + } + + Future deleteDocumentFromTable(int iD) async { + var client = http.Client(); + String apiUrl = '$endpoint/items/$filamentRest/$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 createDocumentInTable(Map data) async { + return await http.post( + Uri.parse('$endpoint/items/$filamentRest'), + 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 updateDocumentInTable(int? iD, Map data) async { + return iD != null + ? http.patch( + Uri.parse('$endpoint/items/$filamentRest/$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 register(String userName, String email, String password, String city, String country) async { + var client = http.Client(); + String apiUrl = '$endpoint/items/$tankUsersRest'; + Map 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; + } + } + + +} diff --git a/lib/widgets/filament_item_widget.dart b/lib/widgets/filament_item_widget.dart new file mode 100644 index 0000000..2bc67ba --- /dev/null +++ b/lib/widgets/filament_item_widget.dart @@ -0,0 +1,203 @@ +import 'package:flutter/material.dart'; +import '../models/filament_model.dart'; + +class FilamentItemWidget extends StatelessWidget { + final FilamentModel filament; + final VoidCallback? onTap; + + const FilamentItemWidget({ + super.key, + required this.filament, + this.onTap, + }); + + // Hilfsmethode zur Bestimmung der Farbe basierend auf dem String "Color" + Color _parseColor(String colorName) { + switch (colorName.toLowerCase().trim()) { + case 'weiß': + return Colors.white; + case 'schwarz': + return Colors.black; + case 'hell braun': + case 'braun': + return Colors.brown.shade400; + case 'knochenfarbe': + return const Color(0xFFE3DAC9); // Bone / Beige + default: + return Colors.grey; + } + } + + // Hilfsmethode zur Umwandlung von "1kg", "7kg" etc. in Gramm + double _parseTotalWeightInGramm(String mengeStr) { + final cleaned = mengeStr.toLowerCase().replaceAll('kg', '').trim(); + final kg = double.tryParse(cleaned) ?? 1.0; + return kg * 1000.0; + } + + @override + Widget build(BuildContext context) { + final parsedColor = _parseColor(filament.color!); + final restGramm = double.tryParse(filament.restMengeAktuelleRolleGramm!) ?? 0.0; + final totalGramm = _parseTotalWeightInGramm(filament.menge!); + final progress = (restGramm / totalGramm).clamp(0.0, 1.0); + + return Card( + elevation: 0, + margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 6), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + side: BorderSide(color: Colors.grey.shade200), + ), + child: InkWell( + borderRadius: BorderRadius.circular(16), + onTap: onTap, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Header: Typ, Farbindikator, Spule/Menge + Row( + children: [ + // Farbpunkte mit Schatten für weiße/helle Farben + Container( + width: 24, + height: 24, + decoration: BoxDecoration( + color: parsedColor, + shape: BoxShape.circle, + border: Border.all(color: Colors.grey.shade300, width: 1), + boxShadow: [ + BoxShadow( + color: parsedColor.withValues(alpha: 0.4), + blurRadius: 6, + spreadRadius: 1, + ) + ], + ), + ), + const SizedBox(width: 12), + // Material & Farbe + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + filament.art!, + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + Text( + filament.color!, + style: TextStyle( + fontSize: 14, + color: Colors.grey.shade600, + ), + ), + ], + ), + ), + // Spulengröße Badge + Chip( + padding: EdgeInsets.zero, + labelStyle: const TextStyle(fontSize: 12), + label: Text(filament.menge!), + backgroundColor: Colors.teal.shade50, + side: BorderSide.none, + ), + ], + ), + const SizedBox(height: 16), + + // Temperaturen (Nozzle & Bed) + Row( + children: [ + _buildIconText( + icon: Icons.thermostat, + label: 'Nozzle', + value: filament.nozzelTemperatur!, + color: Colors.orange.shade700, + ), + const SizedBox(width: 24), + _buildIconText( + icon: Icons.hot_tub, + label: 'Bett', + value: filament.bettTemperatur!, + color: Colors.blue.shade700, + ), + ], + ), + const SizedBox(height: 16), + + // Restmengen Progress Indicator + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Restmenge', + style: TextStyle( + fontSize: 12, + color: Colors.grey.shade600, + ), + ), + Text( + '${restGramm.toInt()}g / ${totalGramm.toInt()}g', + style: const TextStyle( + fontSize: 12, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + const SizedBox(height: 6), + ClipRRect( + borderRadius: BorderRadius.circular(4), + child: LinearProgressIndicator( + value: progress, + minHeight: 6, + backgroundColor: Colors.grey.shade200, + color: progress < 0.2 ? Colors.red : Colors.teal.shade400, + ), + ), + ], + ), + ], + ), + ), + ), + ); + } + + Widget _buildIconText({ + required IconData icon, + required String label, + required String value, + required Color color, + }) { + return Row( + children: [ + Icon(icon, size: 18, color: color), + const SizedBox(width: 4), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + label, + style: TextStyle(fontSize: 10, color: Colors.grey.shade600), + ), + Text( + value, + style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w600), + ), + ], + ), + ], + ); + } +} \ No newline at end of file diff --git a/lib/widgets/my_login_widget.dart b/lib/widgets/my_login_widget.dart new file mode 100644 index 0000000..1a9797e --- /dev/null +++ b/lib/widgets/my_login_widget.dart @@ -0,0 +1,134 @@ +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart' show GoogleFonts; +import '../controller/login_controller.dart'; + +class MyLoginWidget extends StatelessWidget { + final void Function()? onButtonPressed; + final void Function()? signInOnTab; + final LoginController logCtrl; + + const MyLoginWidget({ + super.key, + this.onButtonPressed, + this.signInOnTab, + required this.logCtrl, + }); + + @override + Widget build(BuildContext context) { + final googleFont = GoogleFonts.righteous().fontFamily; + return Column( + children: [ + Stack( + children: [ + // Umriss (Outline) + Text( + "Login Page", + style: TextStyle( + fontSize: 44, + fontFamily: googleFont, + fontStyle: FontStyle.normal, + letterSpacing: 5.0, + foreground: Paint() + ..style = PaintingStyle.stroke + ..strokeWidth = 5 + ..color = Colors.blue.shade900, + ), + ), + // Füllung + Text( + "Login Page", + style: TextStyle( + fontSize: 44, + fontFamily: googleFont, + fontStyle: FontStyle.normal, + color: Colors.orange.shade300, + letterSpacing: 5.0, + ), + ), + ], + ), + SizedBox(height: 20), + Container( + decoration: BoxDecoration( + border: Border.all(color: Colors.orange.shade400, width: 8), + borderRadius: BorderRadius.circular(30), + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(18), + child: Image.asset( + 'assets/images/filamentTransparent.png', + width: 300, + height: 300, + ), + ), + ), + SizedBox(height: 20), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 50.0), + child: TextField( + controller: logCtrl.emailController, + keyboardType: TextInputType.emailAddress, + decoration: InputDecoration( + border: OutlineInputBorder(), + labelText: 'E-Mail', + ), + ), + ), + SizedBox(height: 20), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 50.0), + child: TextField( + obscureText: true, + controller: logCtrl.passwordController, + keyboardType: TextInputType.visiblePassword, + decoration: InputDecoration( + border: OutlineInputBorder(), + labelText: 'Password', + ), + ), + ), + SizedBox(height: 30), + ElevatedButton( + style: ButtonStyle( + backgroundColor: WidgetStatePropertyAll( + Colors.orange.shade500, + ), + foregroundColor: WidgetStatePropertyAll( + Colors.blue.shade900, + ), + minimumSize: WidgetStatePropertyAll(Size(430, 60)), + ), + onPressed: onButtonPressed, + child: Text( + 'Login', + style: TextStyle(fontSize: 34, fontFamily: googleFont), + ), + ), + SizedBox(height: 10), + RichText( + text: TextSpan( + style: TextStyle(fontSize: 16, color: Colors.black), + children: [ + TextSpan(text: 'Zum registrieren '), + WidgetSpan( + child: GestureDetector( + onTap: signInOnTab, + child: Text( + 'Sign in', + style: TextStyle( + fontSize: 16, + color: Colors.blue.shade700, + decoration: TextDecoration.underline, + ), + ), + ), + ), + TextSpan(text: ' clicken!!'), + ], + ), + ), + ], + ); + } +} diff --git a/lib/widgets/my_signin_widget.dart b/lib/widgets/my_signin_widget.dart new file mode 100644 index 0000000..b47533f --- /dev/null +++ b/lib/widgets/my_signin_widget.dart @@ -0,0 +1,162 @@ +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart' show GoogleFonts; +import '../controller/signin_controller.dart'; + +class MySigninWidget extends StatelessWidget { + final void Function()? onButtonPressed; + final void Function()? logInOnTab; + final SigninController signCtrl; + + const MySigninWidget({ + super.key, + this.onButtonPressed, + this.logInOnTab, + required this.signCtrl, + }); + + @override + Widget build(BuildContext context) { + final googleFont = GoogleFonts.righteous().fontFamily; + return Column( + children: [ + Stack( + children: [ + // Umriss (Outline) + Text( + "Signin Page", + style: TextStyle( + fontSize: 44, + fontFamily: googleFont, + fontStyle: FontStyle.normal, + letterSpacing: 5.0, + foreground: Paint() + ..style = PaintingStyle.stroke + ..strokeWidth = 5 + ..color = Colors.blue.shade900, + ), + ), + // Füllung + Text( + "Signin Page", + style: TextStyle( + fontSize: 44, + fontFamily: googleFont, + fontStyle: FontStyle.normal, + color: Colors.orange.shade300, + letterSpacing: 5.0, + ), + ), + ], + ), + SizedBox(height: 20), + Container( + decoration: BoxDecoration( + border: Border.all(color: Colors.orange.shade400, width: 8), + borderRadius: BorderRadius.circular(30), + ), + ), + SizedBox(height: 20), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 50.0), + child: TextField( + controller: signCtrl.userNameController, + keyboardType: TextInputType.emailAddress, + decoration: InputDecoration( + border: OutlineInputBorder(), + labelText: 'Username', + ), + ), + ), + SizedBox(height: 20), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 50.0), + child: TextField( + controller: signCtrl.emailController, + keyboardType: TextInputType.emailAddress, + decoration: InputDecoration( + border: OutlineInputBorder(), + labelText: 'Email', + ), + ), + ), + SizedBox(height: 20), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 50.0), + child: TextField( + obscureText: true, + controller: signCtrl.passwordController, + keyboardType: TextInputType.visiblePassword, + decoration: InputDecoration( + border: OutlineInputBorder(), + labelText: 'Password', + ), + ), + ), + 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( + backgroundColor: WidgetStatePropertyAll( + Colors.orange.shade500, + ), + foregroundColor: WidgetStatePropertyAll( + Colors.blue.shade900, + ), + minimumSize: WidgetStatePropertyAll(Size(430, 60)), + ), + onPressed: onButtonPressed, + child: Text( + 'Register', + style: TextStyle(fontSize: 34, fontFamily: googleFont), + ), + ), + SizedBox(height: 10), + RichText( + text: TextSpan( + style: TextStyle(fontSize: 16, color: Colors.black), + children: [ + TextSpan(text: 'Zum login '), + WidgetSpan( + child: GestureDetector( + onTap: logInOnTab, + child: Text( + 'Log in', + style: TextStyle( + fontSize: 16, + color: Colors.blue.shade700, + decoration: TextDecoration.underline, + ), + ), + ), + ), + TextSpan(text: ' clicken!!'), + ], + ), + ), + ], + ); + } +} diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 2e1de87..be1ee3e 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -6,6 +6,7 @@ list(APPEND FLUTTER_PLUGIN_LIST ) list(APPEND FLUTTER_FFI_PLUGIN_LIST + jni ) set(PLUGIN_BUNDLED_LIBRARIES) diff --git a/pubspec.lock b/pubspec.lock index 9f92f27..1c9a666 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,6 +1,14 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + args: + dependency: transitive + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.dev" + source: hosted + version: "2.7.0" async: dependency: transitive description: @@ -29,10 +37,18 @@ packages: dependency: transitive description: name: clock - sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + sha256: e51d50bca3217c9a9fa2b41a30e4a38971133f5f9ec7a3d57bae095007f1d28e url: "https://pub.dev" source: hosted - version: "1.1.2" + version: "1.1.3" + code_assets: + dependency: transitive + description: + name: code_assets + sha256: "828110d598123b5ea96c00c9f3c72105bf79f8ee36c20a39b26209ade421ec57" + url: "https://pub.dev" + source: hosted + version: "2.1.0" collection: dependency: transitive description: @@ -41,14 +57,38 @@ packages: url: "https://pub.dev" source: hosted version: "1.19.1" + crypto: + dependency: transitive + description: + name: crypto + sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf + 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: name: cupertino_icons - sha256: "41e005c33bd814be4d3096aff55b1908d419fde52ca656c8c47719ec745873cd" + sha256: b7cc2390aac296c083bea762859413eb1cf5b630c70e0c7e4d831018f762a8b2 url: "https://pub.dev" source: hosted - version: "1.0.9" + version: "2.0.0" + cupertino_ui: + dependency: transitive + description: + name: cupertino_ui + sha256: c23816577a1280cff409e989541767887011f8f1d8c1863a794ed308af46f2a9 + url: "https://pub.dev" + source: hosted + version: "1.1.1" fake_async: dependency: transitive description: @@ -57,6 +97,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.3" + ffi: + dependency: transitive + description: + name: ffi + sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45" + url: "https://pub.dev" + source: hosted + version: "2.2.0" flutter: dependency: "direct main" description: flutter @@ -70,11 +118,88 @@ packages: url: "https://pub.dev" source: hosted version: "6.0.0" + flutter_localizations: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" flutter_test: dependency: "direct dev" description: flutter source: sdk version: "0.0.0" + get: + dependency: "direct main" + description: + name: get + sha256: "5ed34a7925b85336e15d472cc4cfe7d9ebf4ab8e8b9f688585bf6b50f4c3d79a" + url: "https://pub.dev" + source: hosted + version: "4.7.3" + google_fonts: + dependency: "direct main" + description: + name: google_fonts + sha256: fdbbfbbb1a62f376ab29fa9627561d389751fb984da0b630ce3326e272911c8a + url: "https://pub.dev" + source: hosted + version: "9.0.0" + hooks: + dependency: transitive + description: + name: hooks + sha256: eaac480a35ec0814146c2c48d96aaa829e0e44a7662c88ae84c9edf4bc35651f + url: "https://pub.dev" + source: hosted + version: "2.2.0" + http: + dependency: "direct main" + description: + name: http + sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" + url: "https://pub.dev" + source: hosted + version: "1.6.0" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev" + source: hosted + version: "4.1.2" + intl: + dependency: "direct main" + description: + name: intl + sha256: "1ca20c894b1717686a2319b8548763d812bc0aabdac580420a44c5178c57a867" + url: "https://pub.dev" + source: hosted + version: "0.20.3" + jni: + dependency: transitive + description: + name: jni + sha256: f038e58b4dc2c9037f50e233175086337e0b305e356d28211bf55f21c504cbd3 + url: "https://pub.dev" + source: hosted + version: "1.0.3" + jni_flutter: + dependency: transitive + description: + name: jni_flutter + sha256: b2310cdd4c18c65c081ab141a41efa94aa26c65431803703ece51996f174f351 + url: "https://pub.dev" + source: hosted + version: "1.0.3" + jni_util: + dependency: transitive + description: + name: jni_util + sha256: "1ba86da04a5f2bf18fde2edb235587e70c5b0fc5bd4ba955f46b00942c3fc35f" + url: "https://pub.dev" + source: hosted + version: "1.0.0" leak_tracker: dependency: transitive description: @@ -107,14 +232,22 @@ packages: url: "https://pub.dev" source: hosted version: "6.1.0" + logging: + dependency: transitive + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" matcher: dependency: transitive description: name: matcher - sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 + sha256: "31bd099b47c10cd1aeb55146a2d46ce0277630ecef3f7dae54ad7873f36696cd" url: "https://pub.dev" source: hosted - version: "0.12.19" + version: "0.12.20" material_color_utilities: dependency: transitive description: @@ -123,14 +256,38 @@ packages: url: "https://pub.dev" source: hosted version: "0.13.0" + material_ui: + dependency: transitive + description: + name: material_ui + sha256: "32d98f11cf5942a095a8c3e01b03e4de3b17941437713dcf36b1088fb62e23d2" + url: "https://pub.dev" + source: hosted + version: "1.5.0" meta: dependency: transitive description: name: meta - sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349" + sha256: "307249ce4ff29d58a18e97f6345f539382eb9c9c29ecda628900f31de0443dd9" url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.19.0" + objective_c: + dependency: transitive + description: + name: objective_c + sha256: ad56fd53a78ff6b1472fa59ff2a4e8b8ccabafc586fc263a1dfad0b99b5553e3 + url: "https://pub.dev" + source: hosted + version: "9.6.0" + package_config: + dependency: transitive + description: + name: package_config + sha256: ffcf4cf3d6c0b74ac43708d9f56625506e8a68aa935abe9d267a7330f320eb5d + url: "https://pub.dev" + source: hosted + version: "3.0.0" path: dependency: transitive description: @@ -139,6 +296,86 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.1" + path_provider: + dependency: transitive + description: + name: path_provider + sha256: a7f4874f987173da295a61c181b8ee71dab59b332a486b391babf26a1b884825 + url: "https://pub.dev" + source: hosted + version: "2.1.6" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "69cbd515a62b94d32a7944f086b2f82b4ac40a1d45bebfc00813a430ab2dabcd" + url: "https://pub.dev" + source: hosted + version: "2.3.1" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699" + url: "https://pub.dev" + source: hosted + version: "2.6.0" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: "58c2005f147315b11e9b4a7bc889cd5203e250cba8e3f012dae259b4972b5c16" + url: "https://pub.dev" + source: hosted + version: "2.2.2" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "484838772624c3a4b94f1e44a3e19897fee738f2d5c4ce448443b0417f7c9dda" + url: "https://pub.dev" + source: hosted + version: "2.1.3" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + platform: + dependency: transitive + description: + name: platform + sha256: a36d119c13416516a7b5913fbe8af8531e11633d784c550b2125f76c758524ec + url: "https://pub.dev" + source: hosted + version: "3.2.0" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "261236774e8b1d69cfc6b9eabbc96c40f25e7a2d6b171f3385d4f65d5734fb24" + url: "https://pub.dev" + source: hosted + version: "2.2.1" + record_use: + dependency: transitive + description: + name: record_use + sha256: "1cb8564af8d43b464294411db9217f5ec04891c6f22ee2c32d73ae05e88a6bd2" + url: "https://pub.dev" + source: hosted + version: "1.1.1" sky_engine: dependency: transitive description: flutter @@ -156,10 +393,10 @@ packages: dependency: transitive description: name: stack_trace - sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + sha256: "277654b3034d17ac6f9f1cb5595db011b1d5d41e8806866db28e0abaa101c490" url: "https://pub.dev" source: hosted - version: "1.12.1" + version: "1.12.2" stream_channel: dependency: transitive description: @@ -188,18 +425,26 @@ packages: dependency: transitive description: name: test_api - sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e" + sha256: "2a122cbe059f8b610d3a5415f42e255b6c17b1f21eee1d960f31080237fb4f11" url: "https://pub.dev" source: hosted - version: "0.7.11" + version: "0.7.12" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" vector_math: dependency: transitive description: name: vector_math - sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + sha256: "92b9910f66ed1057fd4da7b040ae7c74cafacf885bdc81be496928d5049b032d" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.4.3" vm_service: dependency: transitive description: @@ -208,6 +453,30 @@ packages: url: "https://pub.dev" source: hosted version: "15.3.0" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + yaml: + dependency: transitive + description: + name: yaml + sha256: f67cdd8e07d3c6329146aaef1ba043542b3134c12489f553ca9a7435d1068aea + url: "https://pub.dev" + source: hosted + version: "3.1.4" sdks: - dart: ">=3.12.2 <4.0.0" - flutter: ">=3.18.0-18.0.pre.54" + dart: ">=3.13.0 <4.0.0" + flutter: ">=3.47.0" diff --git a/pubspec.yaml b/pubspec.yaml index b98e562..e2c6ca5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -33,7 +33,12 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^1.0.8 + cupertino_icons: ^2.0.0 + cryptography: ^2.9.0 + get: ^4.7.3 + google_fonts: ^9.0.0 + http: ^1.6.0 + intl: ^0.20.2 dev_dependencies: flutter_test: @@ -58,9 +63,9 @@ flutter: uses-material-design: true # To add assets to your application, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg + assets: + - assets/images/filamentTransparent.png + # - images/a_dot_ham.jpeg # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/to/resolution-aware-images diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index b93c4c3..3ad69c6 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -6,6 +6,7 @@ list(APPEND FLUTTER_PLUGIN_LIST ) list(APPEND FLUTTER_FFI_PLUGIN_LIST + jni ) set(PLUGIN_BUNDLED_LIBRARIES)