Merge branch 'ft/app'

This commit is contained in:
itsscb 2023-11-12 23:56:07 +01:00
commit 851dff40c8
49 changed files with 2340 additions and 97 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,4 +1,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
<!-- <uses-permission android:name="android.permission.USE_FINGERPRINT"/> -->
<application
android:label="app"
android:name="${applicationName}"

View File

@ -1,6 +1,11 @@
package com.example.app
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.android.FlutterFragmentActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugins.GeneratedPluginRegistrant
class MainActivity: FlutterActivity() {
class MainActivity: FlutterFragmentActivity() {
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine)
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 332 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

View File

@ -0,0 +1,69 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.3.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 3000 2000" style="enable-background:new 0 0 3000 2000;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
</style>
<rect x="0" y="0" width="3000" height="2000"/>
<g>
<g>
<g>
<g>
<path class="st0" d="M1643.8,246.59l-62.38,104.92l57.67,47.96l46.96-78.8h471.2l44.11-74.08H1643.8z"/>
</g>
<g>
<g>
<polygon class="st0" points="2039.49,518.38 1995.24,592.61 1524.04,592.61 1285.51,993.15 1199.15,993.15 1507.91,474.13
1507.63,473.84 1323.2,320.82 929.22,320.82 1212,795.3 1168.89,867.68 798.75,246.59 1350.04,246.59 1604.26,457.85
1568.29,518.38 "/>
</g>
</g>
</g>
<g>
<g>
<g>
<path class="st0" d="M462.6,1250.09h-89.07v220.83h89.07c72.8,0,126.61-42.97,126.61-110.63
C589.21,1292.91,535.4,1250.09,462.6,1250.09z M462.6,1446.22h-60.38v-171.58h60.38c58.81,0,97.07,34.12,97.07,85.65
C559.67,1412.11,521.41,1446.22,462.6,1446.22z"/>
<path class="st0" d="M701.13,1250.09v220.83h28.55v-220.83H701.13z"/>
<path class="st0" d="M1092.68,1354.01H967.63v23.84l94.64,0.43c-2.28,10.56-6,20.13-11.13,29.26c-3,5.57-7.28,10.56-11.7,15.27
l-0.57,0.57c-15.27,14.85-36.54,24.55-62.24,26.55c-3,0.43-6.28,0.43-9.56,0.43h-3.71c-35.83-1.14-64.38-16.99-79.65-42.25
h-0.43c-7.85-13.56-12.56-29.55-12.56-47.82c0-52.82,39.54-89.64,96.35-89.64c37.83,0,68.38,15.99,84.08,42.82l32.12-0.43
c-18.56-41.11-62.09-67.09-116.19-67.09c-72.37,0-125.9,46.96-125.9,114.34c0,66.8,51.82,113.63,122.91,114.62h9.56
c3.71-0.28,7.71-0.71,11.42-0.71l6.28-1c11.85-2,23.55-5.28,33.55-9.56c13.27-5.71,25.12-13.28,34.83-22.55
c0.29-0.43,0.71-0.71,0.71-1.14c4.85-4.71,9.56-9.85,13.27-15.56l0.86-1c11.42-16.56,17.7-36.11,18.7-58.1
C1093.25,1358.72,1092.82,1356.72,1092.68,1354.01z"/>
<path class="st0" d="M1201.16,1250.09v220.83h28.41v-220.83H1201.16z"/>
<path class="st0" d="M1329.49,1250.09v24.27h83.65v196.56h28.55v-196.56h84.08v-24.27H1329.49z"/>
<path class="st0" d="M1704.2,1250.09h-32.97l-101.21,220.83h31.83l25.98-55.53h120.76l25.27,55.53h31.55L1704.2,1250.09z
M1639.11,1391.55l48.82-112.91l49.82,112.91H1639.11z"/>
<path class="st0" d="M1934.3,1446.65v-196.56h-28.41v220.83h158.31v-24.27H1934.3z"/>
<path class="st0" d="M2193.24,1446.65v-75.37h119.76v-23.98h-119.76v-72.94h139.18v-24.27h-167.73v220.83h170.72v-24.27
H2193.24z"/>
<path class="st0" d="M2547.39,1368.28h3c45.82,0,73.8-25.27,73.8-59.52c0-34.4-27.98-58.38-73.8-58.38h-108.63v220.83h28.26
l0.29-102.92h44.11l75.51,102.63h36.54L2547.39,1368.28z M2470.31,1343.59v-69.23h80.08c28.55,0,44.11,12.42,44.11,34.12
c0,21.27-15.56,35.12-44.11,35.12H2470.31z"/>
<g>
<polygon class="st0" points="817.17,1753.32 831.98,1753.32 831.98,1696.16 903.01,1696.16 903.01,1682.67 831.98,1682.67
831.98,1635.82 911.43,1635.82 911.43,1622.14 817.17,1622.14 "/>
<path class="st0" d="M1097.67,1699.74c20.56-3.85,35.54-16.56,35.54-38.4c0-23.7-18.7-39.11-47.39-39.11h-56.39v131.18h14.7
v-50.96h37.54l37.97,50.96h18.27L1097.67,1699.74z M1044.14,1689.03v-53.24h40.54c21.13,0,33.55,9.85,33.55,25.98
c0,17.13-14.13,27.26-33.69,27.26H1044.14z"/>
<rect x="1257.67" y="1622.14" class="st0" width="14.81" height="131.18"/>
<polygon class="st0" points="1417.74,1693.91 1489.32,1693.91 1489.32,1680.42 1417.74,1680.42 1417.74,1635.63
1497.76,1635.63 1497.76,1622.14 1402.93,1622.14 1402.93,1753.32 1498.7,1753.32 1498.7,1739.83 1417.74,1739.83 "/>
<path class="st0" d="M1663.8,1622.23h-45.54v131.18h45.54c41.11,0,69.66-28.83,69.66-65.81
C1733.46,1650.49,1704.91,1622.23,1663.8,1622.23z M1663.8,1739.56h-30.83v-103.78h30.83c33.12,0,54.24,22.98,54.24,52.1
C1718.04,1717.15,1696.92,1739.56,1663.8,1739.56z"/>
<polygon class="st0" points="1871.52,1693.91 1943.11,1693.91 1943.11,1680.42 1871.52,1680.42 1871.52,1635.63
1951.54,1635.63 1951.54,1622.14 1856.72,1622.14 1856.72,1753.32 1952.47,1753.32 1952.47,1739.83 1871.52,1739.83 "/>
<polygon class="st0" points="2168.59,1622.14 2168.59,1727.27 2085.95,1622.14 2072.09,1622.14 2072.09,1753.32
2086.52,1753.32 2086.52,1645.76 2171.22,1753.32 2183.04,1753.32 2183.04,1622.14 "/>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

@ -1,12 +1,20 @@
import 'package:app/pages/home_page.dart';
import 'package:app/model/services/auth_service.dart';
import 'package:app/model/services/storage_service.dart';
import 'package:app/pages/start_page.dart';
import 'package:app/util/colors.dart';
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart'
show GlobalMaterialLocalizations;
void main() async {
WidgetsFlutterBinding.ensureInitialized();
runApp(
MaterialApp(
localizationsDelegates: const [GlobalMaterialLocalizations.delegate],
supportedLocales: const [Locale('en'), Locale('de')],
theme: ThemeData().copyWith(
canvasColor: Colors.black,
colorScheme: const ColorScheme(
brightness: Brightness.dark,
primary: Colors.white,
@ -41,9 +49,64 @@ void main() async {
backgroundColor: Colors.black,
foregroundColor: Colors.white,
)),
home: HomePage(
loggedOut: false,
),
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;
if (accountLevel! > 0) {
authenticated = await AuthService.authenticateWithBiometrics();
}
setState(() {
_loading = false;
});
}
@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();
}
}

View File

@ -4,6 +4,7 @@ class AppException implements Exception {
AppException([this._message, this._prefix]);
@override
String toString() {
return "$_prefix$_message";
}

View File

@ -0,0 +1,43 @@
import 'package:flutter/services.dart';
import 'package:local_auth/local_auth.dart';
class AuthService {
static Future<bool> authenticateWithBiometrics() async {
//initialize Local Authentication plugin.
final LocalAuthentication localAuthentication = LocalAuthentication();
//status of authentication.
bool isAuthenticated = false;
//check if device supports biometrics authentication.
bool isBiometricSupported = await localAuthentication.isDeviceSupported();
//check if user has enabled biometrics.
//check
bool canCheckBiometrics = await localAuthentication.canCheckBiometrics;
//if device supports biometrics and user has enabled biometrics, then authenticate.
if (isBiometricSupported && canCheckBiometrics) {
// ignore: use_build_context_synchronously
// final messenger = ScaffoldMessenger.of(context);
try {
isAuthenticated = await localAuthentication.authenticate(
localizedReason: 'Scan your fingerprint to authenticate',
options: const AuthenticationOptions(
biometricOnly: false,
useErrorDialogs: true,
stickyAuth: true,
),
);
} on PlatformException catch (err) {
print(err);
// messenger.showSnackBar(SnackBar(
// backgroundColor: CustomColors.error,
// content: Text(
// 'Fehler beim Einrichten der Biometrie: $err',
// style: const TextStyle(color: Colors.white),
// ),
// ));
}
}
print(isAuthenticated);
return isAuthenticated;
}
}

View File

@ -14,6 +14,7 @@ import 'package:app/pb/rpc_get_person.pb.dart';
import 'package:app/pb/rpc_list_persons.pb.dart';
import 'package:app/pb/rpc_login.pb.dart';
import 'package:app/pb/rpc_refresh_token.pb.dart';
import 'package:app/pb/rpc_update_person.pb.dart';
import 'package:app/pb/service_df.pbgrpc.dart';
import 'package:fixnum/fixnum.dart';
import 'package:grpc/grpc.dart';
@ -114,6 +115,10 @@ class BackendService {
return true;
}
static Future<Int64?> get accountId async {
return (await Session.session).accountId;
}
static Future<bool> createAccount(
{required String email, required String password}) async {
try {
@ -126,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());
}
@ -207,14 +212,15 @@ class BackendService {
}
}
Future<Person> createPerson(
{required String firstname,
required String lastname,
required String street,
required String zip,
required String city,
required String country,
required DateTime birthday}) async {
Future<Person> updatePerson(
{required Int64 id,
String? firstname,
String? lastname,
String? street,
String? zip,
String? city,
String? country,
Timestamp? birthday}) async {
Session session = await Session.session;
if (session.accessTokenExpiresAt == null) {
throw UnauthorizedException('Keine Siztung gefunden');
@ -226,16 +232,74 @@ class BackendService {
}
}
try {
final CreatePersonResponse response = await _client.createPerson(
CreatePersonRequest(
accountId: session.accountId,
lastname: lastname,
firstname: firstname,
street: street,
zip: zip,
country: country,
birthday: Timestamp.fromDateTime(birthday),
),
final UpdatePersonRequest req = UpdatePersonRequest(
id: id,
);
if (lastname != null) {
req.lastname = lastname;
}
if (firstname != null) {
req.firstname = firstname;
}
if (street != null) {
req.street = street;
}
if (city != null) {
req.city = city;
}
if (zip != null) {
req.zip = zip;
}
if (country != null) {
req.country = country;
}
if (birthday != null) {
req.birthday = birthday;
}
final UpdatePersonResponse response = await _client.updatePerson(req,
options: CallOptions(
metadata: {'Authorization': 'Bearer ${session.accessToken}'}));
return response.person;
} on SocketException {
throw FetchDataException('Keine Internet Verbindung');
} on GrpcError catch (err) {
throw FetchDataException(err.message);
} catch (err) {
throw InternalException(err.toString());
}
}
Future<Person> createPerson(
{required String firstname,
required String lastname,
required String street,
required String zip,
required String city,
required String country,
required Timestamp birthday}) async {
Session session = await Session.session;
if (session.accessTokenExpiresAt == null) {
throw UnauthorizedException('Keine Siztung gefunden');
}
if (session.accessTokenExpiresAt!.toDateTime().isBefore(DateTime.now())) {
session = await refreshToken(session);
if (session.accessTokenExpiresAt == null) {
throw UnauthorizedException('Sitzung ist abgelaufen');
}
}
try {
final CreatePersonRequest req = CreatePersonRequest(
accountId: session.accountId,
lastname: lastname,
firstname: firstname,
street: street,
city: city,
zip: zip,
country: country,
birthday: birthday,
);
final CreatePersonResponse response = await _client.createPerson(req,
options: CallOptions(
metadata: {'Authorization': 'Bearer ${session.accessToken}'}));
return response.person;

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 lev = await readData('account_level');
if (lev != null) {
level = int.tryParse(lev);
}
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

@ -7,11 +7,12 @@ class AccountViewModel extends BaseViewModel {
AccountViewModel() {
_init();
}
ApiResponse _apiResponse = ApiResponse.initial('Keine Daten');
final ApiResponse _apiResponse = ApiResponse.initial('Keine Daten');
final BackendService _service = BackendService();
Account? _account;
@override
ApiResponse get response {
return _apiResponse;
}
@ -21,7 +22,7 @@ class AccountViewModel extends BaseViewModel {
}
void _init() async {
super.init();
// super.init();
// try {
// _apiResponse = ApiResponse.completed(await _service.getAccount());
// } catch (e) {

View File

@ -1,12 +1,12 @@
import 'package:app/model/apis/api_response.dart';
import 'package:app/model/services/backend_service.dart';
import 'package:app/pages/home_page.dart';
import 'package:app/pages_draft/home_page.dart';
import 'package:app/util/colors.dart';
import 'package:flutter/material.dart';
class BaseViewModel with ChangeNotifier {
BaseViewModel() {
init();
// init();
}
ApiResponse _apiResponse = ApiResponse.initial('Keine Daten');
@ -16,16 +16,16 @@ class BaseViewModel with ChangeNotifier {
return _apiResponse;
}
void init() async {
// if (await BackendService.isLoggedIn) {
try {
_apiResponse = ApiResponse.completed(await _service.getAccount());
} catch (e) {
_apiResponse = ApiResponse.error(e.toString());
}
notifyListeners();
// }
}
// void init() async {
// // if (await BackendService.isLoggedIn) {
// try {
// _apiResponse = ApiResponse.completed(await _service.getAccount());
// } catch (e) {
// _apiResponse = ApiResponse.error(e.toString());
// }
// notifyListeners();
// // }
// }
Future<bool> isLoggedIn(BuildContext context) async {
final messenger = ScaffoldMessenger.of(context);

View File

@ -1,6 +1,9 @@
import 'package:app/model/apis/api_response.dart';
import 'package:app/model/services/backend_service.dart';
import 'package:app/pb/google/protobuf/timestamp.pb.dart';
import 'package:app/pb/person.pb.dart';
import 'package:app/util/colors.dart';
import 'package:fixnum/fixnum.dart';
import 'package:flutter/material.dart';
class PersonsViewModel with ChangeNotifier {
@ -35,8 +38,9 @@ class PersonsViewModel with ChangeNotifier {
required String zip,
required String city,
required String country,
required DateTime birthday}) async {
required Timestamp birthday}) async {
Person person = Person();
final messenger = ScaffoldMessenger.of(context);
_apiResponse = ApiResponse.loading('Erstelle Person');
try {
person = await _service.createPerson(
@ -47,8 +51,102 @@ class PersonsViewModel with ChangeNotifier {
city: city,
country: country,
birthday: birthday);
messenger.showSnackBar(SnackBar(
backgroundColor: CustomColors.success,
content: const Text(
'Gepeichert',
style: TextStyle(color: Colors.white),
),
));
_apiResponse = ApiResponse.completed(person);
} catch (err) {
messenger.showSnackBar(SnackBar(
backgroundColor: CustomColors.error,
content: const Text(
'Fehler beim Speichern',
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(
err.toString(),
textAlign: TextAlign.center,
),
));
},
),
));
_apiResponse = ApiResponse.error(err.toString());
}
notifyListeners();
return person;
}
Future<Person> updatePerson(BuildContext context,
{required Int64 id,
String? firstname,
String? lastname,
String? street,
String? zip,
String? city,
String? country,
Timestamp? birthday}) async {
Person person = Person();
final messenger = ScaffoldMessenger.of(context);
_apiResponse = ApiResponse.loading('Erstelle Person');
try {
person = await _service.updatePerson(
id: id,
firstname: firstname,
lastname: lastname,
street: street,
zip: zip,
city: city,
country: country,
birthday: birthday);
messenger.showSnackBar(SnackBar(
backgroundColor: CustomColors.success,
content: const Text(
'Gepeichert',
style: TextStyle(color: Colors.white),
),
));
_apiResponse = ApiResponse.completed(person);
} catch (err) {
messenger.showSnackBar(SnackBar(
backgroundColor: CustomColors.error,
content: const Text(
'Fehler beim Speichern',
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(
err.toString(),
textAlign: TextAlign.center,
),
));
},
),
));
_apiResponse = ApiResponse.error(err.toString());
}
notifyListeners();

View File

@ -0,0 +1,25 @@
import 'package:app/util/colors.dart';
import 'package:flutter/material.dart';
class AgbPage extends StatelessWidget {
AgbPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
iconTheme: IconThemeData(color: CustomColors.primary),
),
backgroundColor: Colors.black,
body: const Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'TODO: insert AGBs',
textAlign: TextAlign.center,
)
],
),
);
}
}

