ft/adds registration and secure_storage [alpha]

This commit is contained in:
itsscb 2023-11-11 01:10:45 +01:00
parent dc8d75e221
commit d0b93581b2
17 changed files with 757 additions and 52 deletions

View File

@ -45,7 +45,7 @@ android {
applicationId "com.example.app"
// You can update the following values to match your application needs.
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
minSdkVersion flutter.minSdkVersion
minSdkVersion 18
targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName

View File

@ -1,5 +1,10 @@
import 'package:app/model/services/auth_service.dart';
import 'package:app/model/services/storage_service.dart';
import 'package:app/pages/notifications_page.dart';
import 'package:app/pages/start_page.dart';
import 'package:app/pages_draft/home_page.dart';
import 'package:app/pages/verify_email_page.dart';
import 'package:app/pages/registration_page.dart';
import 'package:app/util/colors.dart';
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart'
show GlobalMaterialLocalizations;
@ -47,7 +52,78 @@ void main() async {
backgroundColor: Colors.black,
foregroundColor: Colors.white,
)),
home: StartPage(),
home: const DigitalerFrieden(),
),
);
}
class DigitalerFrieden extends StatefulWidget {
const DigitalerFrieden({super.key});
@override
State<DigitalerFrieden> createState() => _DigitalerFriedenState();
}
class _DigitalerFriedenState extends State<DigitalerFrieden> {
final StorageService _storageService = StorageService();
int? accountLevel;
bool? authenticated;
bool _loading = true;
@override
void initState() {
_init();
super.initState();
}
void _init() async {
accountLevel = await _storageService.accountLevel;
print(accountLevel!);
if (accountLevel! > 0) {
authenticated = await AuthService.authenticateWithBiometrics();
}
_loading = false;
setState(() {});
}
@override
Widget build(BuildContext context) {
if (_loading) {
return Center(
child: Column(
children: [
Image.asset(
'assets/JPEG.jpg',
height: 180,
),
CircularProgressIndicator(
color: CustomColors.primary,
),
],
),
);
}
if (accountLevel == null || accountLevel == 0) {
return const StartPage();
}
// else if (authenticated == null) {
// AuthService.authenticateWithBiometrics().then((value) {
// setState(() {
// authenticated = value;
// });
// });
// }
switch (accountLevel) {
case null:
return const StartPage();
case < 1:
return NotificationsPage();
case 1:
return const RegistrationPage();
case 2:
return VerifyEmailPage();
default:
return const StartPage();
}
}
}

View File

@ -131,7 +131,7 @@ class BackendService {
} on SocketException {
throw FetchDataException('Keine Internet Verbindung');
} on GrpcError catch (err) {
throw FetchDataException(err.message);
throw FetchDataException('${err.message}');
} catch (err) {
throw InternalException(err.toString());
}

View File

@ -0,0 +1,84 @@
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
class StorageItem {
StorageItem(this.key, this.value);
final String key;
final String value;
}
class StorageService {
final _secureStorage = const FlutterSecureStorage(
aOptions: AndroidOptions(
encryptedSharedPreferences: true,
),
);
Future<void> writeData(StorageItem item) async {
await _secureStorage.write(
key: item.key,
value: item.value,
);
}
Future<String?> readData(String key) async {
return await _secureStorage.read(key: key);
}
Future<List<StorageItem>> readAllSecureData() async {
var allData = await _secureStorage.readAll();
List<StorageItem> list =
allData.entries.map((e) => StorageItem(e.key, e.value)).toList();
return list;
}
Future<bool> containsData(String key) async {
return await _secureStorage.containsKey(key: key);
}
Future<void> deleteSecureData(StorageItem item) async {
await _secureStorage.delete(
key: item.key,
);
}
Future<void> deleteAllSecureData() async {
await _secureStorage.deleteAll();
}
Future<void> setNotificationSetting(bool enabled) async {
return await writeData(StorageItem('notifications', enabled ? '1' : '0'));
}
Future<bool> get notificationSetting async {
final enabled = await readData('notifications') == '1' ? true : false;
return enabled;
}
Future<int> get accountLevel async {
int? level;
final l = await readData('account_level');
if (l != null) {
level = int.tryParse(l);
}
return level ?? 0;
}
Future<void> setAccountLevel(int level) async {
return await writeData(StorageItem('account_level', '$level'));
}
Future<void> initAccountLevel() async {
return await writeData(StorageItem('account_level', '0'));
}
Future<void> addAccountLevel() async {
int? level;
final l = await readData('account_level');
if (l != null) {
level = int.tryParse(l);
}
return await writeData(
StorageItem('account_level', '${level != null ? level + 1 : 1}'));
}
}

