diff --git a/.envExample b/.envExample index 524e619..cc43efc 100644 --- a/.envExample +++ b/.envExample @@ -3,4 +3,5 @@ APPWRITE_PROJECT_NAME= APPWRITE_PROJECT_ID= APPWRITE_DATABASE_ID= APPWRITE_COLLECTION_ID= +APPWRITE_SELF_SIGNED= PTVE_API_KEY= \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..3729043 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,25 @@ +{ + // Verwendet IntelliSense zum Ermitteln möglicher Attribute. + // Zeigen Sie auf vorhandene Attribute, um die zugehörigen Beschreibungen anzuzeigen. + // Weitere Informationen finden Sie unter https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "web_flutter_tank_appwrite_app", + "request": "launch", + "type": "dart" + }, + { + "name": "web_flutter_tank_appwrite_app (profile mode)", + "request": "launch", + "type": "dart", + "flutterMode": "profile" + }, + { + "name": "web_flutter_tank_appwrite_app (release mode)", + "request": "launch", + "type": "dart", + "flutterMode": "release" + } + ] +} \ No newline at end of file diff --git a/lib/bindings/input_binding.dart b/lib/bindings/input_binding.dart new file mode 100644 index 0000000..bb9d46f --- /dev/null +++ b/lib/bindings/input_binding.dart @@ -0,0 +1,18 @@ +import 'package:get/get.dart'; +import '../controllers/input_controller.dart'; + +class InputBinding extends Bindings { + @override + void dependencies() { + Get.lazyPut(() => InputController()); + } +} + +/// Alternative: Permanent Binding für App-weite Nutzung +class InputPermanentBinding extends Bindings { + @override + void dependencies() { + // Permanent - Controller bleibt im Speicher + Get.put(InputController(), permanent: true); + } +} diff --git a/lib/controllers/input_controller.dart b/lib/controllers/input_controller.dart new file mode 100644 index 0000000..d5d26f7 --- /dev/null +++ b/lib/controllers/input_controller.dart @@ -0,0 +1,17 @@ +import 'package:get/get.dart'; + +class InputController extends GetxController { + + + @override + void onInit() { + super.onInit(); + } + + @override + void onReady() {} + + @override + void onClose() {} + +} diff --git a/lib/controllers/login_controller.dart b/lib/controllers/login_controller.dart index e53f4d2..49f25d9 100644 --- a/lib/controllers/login_controller.dart +++ b/lib/controllers/login_controller.dart @@ -4,6 +4,7 @@ import 'package:flutter/cupertino.dart'; import 'package:get/get.dart'; import 'package:appwrite/appwrite.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:flutter/foundation.dart'; class LoginController extends GetxController { final _account = Rxn(); @@ -12,48 +13,129 @@ class LoginController extends GetxController { final nameController = TextEditingController(); final _endpoint = dotenv.env['APPWRITE_ENDPOINT_URL'] ?? ''; final _projectId = dotenv.env['APPWRITE_PROJECT_ID'] ?? ''; + final bool _selfSigned = + (dotenv.env['APPWRITE_SELF_SIGNED'] ?? 'false').toLowerCase() == 'true'; final _logedInUser = Rxn(); - + final formKey = GlobalKey(); + final isLogIn = false.obs; + account_models.Account? get account => _account.value; user_models.User? get logedInUser => _logedInUser.value; - - @override + @override void onInit() { - Client client = Client() - .setEndpoint(_endpoint) - .setProject(_projectId); + Client client = Client().setEndpoint(_endpoint).setProject(_projectId); + // Optional: Unsichere/self-signed Zertifikate im Dev zulassen (nicht im Web wirksam) + if (!kIsWeb && _selfSigned) { + client.setSelfSigned(status: true); + } _account.value = Account(client); super.onInit(); } - Future login(String email, String password) async { - await _account.value!.createEmailPasswordSession( - email: email, - password: password, - ); - final user = await _account.value!.get(); - - _logedInUser.value = user; - + emailValidator(String? value) { + if (value == null || value.isEmpty) { + return 'Please enter your email'; + } + final emailRegex = RegExp(r'^[^@]+@[^@]+\.[^@]+'); + if (!emailRegex.hasMatch(value)) { + return 'Please enter a valid email address'; + } + return null; } - Future register(String email, String password, String name) async { - await _account.value!.create( - userId: ID.unique(), - email: email, - password: password, - name: name, - ); - await login(email, password); + passwordValidator(String? value) { + if (value == null || value.isEmpty) { + return 'Please enter your password'; + } + if (value.length < 6) { + return 'Password must be at least 6 characters long'; + } + return null; + } + + nameValidator(String? value) { + if (value == null || value.isEmpty) { + return 'Please enter your name'; + } + return null; + } + + Future login() async { + try { + var result = await _account.value!.createEmailPasswordSession( + email: mailController.text, + password: passwordController.text, + ); + // ignore: avoid_print + print('Login result: $result'); + final user = await _account.value!.get(); + _logedInUser.value = user; + clearFields(); + Get.snackbar( + 'Erfolg', + 'Anmeldung erfolgreich', + snackPosition: SnackPosition.BOTTOM, + ); + } on AppwriteException catch (e) { + final msg = (e.message == null || e.message!.isEmpty) + ? 'Verbindungsfehler. Prüfe Zertifikat/Endpoint.' + : e.message!; + Get.snackbar( + 'Login fehlgeschlagen', + msg, + snackPosition: SnackPosition.BOTTOM, + duration: const Duration(seconds: 5), + ); + } catch (e) { + Get.snackbar( + 'Login fehlgeschlagen', + e.toString(), + snackPosition: SnackPosition.BOTTOM, + duration: const Duration(seconds: 5), + ); + } + } + + clearFields(){ + mailController.clear(); + passwordController.clear(); + nameController.clear(); + } + + Future register() async { + try { + await _account.value!.create( + userId: ID.unique(), + email: mailController.text, + password: passwordController.text, + name: nameController.text, + ); + await login(); + } on AppwriteException catch (e) { + final msg = (e.message == null || e.message!.isEmpty) + ? 'Registrierung fehlgeschlagen. Prüfe Verbindung.' + : e.message!; + Get.snackbar( + 'Registrierung fehlgeschlagen', + msg, + snackPosition: SnackPosition.BOTTOM, + duration: const Duration(seconds: 5), + ); + } catch (e) { + Get.snackbar( + 'Registrierung fehlgeschlagen', + e.toString(), + snackPosition: SnackPosition.BOTTOM, + duration: const Duration(seconds: 5), + ); + } } Future logout() async { - await _account.value!.deleteSession(sessionId: 'current'); - _logedInUser.value = null; - } - - + await _account.value!.deleteSession(sessionId: 'current'); + _logedInUser.value = null; + } @override void onClose() { @@ -63,3 +145,4 @@ class LoginController extends GetxController { super.onClose(); } } + diff --git a/lib/models/input_model.dart b/lib/models/input_model.dart new file mode 100644 index 0000000..e69de29 diff --git a/lib/pages/input/input_view.dart b/lib/pages/input/input_view.dart new file mode 100644 index 0000000..327f29e --- /dev/null +++ b/lib/pages/input/input_view.dart @@ -0,0 +1,14 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import '../../controllers/input_controller.dart'; + +class InputPage extends GetView { + const InputPage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Container(), + ); + } +} diff --git a/lib/pages/login/login_view.dart b/lib/pages/login/login_view.dart index d674dd1..9f8f908 100644 --- a/lib/pages/login/login_view.dart +++ b/lib/pages/login/login_view.dart @@ -1,14 +1,129 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import '../../controllers/login_controller.dart'; +import '../../widgets/my_form_field.dart'; class LoginPage extends GetView { - const LoginPage({super.key}); + const LoginPage({super.key}); - @override - Widget build(BuildContext context) { - return Scaffold( - body: Container(color: Colors.red.shade300, child: Center(child: Text('Login Page')),), + @override + Widget build(BuildContext context) { + var logCtrl = controller; + var displaySize = MediaQuery.of(context).size; + return PopScope( + canPop: false, + child: SafeArea( + child: Scaffold( + body: Container( + decoration: const BoxDecoration( + image: DecorationImage( + image: AssetImage('assets/img/gasolineGuru.jpg'), + fit: BoxFit.cover, + ), + ), + // Optional: Für besseren Kontrast Inhalt leicht abdunkeln + child: Container( + color: Colors.black.withValues(alpha: 0.25), + child: Center( + child: SizedBox( + width: displaySize.width * 2 / 3, + child: Form( + key: logCtrl.formKey, + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.local_gas_station, + size: 100, + color: Colors.grey.shade300, + ), + const SizedBox(height: 20), + _eMailForm(logCtrl), + const SizedBox(height: 20), + _passwdForm(logCtrl), + SizedBox(height: 20), + Obx( + () => !logCtrl.isLogIn.value + ? SizedBox.shrink() + : _nameForm(logCtrl), + ), + SizedBox(height: 20), + ElevatedButton( + onPressed: () async { + if (logCtrl.formKey.currentState!.validate()) { + if (!logCtrl.isLogIn.value) { + await logCtrl.login(); + } else { + await logCtrl.register(); + } + } + }, + child: Obx( + () => Text( + !logCtrl.isLogIn.value ? 'Login' : 'Register', + ), + ), + ), + const SizedBox(height: 20), + TextButton( + onPressed: () { + logCtrl.isLogIn.value = !logCtrl.isLogIn.value; + }, + child: Obx( + () => Text( + !logCtrl.isLogIn.value + ? 'No account? Register' + : 'Already have an account? Login', + style: TextStyle(color: Colors.grey.shade300), + ), + ), + ), + + // Hier können Sie Ihre Login-Felder hinzufügen + ], + ), + ), + ), + ), + ), + ), + ), + ), + ), ); - } + } + + MyFormField _passwdForm(LoginController logCtrl) { + return MyFormField( + userController: logCtrl.passwordController, + validator: (String? value) => logCtrl.passwordValidator(value) as String?, + labelText: 'Password', + filled: true, + fillColor: Colors.grey.shade300, + keyboardType: TextInputType.visiblePassword, + ); + } + + MyFormField _eMailForm(LoginController logCtrl) { + return MyFormField( + userController: logCtrl.mailController, + validator: (String? value) => logCtrl.emailValidator(value) as String?, + labelText: 'E-Mail', + filled: true, + fillColor: Colors.grey.shade300, + keyboardType: TextInputType.emailAddress, + ); + } + + _nameForm(LoginController logCtrl) { + return MyFormField( + userController: logCtrl.nameController, + validator: (String? value) => logCtrl.nameValidator(value) as String?, + labelText: 'Name', + filled: true, + fillColor: Colors.grey.shade300, + keyboardType: TextInputType.name, + ); + } } diff --git a/lib/widgets/my_form_field.dart b/lib/widgets/my_form_field.dart new file mode 100644 index 0000000..a5a95d1 --- /dev/null +++ b/lib/widgets/my_form_field.dart @@ -0,0 +1,37 @@ +import 'package:flutter/material.dart'; + +class MyFormField extends StatelessWidget { + const MyFormField({ + super.key, + this.validator, + required this.labelText, + required this.filled, + required this.fillColor, + required this.keyboardType, + required this.userController, + }); + + final TextEditingController userController; + final String labelText; + final bool filled; + final Color fillColor; + final TextInputType keyboardType; + final String? Function(String?)? validator; + + @override + Widget build(BuildContext context) { + return TextFormField( + autovalidateMode: AutovalidateMode.onUserInteraction, + style: const TextStyle(color: Colors.black), + controller: userController, + decoration: InputDecoration( + labelText: labelText, + filled: filled, + fillColor: fillColor, + ), + keyboardType: keyboardType, + validator: validator, + obscureText: labelText == 'Password' ? true : false, + ); + } +}