View File

@ -0,0 +1,191 @@
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 StatefulWidget {
const NotificationsPage({super.key});
@override
State<NotificationsPage> createState() => _NotificationsPageState();
}
class _NotificationsPageState extends State<NotificationsPage> {
final StorageService _storageService = StorageService();
bool _loading = true;
Future<void> _setNotificationSetting(bool enabled) async {
await _storageService.setNotificationSetting(enabled);
}
@override
void initState() {
_init();
super.initState();
}
void _init() async {
final accountLevel = await _storageService.accountLevel;
if (accountLevel > 2 && mounted) {
await Navigator.push(context,
MaterialPageRoute(builder: (builder) => const RegistrationPage()));
setState(() {
_loading = false;
});
} else {
setState(() {
_loading = false;
});
}
}
@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,
),
],
),
)
: Scaffold(
resizeToAvoidBottomInset: false,
appBar: AppBar(
leading: BackButton(
color: CustomColors.primary,
onPressed: () async {
await _storageService.setAccountLevel(1);
if (mounted) {
Navigator.pop(context);
}
},
),
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,
),
Image.asset('assets/chat_bubbles.png'),
const SizedBox(
height: 60,
),
const Text(
'Erhalte Mitteilungen',
textAlign: TextAlign.center,
style: TextStyle(
fontFamily: 'sans-serif',
fontWeight: FontWeight.bold,
letterSpacing: 2.0,
fontSize: 25,
),
),
const SizedBox(
height: 20,
),
const Text(
'Du erhältst z. B. eine Mitteilung sobald wir eine Digitale Spur gefunden haben.',
textAlign: TextAlign.center,
),
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: () async {
await _setNotificationSetting(true);
await _storageService.setAccountLevel(3);
if (mounted) {
Navigator.push(
context,
MaterialPageRoute(
builder: (builder) => const RegistrationPage(),
// builder: (builder) => SecurityPage(),
),
);
}
},
child: const SizedBox(
height: 60,
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Expanded(
flex: 1,
child: Text(
'Mitteilungen erhalten',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 20,
),
),
),
],
),
),
),
),
const SizedBox(
height: 10,
),
TextButton(
onPressed: () async {
await _setNotificationSetting(false);
await _storageService.setAccountLevel(3);
if (mounted) {
Navigator.push(
context,
MaterialPageRoute(
builder: (builder) => const RegistrationPage(),
// builder: (builder) => SecurityPage(),
),
);
}
},
child: Text(
'Später',
style: TextStyle(color: CustomColors.primary),
),
),
const Spacer(
flex: 1,
),
],
),
),
),
);
}
}