View File

@ -1,9 +1,21 @@
import 'package:app/model/services/storage_service.dart';
import 'package:app/pages/registration_page.dart';
import 'package:app/util/colors.dart';
import 'package:flutter/material.dart';
class NotificationsPage extends StatelessWidget {
const NotificationsPage({super.key});
NotificationsPage({super.key});
final StorageService _storageService = StorageService();
void _setNotificationSetting(bool enabled) {
_storageService.addAccountLevel();
_storageService.setNotificationSetting(enabled).then(
(x) => _storageService.notificationSetting.then(
(value) => print('notifications: $value'),
),
);
}
@override
Widget build(BuildContext context) {
@ -60,6 +72,7 @@ class NotificationsPage extends StatelessWidget {
backgroundColor: CustomColors.primary,
),
onPressed: () {
_setNotificationSetting(true);
Navigator.push(
context,
MaterialPageRoute(
@ -88,7 +101,9 @@ class NotificationsPage extends StatelessWidget {
height: 10,
),
TextButton(
onPressed: () {},
onPressed: () {
_setNotificationSetting(false);
},
child: Text(
'Später',
style: TextStyle(color: CustomColors.primary),

View File

@ -0,0 +1,219 @@
import 'package:app/model/services/storage_service.dart';
import 'package:app/model/view_model/base_vm.dart';
import 'package:app/pages/verify_email_page.dart';
import 'package:app/util/colors.dart';
import 'package:flutter/material.dart';
import 'package:app/util/validation.dart';
class PasswordPage extends StatefulWidget {
PasswordPage({super.key, required this.email, required this.register});
final String email;
final bool register;
@override
State<PasswordPage> createState() => _PasswordPageState();
}
class _PasswordPageState extends State<PasswordPage> {
final BaseViewModel _vm = BaseViewModel();
final StorageService _storageService = StorageService();
final _formKey = GlobalKey<FormState>();
final _passwordController1 = TextEditingController();
final _passwordController2 = TextEditingController();
bool _validPassword = false;
bool _passwordsFilled = false;
bool _showPassword1 = false;
bool _showPassword2 = false;
@override
void dispose() {
_passwordController1.dispose();
_passwordController2.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
iconTheme: IconThemeData(
color: CustomColors.primary,
),
),
body: Padding(
padding: const EdgeInsets.all(20.0),
child: Form(
key: _formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
'Sichere dein Konto mit einem Passwort',
textAlign: TextAlign.center,
style: TextStyle(
fontFamily: 'sans-serif',
fontWeight: FontWeight.bold,
letterSpacing: 2.0,
fontSize: 25,
),
),
const SizedBox(
height: 60,
),
TextFormField(
controller: _passwordController1,
autocorrect: false,
autofocus: true,
keyboardType: TextInputType.visiblePassword,
obscureText: !_showPassword1,
autovalidateMode: AutovalidateMode.always,
decoration: InputDecoration(
suffixIcon: IconButton(
onPressed: () {
setState(() {
_showPassword1 = !_showPassword1;
});
},
icon: Icon(_showPassword1
? Icons.remove_red_eye
: Icons.remove_red_eye_outlined)),
label: const Text('Passwort'),
filled: true,
),
validator: (value) {
if (value == null || !value.isValidPassword) {
_validPassword = false;
return 'Mindestens 12 Zeichen, Zahlen, Sonderzeichen (-_?!=.,*+), Groß- & Kleinbuchstaben';
} else {
_validPassword = true;
return null;
}
},
onChanged: (value) {
_formKey.currentState?.validate();
if (!value.isValidPassword) {
setState(() {
_validPassword = false;
});
} else {
setState(() {
_validPassword = true;
});
}
},
),
const SizedBox(
height: 20,
),
AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
child: !_validPassword
? null
: TextFormField(
controller: _passwordController2,
keyboardType: TextInputType.visiblePassword,
obscureText: !_showPassword2,
decoration: InputDecoration(
suffixIcon: IconButton(
onPressed: () {
setState(() {
_showPassword2 = !_showPassword2;
});
},
icon: Icon(_showPassword2
? Icons.remove_red_eye
: Icons.remove_red_eye_outlined)),
label: const Text('Passwort bestätigen'),
filled: true,
),
validator: (value) {
if (_passwordController1.text !=
_passwordController2.text) {
setState(() {
_passwordsFilled = false;
});
return 'Passwörter stimmen nicht überein';
} else {
setState(() {
_passwordsFilled = true;
});
return null;
}
},
onTap: () => _formKey.currentState?.validate(),
onChanged: (value) {
_formKey.currentState?.validate();
if (_passwordController1.text !=
_passwordController2.text) {
setState(() {
_passwordsFilled = false;
});
} else {
setState(() {
_passwordsFilled = true;
});
}
},
),
),
const SizedBox(
height: 20,
),
AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
child: !_passwordsFilled ||
_passwordController1.text != _passwordController2.text
? null
: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: CustomColors.primary,
),
onPressed: _validPassword && _passwordsFilled
? () async {
if (_formKey.currentState!.validate()) {
final navigator = Navigator.of(context);
final loggedin = await _vm.createAccount(
context,
email: widget.email,
password: _passwordController1.text,
);
if (loggedin && mounted) {
_storageService.addAccountLevel();
navigator.push(
MaterialPageRoute(
builder: (builder) => VerifyEmailPage(),
),
);
}
}
}
: null,
child: const SizedBox(
height: 50,
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Registrierung abschließen',
style: TextStyle(
fontSize: 20,
),
),
],
),
),
),
),
],
),
),
),
);
}
}

