ft/adds account_info and late_person pages

This commit is contained in:
itsscb 2023-11-19 23:52:46 +01:00
parent cc6841448d
commit ebe3426c2c
17 changed files with 1075 additions and 441 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

View File

@ -1,6 +1,8 @@
import 'package:app/model/services/auth_service.dart';
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/late_person_page.dart';
import 'package:app/pages/notifications_page.dart';
import 'package:app/pages/registration_page.dart';
import 'package:app/pages/security_page.dart';
@ -110,30 +112,30 @@ class _DigitalerFriedenState extends State<DigitalerFrieden> {
@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();
}

View File

@ -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<bool> 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<bool> 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');

View File

@ -149,6 +149,67 @@ class BaseViewModel with ChangeNotifier {
notifyListeners();
}
Future<bool> 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<void> logout() async {
_apiResponse = ApiResponse.loading('Logge aus');
notifyListeners();

View File

@ -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<AccountInfoPage> createState() => _AccountInfoPageState();
}
class _AccountInfoPageState extends State<AccountInfoPage> {
bool _loading = true;
final StorageService _storageService = StorageService();
final BaseViewModel _vm = BaseViewModel();
final _formKey = GlobalKey<FormState>();
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<void> _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,
),
),
],
),
),
),
),
],
),
);
}
}

View File

@ -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<LatePersonPage> createState() => _LatePersonPageState();
}
class _LatePersonPageState extends State<LatePersonPage> {
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,
),
),
],
);
}
}

View File

@ -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,
),
),
);
}
}

View File

@ -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<NotificationsPage> {
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<NotificationsPage> {
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<NotificationsPage> {
context,
MaterialPageRoute(
builder: (builder) => const RegistrationPage(),
// builder: (builder) => SecurityPage(),
),
);
}

View File

@ -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<PasswordPage> {
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<PasswordPage> {
children: [
Text(
widget.register
? 'Registrierung abschließen'
? 'Konto erstellen'
: 'Einloggen',
style: const TextStyle(
fontSize: 20,

View File

@ -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<RegistrationPage> {
@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,
),
],
);
}
}

View File

@ -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<SecurityPage> {
@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,
),
],
);
}
}

View File

@ -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),
))
],
);
}
}

View File

@ -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<VerifyEmailPage> {
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,

View File

@ -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);
}

View File

@ -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<Widget> 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,
),
),
),
);
}
}

View File

@ -230,7 +230,7 @@ packages:
source: hosted
version: "4.0.2"
intl:
dependency: "direct main"
dependency: transitive
description:
name: intl
sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d"

View File

@ -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