View File

@ -0,0 +1,244 @@
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 {
const 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: SingleChildScrollView(
child: Form(
key: _formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
widget.register
? 'Sichere dein Konto mit einem Passwort'
: 'Login',
textAlign: TextAlign.center,
style: const 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 {
if (!widget.register) {
_passwordsFilled = true;
}
_validPassword = true;
return null;
}
},
onChanged: (value) {
_formKey.currentState?.validate();
if (!value.isValidPassword) {
setState(() {
_validPassword = false;
});
} else {
setState(() {
if (!widget.register) {
_passwordsFilled = true;
}
_validPassword = true;
});
}
},
),
const SizedBox(
height: 20,
),
AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
child: !widget.register || !_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 ||
(widget.register &&
_passwordController1.text !=
_passwordController2.text)
? null
: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: CustomColors.primary,
),
onPressed: _validPassword && _passwordsFilled
? () async {
if (_formKey.currentState!.validate()) {
FocusScope.of(context).unfocus();
final navigator = Navigator.of(context);
bool loggedin = false;
if (widget.register) {
loggedin = await _vm.createAccount(
context,
email: widget.email,
password: _passwordController1.text,
);
} else {
loggedin = await _vm.login(
context,
email: widget.email,
password: _passwordController1.text,
);
}
if (loggedin && mounted) {
await _storageService.setAccountLevel(4);
navigator.push(
MaterialPageRoute(
builder: (builder) =>
const VerifyEmailPage(),
),
);
}
}
}
: null,
child: SizedBox(
height: 50,
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
widget.register
? 'Registrierung abschließen'
: 'Einloggen',
style: const TextStyle(
fontSize: 20,
),
),
],
),
),
),
),
],
),
),
),
),
);
}
}