View File

@ -1,10 +1,25 @@
import 'package:app/pages_old/start_page.dart';
import 'package:app/pages/password_page.dart';
import 'package:app/util/colors.dart';
import 'package:app/util/validation.dart';
import 'package:flutter/material.dart';
class RegistrationPage extends StatelessWidget {
class RegistrationPage extends StatefulWidget {
const RegistrationPage({super.key});
@override
State<RegistrationPage> createState() => _RegistrationPageState();
}
class _RegistrationPageState extends State<RegistrationPage> {
final formKey = GlobalKey<FormState>();
final mailController = TextEditingController();
@override
void dispose() {
mailController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return SafeArea(
@ -42,56 +57,99 @@ class RegistrationPage extends StatelessWidget {
const SizedBox(
height: 20,
),
const Text(
'Du kannst die Mitteilungen jederzeit wieder deaktivieren.',
textAlign: TextAlign.center,
),
const Spacer(
flex: 2,
),
Hero(
tag: 'flow-button',
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: CustomColors.primary,
),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (builder) => StartPage(),
// builder: (builder) => SecurityPage(),
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,
),
);
},
child: const SizedBox(
height: 60,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Mitteilungen erhalten',
style: TextStyle(
fontSize: 20,
),
),
],
validator: (value) {
if (value == null || !value.isValidEmail) {
return 'Bitte eine valide E-Mail Adresse angeben';
} else {
return null;
}
},
),
),
],
),
),
const SizedBox(
height: 10,
height: 20,
),
TextButton(
onPressed: () {},
child: Text(
'Später',
style: TextStyle(color: CustomColors.primary),
),
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: 1,
flex: 2,
),
],
),

View File

