From ebe3426c2c22dfb63c89471b8555cdf1b077f79f Mon Sep 17 00:00:00 2001 From: itsscb Date: Sun, 19 Nov 2023 23:52:46 +0100 Subject: [PATCH] ft/adds account_info and late_person pages --- frontend/app/assets/icons/icon.jpg | Bin 0 -> 4669 bytes frontend/app/lib/main.dart | 44 ++- .../lib/model/services/backend_service.dart | 56 ++- .../app/lib/model/view_model/base_vm.dart | 61 +++ frontend/app/lib/pages/account_info_page.dart | 359 ++++++++++++++++++ frontend/app/lib/pages/late_person_page.dart | 64 ++++ frontend/app/lib/pages/loading_page.dart | 18 + .../app/lib/pages/notifications_page.dart | 83 +++- frontend/app/lib/pages/password_page.dart | 15 +- frontend/app/lib/pages/registration_page.dart | 279 ++++++-------- frontend/app/lib/pages/security_page.dart | 261 ++++++------- frontend/app/lib/pages/start_page.dart | 184 +++++---- frontend/app/lib/pages/verify_email_page.dart | 27 +- frontend/app/lib/util/validation.dart | 28 +- frontend/app/lib/widgets/custom_scaffold.dart | 33 ++ frontend/app/pubspec.lock | 2 +- frontend/app/pubspec.yaml | 2 +- 17 files changed, 1075 insertions(+), 441 deletions(-) create mode 100644 frontend/app/assets/icons/icon.jpg create mode 100644 frontend/app/lib/pages/account_info_page.dart create mode 100644 frontend/app/lib/pages/late_person_page.dart create mode 100644 frontend/app/lib/pages/loading_page.dart create mode 100644 frontend/app/lib/widgets/custom_scaffold.dart diff --git a/frontend/app/assets/icons/icon.jpg b/frontend/app/assets/icons/icon.jpg new file mode 100644 index 0000000000000000000000000000000000000000..2e09e75462dbc00d4376627471005142ee8ec5d4 GIT binary patch literal 4669 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 W*BM{LeF*~tMMPoQ*zT#l(f { @override Widget build(BuildContext context) { if (_loading) { - return SafeArea( - child: Center( - child: Column( - children: [ - const SizedBox( - height: 150, - ), - Hero( - tag: 'logo', - child: Image.asset( - 'assets/JPEG.jpg', - height: 180, - ), - ), - CircularProgressIndicator( - color: CustomColors.primary, - ), - ], - ), - ), - ); + return const StartPage(); + // return SafeArea( + // child: Center( + // child: Column( + // mainAxisAlignment: MainAxisAlignment.center, + // children: [ + // Hero( + // tag: 'logo', + // child: Image.asset( + // 'assets/JPEG.jpg', + // height: 180, + // ), + // ), + // ], + // ), + // ), + // ); } if (verified) { switch (accountLevel) { + case 6: + return const LatePersonPage(); + case 4 || 5: + return const AccountInfoPage(); default: return const StartPage(); } diff --git a/frontend/app/lib/model/services/backend_service.dart b/frontend/app/lib/model/services/backend_service.dart index 040f915..d3e8e86 100644 --- a/frontend/app/lib/model/services/backend_service.dart +++ b/frontend/app/lib/model/services/backend_service.dart @@ -8,6 +8,7 @@ import 'package:app/pb/google/protobuf/timestamp.pb.dart'; import 'package:app/pb/person.pb.dart'; import 'package:app/data/database.dart'; import 'package:app/pb/rpc_create_account.pb.dart'; +import 'package:app/pb/rpc_create_account_info.pb.dart'; import 'package:app/pb/rpc_create_person.pb.dart'; import 'package:app/pb/rpc_get_account.pb.dart'; import 'package:app/pb/rpc_get_account_info.pb.dart'; @@ -143,15 +144,60 @@ class BackendService { } } + Future createAccountInfo( + {required String firstname, + required String lastname, + required String streetAddress, + required String zip, + required String city, + required String country, + required String phoneNumber, + required DateTime birthday}) async { + try { + final acc = await account; + if (acc == null) { + throw FetchDataException('AccountID nicht gespeichert'); + } + final resp = BackendService.client.createAccountInfo( + CreateAccountInfoRequest( + accountId: acc.id, + firstname: firstname, + lastname: lastname, + street: streetAddress, + zip: zip, + city: city, + country: country, + phone: phoneNumber, + birthday: Timestamp.fromDateTime(birthday), + ), + options: CallOptions( + metadata: { + 'Authorization': 'Bearer ${await _storageService.accessToken}' + }, + ), + ); + return resp != null; + } on SocketException { + throw FetchDataException('Keine Internet Verbindung'); + } on GrpcError catch (err) { + throw FetchDataException('${err.message}'); + } catch (err) { + throw InternalException(err.toString()); + } + } + Future resendVerification({required Int64 accountId}) async { try { final resp = await BackendService.client.resendVerification( - ResendVerificationRequest( - accountId: accountId, - ), - options: CallOptions(metadata: { + ResendVerificationRequest( + accountId: accountId, + ), + options: CallOptions( + metadata: { 'Authorization': 'Bearer ${await _storageService.accessToken}' - })); + }, + ), + ); return resp.account.id == accountId; } on SocketException { throw FetchDataException('Keine Internet Verbindung'); diff --git a/frontend/app/lib/model/view_model/base_vm.dart b/frontend/app/lib/model/view_model/base_vm.dart index 1a93248..25cee29 100644 --- a/frontend/app/lib/model/view_model/base_vm.dart +++ b/frontend/app/lib/model/view_model/base_vm.dart @@ -149,6 +149,67 @@ class BaseViewModel with ChangeNotifier { notifyListeners(); } + Future createAccountInfo( + BuildContext context, { + required String firstname, + required String lastname, + required String streetAddress, + required String zip, + required String city, + required String country, + required String phoneNumber, + required DateTime birthday, + }) async { + notifyListeners(); + final messenger = ScaffoldMessenger.of(context); + bool resp = false; + try { + resp = await _service.createAccountInfo( + firstname: firstname, + lastname: lastname, + streetAddress: streetAddress, + zip: zip, + city: city, + country: country, + phoneNumber: phoneNumber, + birthday: birthday, + ); + if (resp) { + _apiResponse = ApiResponse.completed('Registrierung abgeschlossen'); + } + return resp; + } catch (e) { + messenger.showSnackBar(SnackBar( + backgroundColor: CustomColors.error, + content: const Text( + 'Daten konnten nicht übertragen werden', + style: TextStyle(color: Colors.white), + ), + action: SnackBarAction( + label: 'Details', + onPressed: () { + showDialog( + context: context, + builder: (context) => AlertDialog( + backgroundColor: Colors.black, + icon: Icon( + Icons.error, + color: CustomColors.error, + ), + content: Text( + e.toString(), + textAlign: TextAlign.center, + ), + )); + }, + ), + )); + _apiResponse = ApiResponse.error(e.toString()); + } + notifyListeners(); + return resp; + } + Future logout() async { _apiResponse = ApiResponse.loading('Logge aus'); notifyListeners(); diff --git a/frontend/app/lib/pages/account_info_page.dart b/frontend/app/lib/pages/account_info_page.dart new file mode 100644 index 0000000..2cb11f5 --- /dev/null +++ b/frontend/app/lib/pages/account_info_page.dart @@ -0,0 +1,359 @@ +import 'package:app/model/services/storage_service.dart'; +import 'package:app/model/view_model/base_vm.dart'; +import 'package:app/pages/late_person_page.dart'; +import 'package:app/pages/loading_page.dart'; +import 'package:app/pages/notifications_page.dart'; +import 'package:app/util/colors.dart'; +import 'package:app/util/validation.dart'; +import 'package:app/widgets/custom_scaffold.dart'; +import 'package:flutter/material.dart'; + +class AccountInfoPage extends StatefulWidget { + const AccountInfoPage({super.key}); + + @override + State createState() => _AccountInfoPageState(); +} + +class _AccountInfoPageState extends State { + bool _loading = true; + final StorageService _storageService = StorageService(); + final BaseViewModel _vm = BaseViewModel(); + final _formKey = GlobalKey(); + final _firstNameController = TextEditingController(); + final _lastNameController = TextEditingController(); + final _streetController = TextEditingController(); + final _houseNumberController = TextEditingController(); + final _zipController = TextEditingController(); + final _cityController = TextEditingController(); + final _phoneController = TextEditingController(); + final _countryController = TextEditingController(); + final _birthdayController = TextEditingController(); + final _birthPlaceController = TextEditingController(); + + DateTime? _birthday; + @override + void initState() { + _init(); + super.initState(); + } + + @override + void dispose() { + _firstNameController.dispose(); + _lastNameController.dispose(); + _streetController.dispose(); + _houseNumberController.dispose(); + _zipController.dispose(); + _cityController.dispose(); + _phoneController.dispose(); + _countryController.dispose(); + _birthPlaceController.dispose(); + _birthdayController.dispose(); + super.dispose(); + } + + void _init() async { + _countryController.text = 'Deutschland'; + if (await _storageService.accountLevel < 5) { + await _storageService.setAccountLevel(5); + } + setState(() { + _loading = false; + }); + } + + Future _selectDate(BuildContext context) async { + final DateTime? picked = await showDatePicker( + context: context, + builder: (context, child) => Theme( + data: ThemeData.dark(), + child: child ?? const Text(''), + ), + initialDate: DateTime.now().add(const Duration(days: 365 * -18)), + firstDate: DateTime.now().add(const Duration(days: 365 * -100)), + lastDate: DateTime.now().add(const Duration(days: 365 * -18)), + ); + // firstDate: DateTime.now().add(const Duration(days: 365 * -100)), + // lastDate: DateTime.now().add(const Duration(days: 365 * -18))); + if (picked != null && picked != _birthday) { + setState(() { + _birthday = picked; + _birthdayController.text = + '${picked.day.toString().padLeft(2, '0')}.${picked.month.toString().padLeft(2, '0')}.${picked.year}'; + }); + } + } + + @override + Widget build(BuildContext context) { + return SizedBox( + height: MediaQuery.of(context).size.height, + child: CustomScaffold( + backButton: BackButton( + color: CustomColors.primary, + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (builder) => const NotificationsPage())); + }, + ), + children: _loading + ? [ + LoadingPage(), + ] + : [ + const Text( + 'Registrierung abschließen', + textAlign: TextAlign.center, + style: TextStyle( + fontFamily: 'sans-serif', + fontWeight: FontWeight.bold, + letterSpacing: 2.0, + fontSize: 25, + ), + ), + const SizedBox( + height: 20, + ), + SizedBox( + height: + MediaQuery.of(context).viewInsets.bottom > 0 ? 230 : 460, + child: SingleChildScrollView( + scrollDirection: Axis.vertical, + child: Stack( + children: [ + Form( + key: _formKey, + child: Column( + children: [ + TextFormField( + controller: _firstNameController, + autocorrect: false, + autofocus: true, + decoration: const InputDecoration( + label: Text('Vorname'), + filled: true, + ), + keyboardType: TextInputType.name, + validator: (value) { + if (value == null || !value.isValidName) { + return 'Bitte einen gültigen Vornamen eingeben'; + } + return null; + }, + ), + const SizedBox(height: 20), + TextFormField( + controller: _lastNameController, + autocorrect: false, + autofocus: true, + decoration: const InputDecoration( + label: Text('Nachname'), + filled: true, + ), + keyboardType: TextInputType.name, + validator: (value) { + if (value == null || !value.isValidName) { + return 'Bitte einen gültigen Nachnamen eingeben'; + } + return null; + }, + ), + const SizedBox(height: 20), + TextFormField( + controller: _streetController, + autocorrect: false, + autofocus: true, + decoration: const InputDecoration( + label: Text('Straße'), + filled: true, + ), + keyboardType: TextInputType.streetAddress, + validator: (value) { + if (value == null || + !value.isValidStreetAddress) { + return 'Bitte eine gültige Straße eingeben'; + } + return null; + }, + ), + const SizedBox(height: 20), + Row( + children: [ + SizedBox( + width: 130, + child: TextFormField( + controller: _houseNumberController, + autocorrect: false, + autofocus: true, + decoration: const InputDecoration( + label: Text('Hausnummer'), + filled: true, + ), + keyboardType: TextInputType.number, + validator: (value) { + if (value == null || + !value.isValidHouseNumber) { + return 'Nur Zahlen erlaubt'; + } + return null; + }, + ), + ), + const Spacer(), + SizedBox( + width: 160, + child: TextFormField( + controller: _zipController, + autocorrect: false, + autofocus: true, + decoration: const InputDecoration( + label: Text('Postleitzahl'), + filled: true, + ), + keyboardType: TextInputType.number, + validator: (value) { + if (value == null || + !value.isValidZip) { + return 'Bitte eine gültige Postleitzahl eingeben'; + } + return null; + }, + ), + ), + ], + ), + const SizedBox(height: 20), + TextFormField( + controller: _cityController, + autocorrect: false, + autofocus: true, + decoration: const InputDecoration( + label: Text('Stadt'), + filled: true, + ), + keyboardType: TextInputType.name, + validator: (value) { + if (value == null || !value.isValidCity) { + return 'Bitte einen gültigen Ort eingeben'; + } + return null; + }, + ), + const SizedBox(height: 20), + TextFormField( + controller: _countryController, + autocorrect: false, + autofocus: true, + readOnly: true, + decoration: const InputDecoration( + label: Text('Land'), + filled: true, + ), + keyboardType: TextInputType.name, + validator: (value) { + if (value == null || !value.isValidName) { + return 'Bitte einen gültigen Ort eingeben'; + } + return null; + }, + ), + const SizedBox(height: 20), + TextFormField( + controller: _phoneController, + autocorrect: false, + autofocus: true, + decoration: const InputDecoration( + label: Text('Telefonnummer'), + filled: true, + ), + keyboardType: TextInputType.phone, + validator: (value) { + if (value == null || !value.isValidPhone) { + return 'Bitte eine gültige Telefonnummer eingeben'; + } + return null; + }, + ), + const SizedBox(height: 20), + TextFormField( + onTap: () => _selectDate(context), + controller: _birthdayController, + autocorrect: false, + autofocus: true, + readOnly: true, + decoration: const InputDecoration( + label: Text('Geburtsdatum'), + filled: true, + ), + keyboardType: TextInputType.datetime, + validator: (value) { + if (value == null || value.isEmpty) { + return 'Bitte ein gültiges Datum eingeben'; + } + return null; + }, + ), + ], + ), + ), + ], + ), + ), + ), + const SizedBox(height: 20), + Hero( + tag: 'flow-button', + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: CustomColors.primary, + ), + onPressed: () async { + if (_formKey.currentState!.validate()) { + final resp = await _vm.createAccountInfo( + context, + firstname: _firstNameController.text, + lastname: _lastNameController.text, + city: _cityController.text, + country: _countryController.text, + zip: _zipController.text, + phoneNumber: _phoneController.text, + streetAddress: _streetController.text, + birthday: _birthday!, + ); + + if (resp) { + if (mounted) { + Navigator.push( + context, + MaterialPageRoute( + builder: (builder) => const LatePersonPage(), + ), + ); + } + } + } + }, + child: const SizedBox( + height: 60, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'Weiter', + style: TextStyle( + fontSize: 20, + ), + ), + ], + ), + ), + ), + ), + ], + ), + ); + } +} diff --git a/frontend/app/lib/pages/late_person_page.dart b/frontend/app/lib/pages/late_person_page.dart new file mode 100644 index 0000000..7e2a5cb --- /dev/null +++ b/frontend/app/lib/pages/late_person_page.dart @@ -0,0 +1,64 @@ +import 'package:app/model/services/storage_service.dart'; +import 'package:app/pages/loading_page.dart'; +import 'package:app/pages/notifications_page.dart'; +import 'package:app/util/colors.dart'; +import 'package:app/widgets/custom_scaffold.dart'; +import 'package:flutter/material.dart'; + +class LatePersonPage extends StatefulWidget { + const LatePersonPage({super.key}); + + @override + State createState() => _LatePersonPageState(); +} + +class _LatePersonPageState extends State { + final StorageService _storageService = StorageService(); + bool _loading = true; + + @override + void initState() { + _init(); + super.initState(); + } + + void _init() async { + if (await _storageService.accountLevel < 6) { + await _storageService.setAccountLevel(6); + } + setState(() { + _loading = false; + }); + } + + @override + Widget build(BuildContext context) { + return CustomScaffold( + backButton: BackButton( + color: CustomColors.primary, + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (builder) => const NotificationsPage())); + }, + ), + children: _loading + ? [ + LoadingPage(), + ] + : [ + const Text( + 'Daten der Verstorbenen', + textAlign: TextAlign.center, + style: TextStyle( + fontFamily: 'sans-serif', + fontWeight: FontWeight.bold, + letterSpacing: 2.0, + fontSize: 25, + ), + ), + ], + ); + } +} diff --git a/frontend/app/lib/pages/loading_page.dart b/frontend/app/lib/pages/loading_page.dart new file mode 100644 index 0000000..263cf4c --- /dev/null +++ b/frontend/app/lib/pages/loading_page.dart @@ -0,0 +1,18 @@ +import 'package:flutter/material.dart'; + +class LoadingPage extends StatelessWidget { + LoadingPage({super.key}); + + @override + Widget build(BuildContext context) { + return Center( + child: Hero( + tag: 'logo', + child: Image.asset( + 'assets/icons/icon.jpg', + // height: 180, + ), + ), + ); + } +} diff --git a/frontend/app/lib/pages/notifications_page.dart b/frontend/app/lib/pages/notifications_page.dart index b63214e..e7e4628 100644 --- a/frontend/app/lib/pages/notifications_page.dart +++ b/frontend/app/lib/pages/notifications_page.dart @@ -1,4 +1,6 @@ import 'package:app/model/services/storage_service.dart'; +import 'package:app/pages/account_info_page.dart'; +import 'package:app/pages/late_person_page.dart'; import 'package:app/pages/registration_page.dart'; import 'package:app/pages/security_page.dart'; import 'package:app/pages/verify_email_page.dart'; @@ -123,13 +125,39 @@ class _NotificationsPageState extends State { onPressed: () async { await _setNotificationSetting(true); if (await _storageService.accessToken != null) { - if (mounted) { - Navigator.push( - context, - MaterialPageRoute( - builder: (builder) => const VerifyEmailPage(), - ), - ); + if (await _storageService.verified) { + switch (await _storageService.accountLevel) { + case 4 || 5: + if (mounted) { + Navigator.push( + context, + MaterialPageRoute( + builder: (builder) => + const AccountInfoPage(), + ), + ); + } + case 6: + if (mounted) { + Navigator.push( + context, + MaterialPageRoute( + builder: (builder) => + const LatePersonPage(), + ), + ); + } + } + } else { + if (mounted) { + Navigator.push( + context, + MaterialPageRoute( + builder: (builder) => + const VerifyEmailPage(), + ), + ); + } } } else { if (mounted) { @@ -171,14 +199,38 @@ class _NotificationsPageState extends State { onPressed: () async { await _setNotificationSetting(false); if (await _storageService.accessToken != null) { - if (mounted) { - Navigator.push( - context, - MaterialPageRoute( - builder: (builder) => const VerifyEmailPage(), - // builder: (builder) => SecurityPage(), - ), - ); + if (await _storageService.verified) { + switch (await _storageService.accountLevel) { + case 4 || 5: + if (mounted) { + Navigator.push( + context, + MaterialPageRoute( + builder: (builder) => + const AccountInfoPage(), + ), + ); + } + case 6: + if (mounted) { + Navigator.push( + context, + MaterialPageRoute( + builder: (builder) => + const LatePersonPage(), + ), + ); + } + } + } else { + if (mounted) { + Navigator.push( + context, + MaterialPageRoute( + builder: (builder) => const VerifyEmailPage(), + ), + ); + } } } else { if (mounted) { @@ -186,7 +238,6 @@ class _NotificationsPageState extends State { context, MaterialPageRoute( builder: (builder) => const RegistrationPage(), - // builder: (builder) => SecurityPage(), ), ); } diff --git a/frontend/app/lib/pages/password_page.dart b/frontend/app/lib/pages/password_page.dart index 2def444..287711e 100644 --- a/frontend/app/lib/pages/password_page.dart +++ b/frontend/app/lib/pages/password_page.dart @@ -1,4 +1,5 @@ import 'package:app/model/view_model/base_vm.dart'; +import 'package:app/pages/account_info_page.dart'; import 'package:app/pages/verify_email_page.dart'; import 'package:app/util/colors.dart'; import 'package:flutter/material.dart'; @@ -201,7 +202,17 @@ class _PasswordPageState extends State { password: _passwordController1.text, ); } - if (loggedin && mounted) { + if (loggedin) { + FocusManager.instance.primaryFocus + ?.unfocus(); + final acc = await _vm.account; + if (acc!.emailVerified) { + navigator.push( + MaterialPageRoute( + builder: (builder) => + const AccountInfoPage()), + ); + } navigator.push( MaterialPageRoute( builder: (builder) => @@ -220,7 +231,7 @@ class _PasswordPageState extends State { children: [ Text( widget.register - ? 'Registrierung abschließen' + ? 'Konto erstellen' : 'Einloggen', style: const TextStyle( fontSize: 20, diff --git a/frontend/app/lib/pages/registration_page.dart b/frontend/app/lib/pages/registration_page.dart index ec87592..bc8a549 100644 --- a/frontend/app/lib/pages/registration_page.dart +++ b/frontend/app/lib/pages/registration_page.dart @@ -1,8 +1,10 @@ import 'package:app/model/services/storage_service.dart'; +import 'package:app/pages/loading_page.dart'; import 'package:app/pages/notifications_page.dart'; import 'package:app/pages/password_page.dart'; import 'package:app/util/colors.dart'; import 'package:app/util/validation.dart'; +import 'package:app/widgets/custom_scaffold.dart'; import 'package:flutter/material.dart'; class RegistrationPage extends StatefulWidget { @@ -42,169 +44,140 @@ class _RegistrationPageState extends State { @override Widget build(BuildContext context) { - return SafeArea( - child: _loading - ? Center( - child: Column( - children: [ - const SizedBox( - height: 150, - ), - Hero( - tag: 'logo', - child: Image.asset( - 'assets/JPEG.jpg', - height: 180, - ), - ), - CircularProgressIndicator( - color: CustomColors.primary, - ), - ], + return CustomScaffold( + backButton: BackButton( + color: CustomColors.primary, + onPressed: () { + Navigator.pushAndRemoveUntil( + context, + MaterialPageRoute( + builder: (builder) => const NotificationsPage()), + (route) => false); + }, + ), + children: _loading + ? [ + LoadingPage(), + ] + : [ + const SizedBox( + height: 20, ), - ) - : Scaffold( - appBar: AppBar( - leading: BackButton( - color: CustomColors.primary, - onPressed: () { - Navigator.pushAndRemoveUntil( - context, - MaterialPageRoute( - builder: (builder) => const NotificationsPage()), - (route) => false); - }, - ), - iconTheme: IconThemeData( - color: CustomColors.primary, + const Text( + 'Jetzt Registrieren', + textAlign: TextAlign.center, + style: TextStyle( + fontFamily: 'sans-serif', + fontWeight: FontWeight.bold, + letterSpacing: 2.0, + fontSize: 25, ), ), - body: Padding( - padding: const EdgeInsets.fromLTRB(20, 20, 20, 16), + const SizedBox( + height: 20, + ), + const Text( + 'Gib deine E-Mail Adresse ein.', + // textAlign: TextAlign.center, + ), + const SizedBox( + height: 20, + ), + Form( + key: formKey, child: Column( - // mainAxisAlignment: MainAxisAlignment.center, children: [ - const SizedBox( - height: 20, - ), - const Text( - 'Jetzt Registrieren', - textAlign: TextAlign.center, - style: TextStyle( - fontFamily: 'sans-serif', - fontWeight: FontWeight.bold, - letterSpacing: 2.0, - fontSize: 25, + TextFormField( + autocorrect: false, + autofocus: true, + controller: mailController, + keyboardType: TextInputType.emailAddress, + decoration: const InputDecoration( + helperText: 'test', + label: Text('E-Mail Adresse'), + filled: true, ), - ), - const SizedBox( - height: 20, - ), - const Text( - 'Gib deine E-Mail Adresse ein.', - // textAlign: TextAlign.center, - ), - const SizedBox( - height: 20, - ), - Form( - key: formKey, - child: Column( - children: [ - TextFormField( - autocorrect: false, - autofocus: true, - controller: mailController, - keyboardType: TextInputType.emailAddress, - decoration: const InputDecoration( - helperText: 'test', - label: Text('E-Mail Adresse'), - filled: true, - ), - validator: (value) { - if (value == null || !value.isValidEmail) { - return 'Bitte eine valide E-Mail Adresse angeben'; - } else { - return null; - } - }, - ), - ], - ), - ), - const SizedBox( - height: 20, - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - TextButton( - onPressed: () { - if (formKey.currentState!.validate()) { - Navigator.push( - context, - MaterialPageRoute( - builder: (builder) => PasswordPage( - email: mailController.text, - register: false, - ), - ), - ); - } - }, - child: Text( - 'Stattdessen anmelden', - // textAlign: TextAlign.center, - style: TextStyle( - color: CustomColors.primary, - ), - ), - ), - Hero( - tag: 'flow-button', - child: ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: CustomColors.primary, - ), - onPressed: () { - if (formKey.currentState!.validate()) { - Navigator.push( - context, - MaterialPageRoute( - builder: (builder) => PasswordPage( - email: mailController.text, - register: true, - ), - ), - ); - } - }, - child: const SizedBox( - height: 50, - width: 100, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - 'Weiter', - style: TextStyle( - fontSize: 20, - ), - ), - ], - ), - ), - ), - ), - ], - ), - const Spacer( - flex: 2, + validator: (value) { + if (value == null || !value.isValidEmail) { + return 'Bitte eine valide E-Mail Adresse angeben'; + } else { + return null; + } + }, ), ], ), ), - ), + const SizedBox( + height: 20, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + TextButton( + onPressed: () { + if (formKey.currentState!.validate()) { + Navigator.push( + context, + MaterialPageRoute( + builder: (builder) => PasswordPage( + email: mailController.text, + register: false, + ), + ), + ); + } + }, + child: Text( + 'Stattdessen anmelden', + // textAlign: TextAlign.center, + style: TextStyle( + color: CustomColors.primary, + ), + ), + ), + Hero( + tag: 'flow-button', + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: CustomColors.primary, + ), + onPressed: () { + if (formKey.currentState!.validate()) { + Navigator.push( + context, + MaterialPageRoute( + builder: (builder) => PasswordPage( + email: mailController.text, + register: true, + ), + ), + ); + } + }, + child: const SizedBox( + height: 50, + width: 100, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'Weiter', + style: TextStyle( + fontSize: 20, + ), + ), + ], + ), + ), + ), + ), + ], + ), + const Spacer( + flex: 2, + ), + ], ); } } diff --git a/frontend/app/lib/pages/security_page.dart b/frontend/app/lib/pages/security_page.dart index 64ddb46..e92fff4 100644 --- a/frontend/app/lib/pages/security_page.dart +++ b/frontend/app/lib/pages/security_page.dart @@ -1,8 +1,10 @@ import 'package:app/model/services/auth_service.dart'; import 'package:app/model/services/storage_service.dart'; +import 'package:app/pages/loading_page.dart'; import 'package:app/pages/notifications_page.dart'; import 'package:app/pages/start_page.dart'; import 'package:app/util/colors.dart'; +import 'package:app/widgets/custom_scaffold.dart'; import 'package:flutter/material.dart'; class SecurityPage extends StatefulWidget { @@ -33,151 +35,130 @@ class _SecurityPageState extends State { @override Widget build(BuildContext context) { - return SafeArea( - child: _loading - ? Center( - child: Column( - children: [ - const SizedBox( - height: 150, + return CustomScaffold( + backButton: BackButton( + color: CustomColors.primary, + onPressed: () { + Navigator.push( + context, + MaterialPageRoute(builder: (builder) => const StartPage()), + ); + }, + ), + children: _loading + ? [ + LoadingPage(), + // Center( + // child: Hero( + // tag: 'logo', + // child: Image.asset( + // 'assets/JPEG.jpg', + // height: 180, + // ), + // ), + // ), + ] + : [ + const Spacer(), + const Hero( + tag: 'flow-icon', + child: Icon( + Icons.fingerprint, + color: Colors.white, + size: 200, + ), + ), + const Spacer(), + const Text( + 'Deine Sicherheit kommt an erster Stelle', + textAlign: TextAlign.center, + style: TextStyle( + fontFamily: 'sans-serif', + fontSize: 25, + fontWeight: FontWeight.bold), + ), + const SizedBox( + height: 30, + ), + const Text( + 'Schütze dein Konto mit der biometrischen Erkennung deines Geräts oder lege einen Code fest.', + textAlign: TextAlign.center, + style: TextStyle( + fontFamily: 'sans-serif', + fontSize: 18, + fontWeight: FontWeight.bold), + ), + const Spacer(), + Hero( + tag: 'flow-button', + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: CustomColors.primary, ), - Hero( - tag: 'logo', - child: Image.asset( - 'assets/JPEG.jpg', - height: 180, + onPressed: () async { + bool isAuthenticated = + await AuthService.authenticateWithBiometrics(); + if (isAuthenticated) { + if (mounted) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const NotificationsPage()), + ); + } + } + }, + child: const SizedBox( + height: 60, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'App absichern', + style: TextStyle( + fontSize: 20, + ), + ), + ], ), ), - CircularProgressIndicator( - color: CustomColors.primary, - ), - ], - ), - ) - : Scaffold( - appBar: AppBar( - leading: BackButton( - color: CustomColors.primary, - onPressed: () { - Navigator.pushAndRemoveUntil( - context, - MaterialPageRoute( - builder: (builder) => const StartPage()), - (route) => false); - }, - ), - iconTheme: IconThemeData(color: CustomColors.primary), - ), - body: Padding( - padding: const EdgeInsets.fromLTRB(16, 20, 16, 16), - child: Center( - child: Column( - children: [ - const Spacer(), - const Hero( - tag: 'flow-icon', - child: Icon( - Icons.fingerprint, - color: Colors.white, - size: 200, - ), - ), - const Spacer(), - const Text( - 'Deine Sicherheit kommt an erster Stelle', - textAlign: TextAlign.center, - style: TextStyle( - fontFamily: 'sans-serif', - fontSize: 25, - fontWeight: FontWeight.bold), - ), - const SizedBox( - height: 30, - ), - const Text( - 'Schütze dein Konto mit der biometrischen Erkennung deines Geräts oder lege einen Code fest.', - textAlign: TextAlign.center, - style: TextStyle( - fontFamily: 'sans-serif', - fontSize: 18, - fontWeight: FontWeight.bold), - ), - const Spacer(), - Hero( - tag: 'flow-button', - child: ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: CustomColors.primary, - ), - onPressed: () async { - bool isAuthenticated = - await AuthService.authenticateWithBiometrics(); - if (isAuthenticated) { - if (mounted) { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => - const NotificationsPage()), - ); - } - } - }, - child: const SizedBox( - height: 60, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - 'App absichern', - style: TextStyle( - fontSize: 20, - ), - ), - ], - ), - ), - ), - ), - // const SizedBox( - // height: 10, - // ), - // ElevatedButton( - // style: ElevatedButton.styleFrom( - // backgroundColor: CustomColors.secondary, - // ), - // onPressed: () { - // Navigator.push( - // context, - // MaterialPageRoute( - // builder: (builder) => SecurityPage(), - // ), - // ); - // }, - // child: const SizedBox( - // height: 60, - // child: Row( - // mainAxisAlignment: MainAxisAlignment.center, - // children: [ - // Text( - // 'Eigenen Code festlegen', - // style: TextStyle( - // color: Colors.white, - // fontSize: 22, - // ), - // ), - // ], - // ), - // ), - // ), - const Spacer( - flex: 2, - ), - ], - ), ), ), - ), + // const SizedBox( + // height: 10, + // ), + // ElevatedButton( + // style: ElevatedButton.styleFrom( + // backgroundColor: CustomColors.secondary, + // ), + // onPressed: () { + // Navigator.push( + // context, + // MaterialPageRoute( + // builder: (builder) => SecurityPage(), + // ), + // ); + // }, + // child: const SizedBox( + // height: 60, + // child: Row( + // mainAxisAlignment: MainAxisAlignment.center, + // children: [ + // Text( + // 'Eigenen Code festlegen', + // style: TextStyle( + // color: Colors.white, + // fontSize: 22, + // ), + // ), + // ], + // ), + // ), + // ), + const Spacer( + flex: 2, + ), + ], ); } } diff --git a/frontend/app/lib/pages/start_page.dart b/frontend/app/lib/pages/start_page.dart index fe6e118..4149bf1 100644 --- a/frontend/app/lib/pages/start_page.dart +++ b/frontend/app/lib/pages/start_page.dart @@ -1,6 +1,7 @@ import 'package:app/pages/agb_page.dart'; import 'package:app/pages/security_page.dart'; import 'package:app/util/colors.dart'; +import 'package:app/widgets/custom_scaffold.dart'; import 'package:flutter/material.dart'; class StartPage extends StatelessWidget { @@ -8,105 +9,94 @@ class StartPage extends StatelessWidget { @override Widget build(BuildContext context) { - return SafeArea( - child: Scaffold( - appBar: AppBar( - iconTheme: IconThemeData(color: CustomColors.primary), - ), - body: Padding( - padding: const EdgeInsets.fromLTRB(16, 20, 16, 16), - child: Column( - children: [ - Hero( - tag: 'flow-icon', - child: Image.asset( - 'assets/JPEG.jpg', - height: 180, - ), - ), - const SizedBox( - height: 30, - ), - const Text( - 'Hallo. Digitale Spuren\nentfernen\nper Knopfdruck.', - textAlign: TextAlign.center, - style: TextStyle( - fontFamily: 'sans-serif', - fontWeight: FontWeight.bold, - letterSpacing: 2.0, - fontSize: 25, - ), - ), - const SizedBox( - height: 20, - ), - const Text( - 'Mit uns finden Sie Ihre Digitalen Spuren und können diese entfernen.', - textAlign: TextAlign.center, - style: TextStyle( - fontFamily: 'sans-serif', - fontSize: 18, - ), - ), - const Spacer( - flex: 1, - ), - Hero( - tag: 'flow-button', - child: ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: CustomColors.primary, - ), - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (builder) => const SecurityPage(), - // builder: (builder) => SecurityPage(), - ), - ); - }, - child: const SizedBox( - height: 60, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - 'Weiter', - style: TextStyle( - fontSize: 20, - ), - ), - ], - ), - ), - ), - ), - const Spacer( - flex: 1, - ), - const Text( - 'Mit der weiteren Nutzung stimmst du den folgenden Bedingungen zu:', - textAlign: TextAlign.center, - style: TextStyle( - fontFamily: 'sans-serif', - fontSize: 16, - ), - ), - TextButton( - onPressed: () { - showDialog( - context: context, builder: (builder) => AgbPage()); - }, - child: Text( - 'AGB - Datenschutzerklärung', - textAlign: TextAlign.center, - style: TextStyle(color: CustomColors.primary), - )) - ], + return CustomScaffold( + children: [ + Hero( + tag: 'flow-icon', + child: Image.asset( + 'assets/JPEG.jpg', + height: 180, ), ), - ), + const SizedBox( + height: 30, + ), + const Text( + 'Hallo. Digitale Spuren\nentfernen\nper Knopfdruck.', + textAlign: TextAlign.center, + style: TextStyle( + fontFamily: 'sans-serif', + fontWeight: FontWeight.bold, + letterSpacing: 2.0, + fontSize: 25, + ), + ), + const SizedBox( + height: 20, + ), + const Text( + 'Mit uns finden Sie Ihre Digitalen Spuren und können diese entfernen.', + textAlign: TextAlign.center, + style: TextStyle( + fontFamily: 'sans-serif', + fontSize: 18, + ), + ), + const Spacer( + flex: 1, + ), + Hero( + tag: 'flow-button', + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: CustomColors.primary, + ), + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (builder) => const SecurityPage(), + // builder: (builder) => SecurityPage(), + ), + ); + }, + child: const SizedBox( + height: 60, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'Weiter', + style: TextStyle( + fontSize: 20, + ), + ), + ], + ), + ), + ), + ), + const Spacer( + flex: 1, + ), + const Text( + 'Mit der weiteren Nutzung stimmst du den folgenden Bedingungen zu:', + textAlign: TextAlign.center, + style: TextStyle( + fontFamily: 'sans-serif', + fontSize: 16, + ), + ), + TextButton( + onPressed: () { + showDialog(context: context, builder: (builder) => AgbPage()); + }, + child: Text( + 'AGB - Datenschutzerklärung', + textAlign: TextAlign.center, + style: TextStyle(color: CustomColors.primary), + )) + ], ); } } diff --git a/frontend/app/lib/pages/verify_email_page.dart b/frontend/app/lib/pages/verify_email_page.dart index 0494fcf..973cd4a 100644 --- a/frontend/app/lib/pages/verify_email_page.dart +++ b/frontend/app/lib/pages/verify_email_page.dart @@ -1,5 +1,6 @@ import 'package:app/model/services/storage_service.dart'; import 'package:app/model/view_model/base_vm.dart'; +import 'package:app/pages/account_info_page.dart'; import 'package:app/pages/notifications_page.dart'; import 'package:app/util/colors.dart'; import 'package:flutter/material.dart'; @@ -113,7 +114,31 @@ class _VerifyEmailPageState extends State { style: ElevatedButton.styleFrom( backgroundColor: CustomColors.primary, ), - onPressed: () {}, + onPressed: () async { + final acc = await _vm.account; + if (acc != null && acc.emailVerified) { + if (mounted) { + Navigator.push( + context, + MaterialPageRoute( + builder: (builder) => + const AccountInfoPage()), + ); + } + } else { + if (mounted) { + ScaffoldMessenger.of(context).clearSnackBars(); + ScaffoldMessenger.of(context) + .showSnackBar(SnackBar( + backgroundColor: CustomColors.error, + content: const Text( + 'E-Mail Adresse ist noch nicht verifiziert.', + style: TextStyle(color: Colors.white), + ), + )); + } + } + }, child: const SizedBox( height: 50, width: 100, diff --git a/frontend/app/lib/util/validation.dart b/frontend/app/lib/util/validation.dart index 10f779c..33bd309 100644 --- a/frontend/app/lib/util/validation.dart +++ b/frontend/app/lib/util/validation.dart @@ -1,8 +1,12 @@ final emailRegExp = RegExp(r"^[a-zA-Z0-9.]+@[a-zA-Z0-9]+\.[a-zA-Z]+"); -final nameRegExp = - RegExp(r"^\s*([A-Za-z]{1,}([\.,] |[-']| ))+[A-Za-z]+\.?\s*$"); -final phoneRegExp = RegExp(r"^\+?0[0-9]{10}$"); -final passwordRegExp = RegExp(r'^[0-9a-zA-Z\-\_\.\,\*\+\=?!]{12,64}$'); +final nameRegExp = RegExp(r"^[a-zA-ZäöüÄÖÜß]+(?:[-\s][a-zA-ZäöüÄÖÜß]+)?$"); +final phoneRegExp = RegExp(r"^(?:\+49|0)[1-9][0-9\s\-\/]*$"); +final streetRegExp = RegExp(r"^[a-zA-ZäöüÄÖÜß\s\-\d]+$"); +final houseNumberRegExp = RegExp(r"^[0-9]+$"); +final zipRegExp = RegExp(r"^\d{5}$"); +final cityRegExp = RegExp(r"^[a-zA-ZäöüÄÖÜß\s\-\.,]+$"); +final passwordRegExp = RegExp( + r'^(?=.*[a-zäöü])(?=.*[A-ZÄÖÜ])(?=.*\d)(?=.*[@$!%*?&])[A-Za-zäöüÄÖÜ\d@$!%*?&]{8,}$'); extension valString on String { bool get isValidEmail { @@ -13,6 +17,22 @@ extension valString on String { return nameRegExp.hasMatch(this); } + bool get isValidStreetAddress { + return streetRegExp.hasMatch(this); + } + + bool get isValidHouseNumber { + return houseNumberRegExp.hasMatch(this); + } + + bool get isValidZip { + return zipRegExp.hasMatch(this); + } + + bool get isValidCity { + return cityRegExp.hasMatch(this); + } + bool get isValidPassword { return passwordRegExp.hasMatch(this); } diff --git a/frontend/app/lib/widgets/custom_scaffold.dart b/frontend/app/lib/widgets/custom_scaffold.dart new file mode 100644 index 0000000..b4bd3d6 --- /dev/null +++ b/frontend/app/lib/widgets/custom_scaffold.dart @@ -0,0 +1,33 @@ +import 'package:app/util/colors.dart'; +import 'package:flutter/material.dart'; + +class CustomScaffold extends StatelessWidget { + const CustomScaffold({ + super.key, + required this.children, + this.backButton, + }); + + final List children; + final Widget? backButton; + + @override + Widget build(BuildContext context) { + return SafeArea( + child: Scaffold( + resizeToAvoidBottomInset: false, + appBar: AppBar( + automaticallyImplyLeading: false, + leading: backButton, + iconTheme: IconThemeData(color: CustomColors.primary), + ), + body: Padding( + padding: const EdgeInsets.symmetric(horizontal: 45, vertical: 40), + child: Column( + children: children, + ), + ), + ), + ); + } +} diff --git a/frontend/app/pubspec.lock b/frontend/app/pubspec.lock index e313442..61d87c4 100644 --- a/frontend/app/pubspec.lock +++ b/frontend/app/pubspec.lock @@ -230,7 +230,7 @@ packages: source: hosted version: "4.0.2" intl: - dependency: "direct main" + dependency: transitive description: name: intl sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d" diff --git a/frontend/app/pubspec.yaml b/frontend/app/pubspec.yaml index 7b5cda3..a246fe6 100644 --- a/frontend/app/pubspec.yaml +++ b/frontend/app/pubspec.yaml @@ -46,7 +46,6 @@ dependencies: path: ^1.8.3 fixnum: ^1.1.0 provider: ^6.0.5 - intl: ^0.18.1 flutter_secure_storage: ^9.0.0 dev_dependencies: @@ -75,6 +74,7 @@ flutter: # To add assets to your application, add an assets section, like this: assets: - assets/chat_bubbles.png + - assets/icons/icon.jpg - assets/JPEG.jpg - lib/assets/logo_300x200.png - lib/assets/hero-pattern-300x200.png