View File

@ -0,0 +1,215 @@
import 'package:app/model/services/storage_service.dart';
import 'package:app/pages/password_page.dart';
import 'package:app/pages/verify_email_page.dart';
import 'package:app/util/colors.dart';
import 'package:app/util/validation.dart';
import 'package:flutter/material.dart';
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();
bool _loading = true;
final StorageService _storageService = StorageService();
@override
void initState() {
_init();
super.initState();
}
void _init() async {
final accountLevel = await _storageService.accountLevel;
if (accountLevel > 3 && mounted) {
await Navigator.push(context,
MaterialPageRoute(builder: (builder) => const VerifyEmailPage()));
setState(() {
_loading = false;
});
} else {
setState(() {
_loading = false;
});
}
}
@override
void dispose() {
mailController.dispose();
super.dispose();
}
@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,
),
],
),
)
: Scaffold(
appBar: AppBar(
leading: BackButton(
color: CustomColors.primary,
onPressed: () async {
await _storageService.setAccountLevel(2);
if (mounted) {
Navigator.pop(context);
}
},
),
iconTheme: IconThemeData(
color: CustomColors.primary,
),
),
body: Padding(
padding: const EdgeInsets.fromLTRB(20, 20, 20, 16),
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,
),
),
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,
),
],
),
),
),
);
}
}

View File