@ -0,0 +1,110 @@
import 'package:app/util/colors.dart';
import 'package:flutter/material.dart';
class VerifyEmailPage extends StatelessWidget {
VerifyEmailPage({super.key});
@override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
appBar: AppBar(
iconTheme: IconThemeData(
color: CustomColors.primary,
),
),
body: Padding(
padding: const EdgeInsets.fromLTRB(20, 20, 20, 16),
child: Column(
// mainAxisAlignment: MainAxisAlignment.center,
children: [
const SizedBox(
height: 80,
),
const Text(
'Verifizieren',
textAlign: TextAlign.center,
style: TextStyle(
fontFamily: 'sans-serif',
fontWeight: FontWeight.bold,
letterSpacing: 2.0,
fontSize: 25,
),
),
const SizedBox(
height: 50,
),
const Text(
'Wir haben dir eine E-Mail geschickt.',
textAlign: TextAlign.center,
// textAlign: TextAlign.center,
),
const SizedBox(
height: 20,
),
const Text(
'Bitte verifiziere deine E-Mail Adresse, dann geht es weiter.',
textAlign: TextAlign.center,
// textAlign: TextAlign.center,
),
const SizedBox(
height: 80,
),
Hero(
tag: 'flow-button',
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: CustomColors.primary,
),
onPressed: () {},
child: const SizedBox(
height: 50,
width: 100,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Weiter',
style: TextStyle(
fontSize: 20,
),
),
],
),
),
),
),
const SizedBox(
height: 60,
),
const Text(
'Noch keine E-Mail erhalten?',
// textAlign: TextAlign.center,
),
const Text(
'Schon im Spam-Ordner nachgeschaut?',
// textAlign: TextAlign.center,
),
const SizedBox(
height: 20,
),
TextButton(
onPressed: () {},
child: Text(
'Erneut senden',
// textAlign: TextAlign.center,
style: TextStyle(
color: CustomColors.primary,
),
),
),
const Spacer(
flex: 2,
),
],
),
),
),
);
}
}

View File

@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
class CustomColors {
static Color get error {
return const Color.fromARGB(200, 255, 90, 90);
return const Color.fromARGB(255, 255, 82, 82);
}
static Color get success {

View File

View File

@ -2,7 +2,7 @@ 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'^.+$');
final passwordRegExp = RegExp(r'^[0-9a-zA-Z\-\_\.\,\*\+\=?!]{12,64}$');
extension valString on String {
bool get isValidEmail {

View File

@ -6,6 +6,10 @@
#include "generated_plugin_registrant.h"
#include <flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h>
void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin");
flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar);
}

View File

@ -3,6 +3,7 @@
#
list(APPEND FLUTTER_PLUGIN_LIST
flutter_secure_storage_linux
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST

View File

@ -89,6 +89,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.3.1"
ffi:
dependency: transitive
description:
name: ffi
sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878"
url: "https://pub.dev"
source: hosted
version: "2.1.0"
fixnum:
dependency: "direct main"
description:
@ -123,11 +131,64 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.0.17"
flutter_secure_storage:
dependency: "direct main"
description:
name: flutter_secure_storage
sha256: ffdbb60130e4665d2af814a0267c481bcf522c41ae2e43caf69fa0146876d685
url: "https://pub.dev"
source: hosted
version: "9.0.0"
flutter_secure_storage_linux:
dependency: transitive
description:
name: flutter_secure_storage_linux
sha256: "3d5032e314774ee0e1a7d0a9f5e2793486f0dff2dd9ef5a23f4e3fb2a0ae6a9e"
url: "https://pub.dev"
source: hosted
version: "1.2.0"
flutter_secure_storage_macos:
dependency: transitive
description:
name: flutter_secure_storage_macos
sha256: bd33935b4b628abd0b86c8ca20655c5b36275c3a3f5194769a7b3f37c905369c
url: "https://pub.dev"
source: hosted
version: "3.0.1"
flutter_secure_storage_platform_interface:
dependency: transitive
description:
name: flutter_secure_storage_platform_interface
sha256: "0d4d3a5dd4db28c96ae414d7ba3b8422fd735a8255642774803b2532c9a61d7e"
url: "https://pub.dev"
source: hosted
version: "1.0.2"
flutter_secure_storage_web:
dependency: transitive
description:
name: flutter_secure_storage_web
sha256: "30f84f102df9dcdaa2241866a958c2ec976902ebdaa8883fbfe525f1f2f3cf20"
url: "https://pub.dev"
source: hosted
version: "1.1.2"
flutter_secure_storage_windows:
dependency: transitive
description:
name: flutter_secure_storage_windows
sha256: "5809c66f9dd3b4b93b0a6e2e8561539405322ee767ac2f64d084e2ab5429d108"
url: "https://pub.dev"
source: hosted
version: "3.0.0"
flutter_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
flutter_web_plugins:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
googleapis_auth:
dependency: transitive
description:
@ -272,6 +333,62 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.8.3"
path_provider:
dependency: transitive
description:
name: path_provider
sha256: a1aa8aaa2542a6bc57e381f132af822420216c80d4781f7aa085ca3229208aaa
url: "https://pub.dev"
source: hosted
version: "2.1.1"
path_provider_android:
dependency: transitive
description:
name: path_provider_android
sha256: e595b98692943b4881b219f0a9e3945118d3c16bd7e2813f98ec6e532d905f72
url: "https://pub.dev"
source: hosted
version: "2.2.1"
path_provider_foundation:
dependency: transitive
description:
name: path_provider_foundation
sha256: "19314d595120f82aca0ba62787d58dde2cc6b5df7d2f0daf72489e38d1b57f2d"
url: "https://pub.dev"
source: hosted
version: "2.3.1"
path_provider_linux:
dependency: transitive
description:
name: path_provider_linux
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
url: "https://pub.dev"
source: hosted
version: "2.2.1"
path_provider_platform_interface:
dependency: transitive
description:
name: path_provider_platform_interface
sha256: "94b1e0dd80970c1ce43d5d4e050a9918fce4f4a775e6142424c30a29a363265c"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
path_provider_windows:
dependency: transitive
description:
name: path_provider_windows
sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170"
url: "https://pub.dev"
source: hosted
version: "2.2.1"
platform:
dependency: transitive
description:
name: platform
sha256: "0a279f0707af40c890e80b1e9df8bb761694c074ba7e1d4ab1bc4b728e200b59"
url: "https://pub.dev"
source: hosted
version: "3.1.3"
plugin_platform_interface:
dependency: transitive
description:
@ -405,6 +522,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.1.4-beta"
win32:
dependency: transitive
description:
name: win32
sha256: "350a11abd2d1d97e0cc7a28a81b781c08002aa2864d9e3f192ca0ffa18b06ed3"
url: "https://pub.dev"
source: hosted
version: "5.0.9"
xdg_directories:
dependency: transitive
description:
name: xdg_directories
sha256: "589ada45ba9e39405c198fe34eb0f607cddb2108527e658136120892beac46d2"
url: "https://pub.dev"
source: hosted
version: "1.0.3"
sdks:
dart: ">=3.1.4 <4.0.0"
flutter: ">=3.10.0"

View File

@ -47,6 +47,7 @@ dependencies:
fixnum: ^1.1.0
provider: ^6.0.5
intl: ^0.18.1
flutter_secure_storage: ^9.0.0
dev_dependencies:
lints: ^2.0.0

View File

@ -6,9 +6,12 @@
#include "generated_plugin_registrant.h"
#include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h>
#include <local_auth_windows/local_auth_plugin.h>
void RegisterPlugins(flutter::PluginRegistry* registry) {
FlutterSecureStorageWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin"));
LocalAuthPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("LocalAuthPlugin"));
}

View File

@ -3,6 +3,7 @@
#
list(APPEND FLUTTER_PLUGIN_LIST
flutter_secure_storage_windows
local_auth_windows
)