@ -0,0 +1,187 @@
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/util/colors.dart';
import 'package:flutter/material.dart';
class SecurityPage extends StatefulWidget {
const SecurityPage({super.key});
@override
State<SecurityPage> createState() => _SecurityPageState();
}
class _SecurityPageState extends State<SecurityPage> {
final StorageService _storageService = StorageService();
bool _loading = true;
@override
void initState() {
_init();
super.initState();
}
void _init() async {
final accountLevel = await _storageService.accountLevel;
if (accountLevel > 1 && mounted) {
await Navigator.push(context,
MaterialPageRoute(builder: (builder) => const NotificationsPage()));
setState(() {
_loading = false;
});
} else {
setState(() {
_loading = false;
});
}
}
@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,
),
],
),
)
: Scaffold(
appBar: AppBar(
leading: BackButton(
color: CustomColors.primary,
onPressed: () async {
await _storageService.setAccountLevel(0);
if (mounted) {
Navigator.pop(context);
}
},
),
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) {
await _storageService.setAccountLevel(2);
// ignore: use_build_context_synchronously
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,
),
],
),
),
),
),
);
}
}

View File

@ -0,0 +1,166 @@
import 'package:app/model/services/storage_service.dart';
import 'package:app/pages/agb_page.dart';
import 'package:app/pages/security_page.dart';
import 'package:app/util/colors.dart';
import 'package:flutter/material.dart';
class StartPage extends StatefulWidget {
const StartPage({super.key});
@override
State<StartPage> createState() => _StartPageState();
}
class _StartPageState extends State<StartPage> {
final StorageService _storageService = StorageService();
bool _loading = true;
@override
void initState() {
_init();
super.initState();
}
void _init() async {
int accountLevel = await _storageService.accountLevel;
if (accountLevel > 0 && mounted) {
await Navigator.push(context,
MaterialPageRoute(builder: (builder) => const SecurityPage()));
setState(() {
_loading = false;
});
} else {
setState(() {
_loading = false;
});
}
}
@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,
),
],
),
)
: 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: () async {
await _storageService.setAccountLevel(1);
if (mounted) {
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

@ -0,0 +1,156 @@
import 'package:app/model/services/storage_service.dart';
import 'package:app/util/colors.dart';
import 'package:flutter/material.dart';
class VerifyEmailPage extends StatefulWidget {
const VerifyEmailPage({super.key});
@override
State<VerifyEmailPage> createState() => _VerifyEmailPageState();
}
class _VerifyEmailPageState extends State<VerifyEmailPage> {
final StorageService _storageService = StorageService();
bool _loading = true;
@override
void initState() {
super.initState();
setState(() {
_loading = false;
});
}
@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,
),
],
),
)
: Scaffold(
appBar: AppBar(
leading: BackButton(
color: CustomColors.primary,
onPressed: () async {
await _storageService.setAccountLevel(3);
if (mounted) {
Navigator.pop(context);
}
},
),
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

@ -1,7 +1,8 @@
import 'package:app/model/services/backend_service.dart';
import 'package:app/model/view_model/account_vm.dart';
import 'package:app/pages/login_overlay.dart';
import 'package:app/pages/persons_page.dart';
import 'package:app/model/view_model/base_vm.dart';
import 'package:app/pages_draft/login_overlay.dart';
import 'package:app/pages_draft/persons_page.dart';
import 'package:app/widgets/background.dart';
import 'package:app/widgets/bottom_navigation.dart';
import 'package:app/widgets/bottom_navigation_item.dart';
@ -48,7 +49,9 @@ class _HomePageState extends State<HomePage> {
_isLoggedIn(BuildContext context) async {
bool logged = await vm.isLoggedIn(context);
_loggedin = logged;
setState(() {
_loggedin = logged;
});
}
void _setLoading(bool loading) {
@ -137,9 +140,10 @@ class _HomePageState extends State<HomePage> {
),
BottomNavigationItem(
onPressed: () async {
await showLogin(context);
bool res = await showLogin(context);
setState(() {
vm.isLoggedIn(context);
_loggedin = res;
// vm.isLoggedIn(context);
});
},
icon: Icons.login,
@ -150,7 +154,7 @@ class _HomePageState extends State<HomePage> {
BottomNavigationItem(
onPressed: () async {
final navigator = Navigator.of(context);
if (await vm.isLoggedIn(context)) {
if (_loggedin) {
navigator.push(MaterialPageRoute(
builder: (builder) => const PersonsPage()));
} else {
@ -176,10 +180,9 @@ class _HomePageState extends State<HomePage> {
body: Padding(
padding: const EdgeInsets.fromLTRB(16, 45, 16, 16),
child: Center(
child: ChangeNotifierProvider<AccountViewModel>(
create: (context) => AccountViewModel(),
child:
Consumer<AccountViewModel>(builder: (context, value, child) {
child: ChangeNotifierProvider<BaseViewModel>(
create: (context) => BaseViewModel(),
child: Consumer<BaseViewModel>(builder: (context, value, child) {
// _checkResponse(value.response);
if (!widget.loggedOut) {
_isLoggedIn(context);

View File

@ -0,0 +1,339 @@
import 'package:app/model/view_model/persons_vm.dart';
import 'package:app/pb/google/protobuf/timestamp.pb.dart';
import 'package:app/pb/person.pb.dart';
import 'package:app/util/validation.dart';
import 'package:app/widgets/background.dart';
import 'package:app/widgets/bottom_navigation.dart';
import 'package:app/widgets/bottom_navigation_item.dart';
import 'package:fixnum/fixnum.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:intl/intl.dart';
Future<Person> showPerson(BuildContext context, {Person? person}) async {
PersonsViewModel vm = PersonsViewModel();
final formKey = GlobalKey<FormState>();
final firstnameController = TextEditingController();
final lastnameController = TextEditingController();
final cityController = TextEditingController();
final zipController = TextEditingController();
final streetController = TextEditingController();
final countryController = TextEditingController();
final birthdayController = TextEditingController();
Future<void> _init() async {
if (person == null) {
person ??= Person();
// person ??= Person(accountId: await BackendService.accountId);
} else {
firstnameController.text = person!.firstname;
lastnameController.text = person!.lastname;
cityController.text = person!.city;
zipController.text = person!.zip;
streetController.text = person!.street;
countryController.text = person!.country;
birthdayController.text =
DateFormat('dd.MM.yyyy').format(person!.birthday.toDateTime());
}
}
await _init();
void _updateData() {
person!.firstname = firstnameController.text;
person!.lastname = lastnameController.text;
person!.city = cityController.text;
person!.street = streetController.text;
person!.zip = zipController.text;
person!.country = countryController.text;
}
Future<void> createPerson(BuildContext context) async {
final navigator = Navigator.of(context);
_updateData();
person!.id = Int64(0);
person = await vm.createPerson(context,
firstname: person!.firstname,
lastname: person!.lastname,
street: person!.street,
zip: person!.zip,
city: person!.city,
country: person!.country,
birthday: person!.birthday);
if (person!.id != 0) {
navigator.pop(person);
}
}
Future<void> updatePerson(BuildContext context) async {
final navigator = Navigator.of(context);
_updateData();
final personUpdate = await vm.updatePerson(context,
id: person!.id,
firstname: person!.firstname != firstnameController.text
? person!.firstname
: null,
lastname: person!.lastname != lastnameController.text
? person!.lastname
: null,
street: person!.street != streetController.text ? person!.street : null,
zip: person!.zip != zipController.text ? person!.zip : null,
city: person!.city != cityController.text ? person!.city : null,
country:
person!.country != countryController.text ? person!.country : null,
birthday:
DateFormat('dd.MM.yyyy').format(person!.birthday.toDateTime()) !=
birthdayController.text
? person!.birthday
: null);
if (personUpdate != person) {
navigator.pop(person);
}
}
// ignore: use_build_context_synchronously
await showModalBottomSheet(
context: context,
builder: (builder) {
return Background(
child: Scaffold(
bottomNavigationBar: BottomNavigation(
hideMenu: true,
children: [
BottomNavigationItem(
onPressed: () {
Navigator.pop(context, false);
},
icon: Icons.arrow_back,
color: Colors.white,
label: 'Zurück',
),
BottomNavigationItem(
onPressed: () {
Navigator.pop(context, false);
},
icon: Icons.home,
color: Colors.white,
label: 'Home',
),
],
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const SizedBox(
height: 50,
),
Text(
person!.id == 0 ? 'Person anlegen' : 'Person anpassen',
style: const TextStyle(
fontFamily: 'sans-serif',
fontSize: 24,
height: 1.6,
fontWeight: FontWeight.normal,
letterSpacing: 6),
),
ChangeNotifierProvider<PersonsViewModel>(
create: (context) => PersonsViewModel(),
child: Consumer<PersonsViewModel>(
builder: (context, value, child) => Form(
key: formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const SizedBox(
height: 40,
),
TextFormField(
controller: firstnameController,
decoration: const InputDecoration(
fillColor: Color.fromARGB(30, 255, 255, 255),
filled: true,
suffix: Text('Vorname'),
hintStyle: TextStyle(
color: Colors.white38,
),
hintText: 'Vorname',
),
keyboardType: TextInputType.name,
validator: (value) {
if (value == null || !value.isValidName) {
return 'Bitte einen gültigen Vornamen eingeben';
}
return null;
},
),
TextFormField(
controller: lastnameController,
decoration: const InputDecoration(
fillColor: Color.fromARGB(30, 255, 255, 255),
filled: true,
suffix: Text('Nachname'),
hintStyle: TextStyle(
color: Colors.white38,
),
hintText: 'Nachname',
),
keyboardType: TextInputType.name,
validator: (value) {
if (value == null || !value.isValidName) {
return 'Bitte einen gültigen Nachnamen eingeben';
}
return null;
},
),
TextFormField(
readOnly: true,
onTap: () async {
DateTime? pickedDate = await showDatePicker(
context: context,
locale: const Locale('de', 'DE'),
initialDate: DateTime.now(),
firstDate: DateTime(1930),
lastDate: DateTime(DateTime.now().year + 1),
builder: (context, child) => Theme(
data: ThemeData.dark(),
child: child != null ? child : Text(''),
),
);
if (pickedDate != null) {
person!.birthday =
Timestamp.fromDateTime(pickedDate);
birthdayController.text =
DateFormat('dd.MM.yyyy')
.format(pickedDate);
}
},
controller: birthdayController,
decoration: const InputDecoration(
fillColor: Color.fromARGB(30, 255, 255, 255),
filled: true,
suffix: Text('Geburtstag'),
hintStyle: TextStyle(
color: Colors.white38,
),
hintText: 'Geburtstag',
),
keyboardType: TextInputType.name,
// validator: (value) {
// if (value == null || !value.isValidName) {
// return 'Bitte einen gültigen Nachnamen eingeben';
// }
// return null;
// },
),
TextFormField(
controller: streetController,
decoration: const InputDecoration(
fillColor: Color.fromARGB(30, 255, 255, 255),
filled: true,
suffix: Text('Straße'),
hintStyle: TextStyle(
color: Colors.white38,
),
hintText: 'Straße mit Hausnummer',
),
keyboardType: TextInputType.name,
validator: (value) {
if (value == null || !value.isValidName) {
return 'Bitte eine gültige Straße mit Hausnummer eingeben';
}
return null;
},
),
TextFormField(
controller: zipController,
decoration: const InputDecoration(
fillColor: Color.fromARGB(30, 255, 255, 255),
filled: true,
suffix: Text('PLZ'),
hintStyle: TextStyle(
color: Colors.white38,
),
hintText: 'PLZ',
),
keyboardType: TextInputType.name,
validator: (value) {
if (value == null || !value.isValidName) {
return 'Bitte eine gültige PLZ eingeben';
}
return null;
},
),
TextFormField(
controller: cityController,
decoration: const InputDecoration(
fillColor: Color.fromARGB(30, 255, 255, 255),
filled: true,
suffix: Text('Stadt'),
hintStyle: TextStyle(
color: Colors.white38,
),
hintText: 'Stadt',
),
keyboardType: TextInputType.name,
validator: (value) {
if (value == null || !value.isValidName) {
return 'Bitte eine gültige Stadt eingeben';
}
return null;
},
),
TextFormField(
controller: countryController,
decoration: const InputDecoration(
fillColor: Color.fromARGB(30, 255, 255, 255),
filled: true,
suffix: Text('Land'),
hintStyle: TextStyle(
color: Colors.white38,
),
hintText: 'Land',
),
keyboardType: TextInputType.name,
validator: (value) {
if (value == null || !value.isValidName) {
return 'Bitte ein gültiges Land eingeben';
}
return null;
},
),
const SizedBox(
height: 15,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
onPressed: () async {
person!.id.isZero
? await createPerson(context)
: await updatePerson(context);
},
child: const Icon(Icons.update),
),
],
)
],
),
),
),
),
],
),
),
),
);
},
useSafeArea: true,
isScrollControlled: true,
backgroundColor: Colors.black);
return person!;
}

View File

@ -1,8 +1,10 @@
import 'package:app/model/apis/api_response.dart';
import 'package:app/model/services/backend_service.dart';
import 'package:app/model/view_model/persons_vm.dart';
import 'package:app/pages/home_page.dart';
import 'package:app/pages_draft/home_page.dart';
import 'package:app/pages_draft/person_details_page.dart';
import 'package:app/pb/person.pb.dart';
import 'package:app/util/validation.dart';
import 'package:app/widgets/background.dart';
import 'package:app/widgets/bottom_navigation.dart';
import 'package:app/widgets/bottom_navigation_item.dart';
@ -22,24 +24,25 @@ class _PersonsPageState extends State<PersonsPage> {
@override
void initState() {
super.initState();
_init();
// _init();
}
void _init() async {
_setLoading(true);
_loggedin = await BackendService.isLoggedIn;
_setLoading(false);
}
// void _init() async {
// _setLoading(true);
// _loggedin = await BackendService.isLoggedIn;
// _setLoading(false);
// }
void _setLoading(bool loading) {
setState(() {
_loading = loading;
});
}
// void _setLoading(bool loading) {
// setState(() {
// _loading = loading;
// });
// }
void _checkResponse(ApiResponse response) {
if (response.status == Status.ERROR &&
response.message!.contains('unauthenticated')) {
(response.message!.contains('unauthenticated') ||
response.message!.contains('blocked'))) {
BackendService.logout();
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(
@ -50,7 +53,7 @@ class _PersonsPageState extends State<PersonsPage> {
}
}
bool _loading = true;
bool _loading = false;
bool _loggedin = false;
List<Person> persons = [];
@ -61,13 +64,58 @@ class _PersonsPageState extends State<PersonsPage> {
}
List<Widget> _personsList(List<Person> persons) {
persons.sort((a, b) {
final comp = a.lastname.compareTo(b.lastname);
if (comp != 0) {
return comp;
}
return a.firstname.compareTo(b.firstname);
});
final List<Widget> list = [];
for (var p in persons) {
list.add(Card(
color: Colors.black,
child: Text(
'${p.firstname} ${p.lastname}',
style: const TextStyle(color: Colors.white),
list.add(TextButton(
onPressed: () async {
final Person per = await showPerson(context, person: p);
if (!per.id.isZero && !persons.contains(per)) {
setState(() {
this.persons.add(per);
});
}
},
child: Card(
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
color: const Color.fromARGB(100, 89, 88, 88),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 14),
child: Row(
children: [
SizedBox(
height: 40,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
p.lastname.titleCase,
style: const TextStyle(color: Colors.white),
// overflow: TextOverflow.fade,
textAlign: TextAlign.start,
),
const Spacer(),
Text(
p.firstname.titleCase,
style: const TextStyle(color: Colors.white),
textAlign: TextAlign.start,
// overflow: TextOverflow.fade,
),
],
),
),
const Spacer(),
const Text('STATUS')
],
),
),
),
));
}
@ -80,7 +128,14 @@ class _PersonsPageState extends State<PersonsPage> {
child: Scaffold(
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
floatingActionButton: FloatingActionButton(
onPressed: () {},
onPressed: () async {
final p = await showPerson(context);
if (!p.id.isZero && !persons.contains(p)) {
setState(() {
persons.add(p);
});
}
},
child: const Icon(Icons.add),
),
appBar: AppBar(
@ -163,7 +218,9 @@ class _PersonsPageState extends State<PersonsPage> {
child: Consumer<PersonsViewModel>(
builder: (context, value, child) {
_checkResponse(value.response);
listPersons(context);
if (persons.isEmpty) {
listPersons(context);
}
return _loading
? const CircularProgressIndicator(
color: Colors.grey,

View File

@ -1,9 +1,5 @@
import 'package:app/gapi/client.dart';
import 'package:app/pages_old/start_page.dart';
import 'package:app/pb/account_info.pb.dart';
import 'package:app/pb/rpc_get_account_info.pb.dart';
import 'package:app/widgets/background.dart';
import 'package:app/widgets/bottom_bar.dart';
import 'package:app/widgets/loading_widget.dart';
import 'package:app/widgets/side_drawer.dart';
import 'package:flutter/material.dart';

View File

@ -1,12 +1,9 @@
import 'package:app/gapi/client.dart';
import 'package:app/model/services/backend_service.dart';
import 'package:app/pages_old/start_page.dart';
import 'package:app/widgets/background.dart';
import 'package:app/widgets/bottom_bar.dart';
import 'package:app/widgets/loading_widget.dart';
import 'package:app/widgets/side_drawer.dart';
import 'package:flutter/material.dart';
import 'package:grpc/grpc.dart';
// GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();

View File

@ -1,11 +1,8 @@
import 'package:app/gapi/client.dart';
import 'package:app/pages_old/start_page.dart';
import 'package:app/widgets/background.dart';
import 'package:app/widgets/bottom_bar.dart';
import 'package:app/widgets/loading_widget.dart';
import 'package:app/widgets/side_drawer.dart';
import 'package:flutter/material.dart';
import 'package:grpc/grpc.dart';
class RegisterPage extends StatefulWidget {
const RegisterPage({

View File

@ -1,9 +1,6 @@
import 'package:app/gapi/client.dart';
import 'package:app/model/apis/api_response.dart';
import 'package:app/model/view_model/account_vm.dart';
import 'package:app/pages_old/dashboard_page.dart';
import 'package:app/pages_old/login_page.dart';
import 'package:app/pages_old/register_page.dart';
import 'package:app/pb/account.pb.dart';
import 'package:app/widgets/background.dart';
import 'package:app/widgets/bottom_bar.dart';

View File

@ -2,10 +2,18 @@ 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 {
return const Color.fromARGB(200, 55, 125, 55);
return const Color.fromARGB(255, 51, 217, 178);
}
static Color get primary {
return const Color.fromARGB(255, 51, 217, 178);
}
static Color get secondary {
return const Color.fromARGB(255, 52, 172, 224);
}
}

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 {
@ -24,4 +24,10 @@ extension valString on String {
bool get isValidPhone {
return phoneRegExp.hasMatch(this);
}
String get titleCase {
return split(' ')
.map((str) => str[0].toUpperCase() + str.substring(1))
.join(' ');
}
}

View File

@ -7,10 +7,13 @@ class BottomNavigation extends StatelessWidget {
required this.children,
this.backgroundColor,
this.iconColor,
this.hideMenu,
}) {
hideMenu ??= false;
backgroundColor ??= Colors.black;
}
bool? hideMenu;
List<Widget> children;
Color? backgroundColor;
Color? iconColor;
@ -32,14 +35,18 @@ class BottomNavigation extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.center,
children: [
...children,
Builder(builder: (context) {
return IconButton(
onPressed: () => Scaffold.of(context).openDrawer(),
icon: const Icon(
Icons.menu,
color: Colors.white,
));
}),
if (!hideMenu!)
Builder(
builder: (context) {
return IconButton(
onPressed: () => Scaffold.of(context).openDrawer(),
icon: const Icon(
Icons.menu,
color: Colors.white,
),
);
},
)
],
),
),

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:
@ -110,11 +118,77 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.0.3"
flutter_localizations:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_plugin_android_lifecycle:
dependency: transitive
description:
name: flutter_plugin_android_lifecycle
sha256: b068ffc46f82a55844acfa4fdbb61fad72fa2aef0905548419d97f0f95c456da
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:
@ -155,6 +229,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.0.2"
intl:
dependency: "direct main"
description:
name: intl
sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d"
url: "https://pub.dev"
source: hosted
version: "0.18.1"
js:
dependency: transitive
description:
@ -171,6 +253,46 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.1"
local_auth:
dependency: "direct main"
description:
name: local_auth
sha256: "7e6c63082e399b61e4af71266b012e767a5d4525dd6e9ba41e174fd42d76e115"
url: "https://pub.dev"
source: hosted
version: "2.1.7"
local_auth_android:
dependency: transitive
description:
name: local_auth_android
sha256: df4ccb3193525b8a60c78a5ca7bf188a47705bcf77bcc837a6b2cf6da64ae0e2
url: "https://pub.dev"
source: hosted
version: "1.0.35"
local_auth_ios:
dependency: transitive
description:
name: local_auth_ios
sha256: "8293faf72ef0ac4710f209edd03916c2d4c1eeab0483bdcf9b2e659c2f7d737b"
url: "https://pub.dev"
source: hosted
version: "1.1.5"
local_auth_platform_interface:
dependency: transitive
description:
name: local_auth_platform_interface
sha256: fc5bd537970a324260fda506cfb61b33ad7426f37a8ea5c461cf612161ebba54
url: "https://pub.dev"
source: hosted
version: "1.0.8"
local_auth_windows:
dependency: transitive
description:
name: local_auth_windows
sha256: "505ba3367ca781efb1c50d3132e44a2446bccc4163427bc203b9b4d8994d97ea"
url: "https://pub.dev"
source: hosted
version: "1.0.10"
matcher:
dependency: transitive
description:
@ -211,6 +333,70 @@ 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:
name: plugin_platform_interface
sha256: da3fdfeccc4d4ff2da8f8c556704c08f912542c5fb3cf2233ed75372384a034d
url: "https://pub.dev"
source: hosted
version: "2.1.6"
pointycastle:
dependency: transitive
description:
@ -336,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.3.0"
flutter: ">=3.10.0"

View File

@ -28,11 +28,14 @@ environment:
# the latest version available on pub.dev. To see which dependencies have newer
# versions available, run `flutter pub outdated`.
dependencies:
local_auth: ^2.1.7
async: ^2.2.0
protobuf: ^3.0.0
collection: ^1.15.0-nullsafety.4
flutter:
sdk: flutter
flutter_localizations:
sdk: flutter
# The following adds the Cupertino Icons font to your application.
@ -43,6 +46,8 @@ 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:
lints: ^2.0.0
@ -69,6 +74,8 @@ flutter:
# To add assets to your application, add an assets section, like this:
assets:
- assets/chat_bubbles.png
- assets/JPEG.jpg
- lib/assets/logo_300x200.png
- lib/assets/hero-pattern-300x200.png
# - images/a_dot_burr.jpeg

View File

@ -6,6 +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,8 @@
#
list(APPEND FLUTTER_PLUGIN_LIST
flutter_secure_storage_windows
local_auth_windows
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST