Merge branch 'ft/app'
@ -45,7 +45,7 @@ android {
|
|||||||
applicationId "com.example.app"
|
applicationId "com.example.app"
|
||||||
// You can update the following values to match your application needs.
|
// 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.
|
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
|
||||||
minSdkVersion flutter.minSdkVersion
|
minSdkVersion 18
|
||||||
targetSdkVersion flutter.targetSdkVersion
|
targetSdkVersion flutter.targetSdkVersion
|
||||||
versionCode flutterVersionCode.toInteger()
|
versionCode flutterVersionCode.toInteger()
|
||||||
versionName flutterVersionName
|
versionName flutterVersionName
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
<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
|
<application
|
||||||
android:label="app"
|
android:label="app"
|
||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
package com.example.app
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
BIN
frontend/app/assets/JPEG.jpg
Normal file
After Width: | Height: | Size: 178 KiB |
BIN
frontend/app/assets/Mockup.jpg
Normal file
After Width: | Height: | Size: 1.1 MiB |
BIN
frontend/app/assets/chat_bubbles.jpg
Normal file
After Width: | Height: | Size: 290 KiB |
BIN
frontend/app/assets/chat_bubbles.png
Normal file
After Width: | Height: | Size: 332 KiB |
BIN
frontend/app/assets/icons/chair_magnifier.png
Normal file
After Width: | Height: | Size: 45 KiB |
BIN
frontend/app/assets/icons/conversation.png
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
frontend/app/assets/icons/financial_growth.png
Normal file
After Width: | Height: | Size: 56 KiB |
BIN
frontend/app/assets/icons/map_search.png
Normal file
After Width: | Height: | Size: 19 KiB |
BIN
frontend/app/assets/icons/message_lookout.png
Normal file
After Width: | Height: | Size: 27 KiB |
BIN
frontend/app/assets/icons/office.png
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
frontend/app/assets/icons/people_cheering.png
Normal file
After Width: | Height: | Size: 64 KiB |
BIN
frontend/app/assets/vector-EPS.eps
Normal file
69
frontend/app/assets/vector-SVG.svg
Normal 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 |
@ -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/material.dart';
|
||||||
|
import 'package:flutter_localizations/flutter_localizations.dart'
|
||||||
|
show GlobalMaterialLocalizations;
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
runApp(
|
runApp(
|
||||||
MaterialApp(
|
MaterialApp(
|
||||||
|
localizationsDelegates: const [GlobalMaterialLocalizations.delegate],
|
||||||
|
supportedLocales: const [Locale('en'), Locale('de')],
|
||||||
theme: ThemeData().copyWith(
|
theme: ThemeData().copyWith(
|
||||||
|
canvasColor: Colors.black,
|
||||||
colorScheme: const ColorScheme(
|
colorScheme: const ColorScheme(
|
||||||
brightness: Brightness.dark,
|
brightness: Brightness.dark,
|
||||||
primary: Colors.white,
|
primary: Colors.white,
|
||||||
@ -41,9 +49,64 @@ void main() async {
|
|||||||
backgroundColor: Colors.black,
|
backgroundColor: Colors.black,
|
||||||
foregroundColor: Colors.white,
|
foregroundColor: Colors.white,
|
||||||
)),
|
)),
|
||||||
home: HomePage(
|
home: const DigitalerFrieden(),
|
||||||
loggedOut: false,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -4,6 +4,7 @@ class AppException implements Exception {
|
|||||||
|
|
||||||
AppException([this._message, this._prefix]);
|
AppException([this._message, this._prefix]);
|
||||||
|
|
||||||
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return "$_prefix$_message";
|
return "$_prefix$_message";
|
||||||
}
|
}
|
||||||
|
43
frontend/app/lib/model/services/auth_service.dart
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
@ -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_list_persons.pb.dart';
|
||||||
import 'package:app/pb/rpc_login.pb.dart';
|
import 'package:app/pb/rpc_login.pb.dart';
|
||||||
import 'package:app/pb/rpc_refresh_token.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:app/pb/service_df.pbgrpc.dart';
|
||||||
import 'package:fixnum/fixnum.dart';
|
import 'package:fixnum/fixnum.dart';
|
||||||
import 'package:grpc/grpc.dart';
|
import 'package:grpc/grpc.dart';
|
||||||
@ -114,6 +115,10 @@ class BackendService {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Future<Int64?> get accountId async {
|
||||||
|
return (await Session.session).accountId;
|
||||||
|
}
|
||||||
|
|
||||||
static Future<bool> createAccount(
|
static Future<bool> createAccount(
|
||||||
{required String email, required String password}) async {
|
{required String email, required String password}) async {
|
||||||
try {
|
try {
|
||||||
@ -126,7 +131,7 @@ class BackendService {
|
|||||||
} on SocketException {
|
} on SocketException {
|
||||||
throw FetchDataException('Keine Internet Verbindung');
|
throw FetchDataException('Keine Internet Verbindung');
|
||||||
} on GrpcError catch (err) {
|
} on GrpcError catch (err) {
|
||||||
throw FetchDataException(err.message);
|
throw FetchDataException('${err.message}');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw InternalException(err.toString());
|
throw InternalException(err.toString());
|
||||||
}
|
}
|
||||||
@ -207,14 +212,15 @@ class BackendService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Person> createPerson(
|
Future<Person> updatePerson(
|
||||||
{required String firstname,
|
{required Int64 id,
|
||||||
required String lastname,
|
String? firstname,
|
||||||
required String street,
|
String? lastname,
|
||||||
required String zip,
|
String? street,
|
||||||
required String city,
|
String? zip,
|
||||||
required String country,
|
String? city,
|
||||||
required DateTime birthday}) async {
|
String? country,
|
||||||
|
Timestamp? birthday}) async {
|
||||||
Session session = await Session.session;
|
Session session = await Session.session;
|
||||||
if (session.accessTokenExpiresAt == null) {
|
if (session.accessTokenExpiresAt == null) {
|
||||||
throw UnauthorizedException('Keine Siztung gefunden');
|
throw UnauthorizedException('Keine Siztung gefunden');
|
||||||
@ -226,16 +232,74 @@ class BackendService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
final CreatePersonResponse response = await _client.createPerson(
|
final UpdatePersonRequest req = UpdatePersonRequest(
|
||||||
CreatePersonRequest(
|
id: id,
|
||||||
accountId: session.accountId,
|
);
|
||||||
lastname: lastname,
|
|
||||||
firstname: firstname,
|
if (lastname != null) {
|
||||||
street: street,
|
req.lastname = lastname;
|
||||||
zip: zip,
|
}
|
||||||
country: country,
|
if (firstname != null) {
|
||||||
birthday: Timestamp.fromDateTime(birthday),
|
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(
|
options: CallOptions(
|
||||||
metadata: {'Authorization': 'Bearer ${session.accessToken}'}));
|
metadata: {'Authorization': 'Bearer ${session.accessToken}'}));
|
||||||
return response.person;
|
return response.person;
|
||||||
|
84
frontend/app/lib/model/services/storage_service.dart
Normal 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}'));
|
||||||
|
}
|
||||||
|
}
|
@ -7,11 +7,12 @@ class AccountViewModel extends BaseViewModel {
|
|||||||
AccountViewModel() {
|
AccountViewModel() {
|
||||||
_init();
|
_init();
|
||||||
}
|
}
|
||||||
ApiResponse _apiResponse = ApiResponse.initial('Keine Daten');
|
final ApiResponse _apiResponse = ApiResponse.initial('Keine Daten');
|
||||||
|
|
||||||
final BackendService _service = BackendService();
|
final BackendService _service = BackendService();
|
||||||
Account? _account;
|
Account? _account;
|
||||||
|
|
||||||
|
@override
|
||||||
ApiResponse get response {
|
ApiResponse get response {
|
||||||
return _apiResponse;
|
return _apiResponse;
|
||||||
}
|
}
|
||||||
@ -21,7 +22,7 @@ class AccountViewModel extends BaseViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _init() async {
|
void _init() async {
|
||||||
super.init();
|
// super.init();
|
||||||
// try {
|
// try {
|
||||||
// _apiResponse = ApiResponse.completed(await _service.getAccount());
|
// _apiResponse = ApiResponse.completed(await _service.getAccount());
|
||||||
// } catch (e) {
|
// } catch (e) {
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import 'package:app/model/apis/api_response.dart';
|
import 'package:app/model/apis/api_response.dart';
|
||||||
import 'package:app/model/services/backend_service.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:app/util/colors.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class BaseViewModel with ChangeNotifier {
|
class BaseViewModel with ChangeNotifier {
|
||||||
BaseViewModel() {
|
BaseViewModel() {
|
||||||
init();
|
// init();
|
||||||
}
|
}
|
||||||
ApiResponse _apiResponse = ApiResponse.initial('Keine Daten');
|
ApiResponse _apiResponse = ApiResponse.initial('Keine Daten');
|
||||||
|
|
||||||
@ -16,16 +16,16 @@ class BaseViewModel with ChangeNotifier {
|
|||||||
return _apiResponse;
|
return _apiResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
void init() async {
|
// void init() async {
|
||||||
// if (await BackendService.isLoggedIn) {
|
// // if (await BackendService.isLoggedIn) {
|
||||||
try {
|
// try {
|
||||||
_apiResponse = ApiResponse.completed(await _service.getAccount());
|
// _apiResponse = ApiResponse.completed(await _service.getAccount());
|
||||||
} catch (e) {
|
// } catch (e) {
|
||||||
_apiResponse = ApiResponse.error(e.toString());
|
// _apiResponse = ApiResponse.error(e.toString());
|
||||||
}
|
// }
|
||||||
notifyListeners();
|
// notifyListeners();
|
||||||
// }
|
// // }
|
||||||
}
|
// }
|
||||||
|
|
||||||
Future<bool> isLoggedIn(BuildContext context) async {
|
Future<bool> isLoggedIn(BuildContext context) async {
|
||||||
final messenger = ScaffoldMessenger.of(context);
|
final messenger = ScaffoldMessenger.of(context);
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
import 'package:app/model/apis/api_response.dart';
|
import 'package:app/model/apis/api_response.dart';
|
||||||
import 'package:app/model/services/backend_service.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/pb/person.pb.dart';
|
||||||
|
import 'package:app/util/colors.dart';
|
||||||
|
import 'package:fixnum/fixnum.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class PersonsViewModel with ChangeNotifier {
|
class PersonsViewModel with ChangeNotifier {
|
||||||
@ -35,8 +38,9 @@ class PersonsViewModel with ChangeNotifier {
|
|||||||
required String zip,
|
required String zip,
|
||||||
required String city,
|
required String city,
|
||||||
required String country,
|
required String country,
|
||||||
required DateTime birthday}) async {
|
required Timestamp birthday}) async {
|
||||||
Person person = Person();
|
Person person = Person();
|
||||||
|
final messenger = ScaffoldMessenger.of(context);
|
||||||
_apiResponse = ApiResponse.loading('Erstelle Person');
|
_apiResponse = ApiResponse.loading('Erstelle Person');
|
||||||
try {
|
try {
|
||||||
person = await _service.createPerson(
|
person = await _service.createPerson(
|
||||||
@ -47,8 +51,102 @@ class PersonsViewModel with ChangeNotifier {
|
|||||||
city: city,
|
city: city,
|
||||||
country: country,
|
country: country,
|
||||||
birthday: birthday);
|
birthday: birthday);
|
||||||
|
messenger.showSnackBar(SnackBar(
|
||||||
|
backgroundColor: CustomColors.success,
|
||||||
|
content: const Text(
|
||||||
|
'Gepeichert',
|
||||||
|
style: TextStyle(color: Colors.white),
|
||||||
|
),
|
||||||
|
));
|
||||||
_apiResponse = ApiResponse.completed(person);
|
_apiResponse = ApiResponse.completed(person);
|
||||||
} catch (err) {
|
} 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());
|
_apiResponse = ApiResponse.error(err.toString());
|
||||||
}
|
}
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
25
frontend/app/lib/pages/agb_page.dart
Normal 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,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
191
frontend/app/lib/pages/notifications_page.dart
Normal 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,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
244
frontend/app/lib/pages/password_page.dart
Normal 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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
215
frontend/app/lib/pages/registration_page.dart
Normal 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,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
187
frontend/app/lib/pages/security_page.dart
Normal 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,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
166
frontend/app/lib/pages/start_page.dart
Normal 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),
|
||||||
|
))
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
156
frontend/app/lib/pages/verify_email_page.dart
Normal 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,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,8 @@
|
|||||||
import 'package:app/model/services/backend_service.dart';
|
import 'package:app/model/services/backend_service.dart';
|
||||||
import 'package:app/model/view_model/account_vm.dart';
|
import 'package:app/model/view_model/account_vm.dart';
|
||||||
import 'package:app/pages/login_overlay.dart';
|
import 'package:app/model/view_model/base_vm.dart';
|
||||||
import 'package:app/pages/persons_page.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/background.dart';
|
||||||
import 'package:app/widgets/bottom_navigation.dart';
|
import 'package:app/widgets/bottom_navigation.dart';
|
||||||
import 'package:app/widgets/bottom_navigation_item.dart';
|
import 'package:app/widgets/bottom_navigation_item.dart';
|
||||||
@ -48,7 +49,9 @@ class _HomePageState extends State<HomePage> {
|
|||||||
|
|
||||||
_isLoggedIn(BuildContext context) async {
|
_isLoggedIn(BuildContext context) async {
|
||||||
bool logged = await vm.isLoggedIn(context);
|
bool logged = await vm.isLoggedIn(context);
|
||||||
_loggedin = logged;
|
setState(() {
|
||||||
|
_loggedin = logged;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void _setLoading(bool loading) {
|
void _setLoading(bool loading) {
|
||||||
@ -137,9 +140,10 @@ class _HomePageState extends State<HomePage> {
|
|||||||
),
|
),
|
||||||
BottomNavigationItem(
|
BottomNavigationItem(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await showLogin(context);
|
bool res = await showLogin(context);
|
||||||
setState(() {
|
setState(() {
|
||||||
vm.isLoggedIn(context);
|
_loggedin = res;
|
||||||
|
// vm.isLoggedIn(context);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
icon: Icons.login,
|
icon: Icons.login,
|
||||||
@ -150,7 +154,7 @@ class _HomePageState extends State<HomePage> {
|
|||||||
BottomNavigationItem(
|
BottomNavigationItem(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
final navigator = Navigator.of(context);
|
final navigator = Navigator.of(context);
|
||||||
if (await vm.isLoggedIn(context)) {
|
if (_loggedin) {
|
||||||
navigator.push(MaterialPageRoute(
|
navigator.push(MaterialPageRoute(
|
||||||
builder: (builder) => const PersonsPage()));
|
builder: (builder) => const PersonsPage()));
|
||||||
} else {
|
} else {
|
||||||
@ -176,10 +180,9 @@ class _HomePageState extends State<HomePage> {
|
|||||||
body: Padding(
|
body: Padding(
|
||||||
padding: const EdgeInsets.fromLTRB(16, 45, 16, 16),
|
padding: const EdgeInsets.fromLTRB(16, 45, 16, 16),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: ChangeNotifierProvider<AccountViewModel>(
|
child: ChangeNotifierProvider<BaseViewModel>(
|
||||||
create: (context) => AccountViewModel(),
|
create: (context) => BaseViewModel(),
|
||||||
child:
|
child: Consumer<BaseViewModel>(builder: (context, value, child) {
|
||||||
Consumer<AccountViewModel>(builder: (context, value, child) {
|
|
||||||
// _checkResponse(value.response);
|
// _checkResponse(value.response);
|
||||||
if (!widget.loggedOut) {
|
if (!widget.loggedOut) {
|
||||||
_isLoggedIn(context);
|
_isLoggedIn(context);
|
339
frontend/app/lib/pages_draft/person_details_page.dart
Normal 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!;
|
||||||
|
}
|
@ -1,8 +1,10 @@
|
|||||||
import 'package:app/model/apis/api_response.dart';
|
import 'package:app/model/apis/api_response.dart';
|
||||||
import 'package:app/model/services/backend_service.dart';
|
import 'package:app/model/services/backend_service.dart';
|
||||||
import 'package:app/model/view_model/persons_vm.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/pb/person.pb.dart';
|
||||||
|
import 'package:app/util/validation.dart';
|
||||||
import 'package:app/widgets/background.dart';
|
import 'package:app/widgets/background.dart';
|
||||||
import 'package:app/widgets/bottom_navigation.dart';
|
import 'package:app/widgets/bottom_navigation.dart';
|
||||||
import 'package:app/widgets/bottom_navigation_item.dart';
|
import 'package:app/widgets/bottom_navigation_item.dart';
|
||||||
@ -22,24 +24,25 @@ class _PersonsPageState extends State<PersonsPage> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_init();
|
// _init();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _init() async {
|
// void _init() async {
|
||||||
_setLoading(true);
|
// _setLoading(true);
|
||||||
_loggedin = await BackendService.isLoggedIn;
|
// _loggedin = await BackendService.isLoggedIn;
|
||||||
_setLoading(false);
|
// _setLoading(false);
|
||||||
}
|
// }
|
||||||
|
|
||||||
void _setLoading(bool loading) {
|
// void _setLoading(bool loading) {
|
||||||
setState(() {
|
// setState(() {
|
||||||
_loading = loading;
|
// _loading = loading;
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
|
|
||||||
void _checkResponse(ApiResponse response) {
|
void _checkResponse(ApiResponse response) {
|
||||||
if (response.status == Status.ERROR &&
|
if (response.status == Status.ERROR &&
|
||||||
response.message!.contains('unauthenticated')) {
|
(response.message!.contains('unauthenticated') ||
|
||||||
|
response.message!.contains('blocked'))) {
|
||||||
BackendService.logout();
|
BackendService.logout();
|
||||||
Navigator.of(context).pushAndRemoveUntil(
|
Navigator.of(context).pushAndRemoveUntil(
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
@ -50,7 +53,7 @@ class _PersonsPageState extends State<PersonsPage> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _loading = true;
|
bool _loading = false;
|
||||||
bool _loggedin = false;
|
bool _loggedin = false;
|
||||||
List<Person> persons = [];
|
List<Person> persons = [];
|
||||||
|
|
||||||
@ -61,13 +64,58 @@ class _PersonsPageState extends State<PersonsPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
List<Widget> _personsList(List<Person> persons) {
|
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 = [];
|
final List<Widget> list = [];
|
||||||
for (var p in persons) {
|
for (var p in persons) {
|
||||||
list.add(Card(
|
list.add(TextButton(
|
||||||
color: Colors.black,
|
onPressed: () async {
|
||||||
child: Text(
|
final Person per = await showPerson(context, person: p);
|
||||||
'${p.firstname} ${p.lastname}',
|
if (!per.id.isZero && !persons.contains(per)) {
|
||||||
style: const TextStyle(color: Colors.white),
|
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(
|
child: Scaffold(
|
||||||
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
|
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
|
||||||
floatingActionButton: FloatingActionButton(
|
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),
|
child: const Icon(Icons.add),
|
||||||
),
|
),
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
@ -163,7 +218,9 @@ class _PersonsPageState extends State<PersonsPage> {
|
|||||||
child: Consumer<PersonsViewModel>(
|
child: Consumer<PersonsViewModel>(
|
||||||
builder: (context, value, child) {
|
builder: (context, value, child) {
|
||||||
_checkResponse(value.response);
|
_checkResponse(value.response);
|
||||||
listPersons(context);
|
if (persons.isEmpty) {
|
||||||
|
listPersons(context);
|
||||||
|
}
|
||||||
return _loading
|
return _loading
|
||||||
? const CircularProgressIndicator(
|
? const CircularProgressIndicator(
|
||||||
color: Colors.grey,
|
color: Colors.grey,
|
@ -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/account_info.pb.dart';
|
||||||
import 'package:app/pb/rpc_get_account_info.pb.dart';
|
|
||||||
import 'package:app/widgets/background.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/loading_widget.dart';
|
||||||
import 'package:app/widgets/side_drawer.dart';
|
import 'package:app/widgets/side_drawer.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
@ -1,12 +1,9 @@
|
|||||||
import 'package:app/gapi/client.dart';
|
|
||||||
import 'package:app/model/services/backend_service.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/background.dart';
|
||||||
import 'package:app/widgets/bottom_bar.dart';
|
import 'package:app/widgets/bottom_bar.dart';
|
||||||
import 'package:app/widgets/loading_widget.dart';
|
import 'package:app/widgets/loading_widget.dart';
|
||||||
import 'package:app/widgets/side_drawer.dart';
|
import 'package:app/widgets/side_drawer.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:grpc/grpc.dart';
|
|
||||||
|
|
||||||
// GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
|
// GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
|
||||||
|
|
||||||
|
@ -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/background.dart';
|
||||||
import 'package:app/widgets/bottom_bar.dart';
|
import 'package:app/widgets/bottom_bar.dart';
|
||||||
import 'package:app/widgets/loading_widget.dart';
|
import 'package:app/widgets/loading_widget.dart';
|
||||||
import 'package:app/widgets/side_drawer.dart';
|
import 'package:app/widgets/side_drawer.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:grpc/grpc.dart';
|
|
||||||
|
|
||||||
class RegisterPage extends StatefulWidget {
|
class RegisterPage extends StatefulWidget {
|
||||||
const RegisterPage({
|
const RegisterPage({
|
||||||
|
@ -1,9 +1,6 @@
|
|||||||
import 'package:app/gapi/client.dart';
|
|
||||||
import 'package:app/model/apis/api_response.dart';
|
import 'package:app/model/apis/api_response.dart';
|
||||||
import 'package:app/model/view_model/account_vm.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/login_page.dart';
|
||||||
import 'package:app/pages_old/register_page.dart';
|
|
||||||
import 'package:app/pb/account.pb.dart';
|
import 'package:app/pb/account.pb.dart';
|
||||||
import 'package:app/widgets/background.dart';
|
import 'package:app/widgets/background.dart';
|
||||||
import 'package:app/widgets/bottom_bar.dart';
|
import 'package:app/widgets/bottom_bar.dart';
|
||||||
|
@ -2,10 +2,18 @@ import 'package:flutter/material.dart';
|
|||||||
|
|
||||||
class CustomColors {
|
class CustomColors {
|
||||||
static Color get error {
|
static Color get error {
|
||||||
return const Color.fromARGB(200, 255, 90, 90);
|
return const Color.fromARGB(255, 255, 82, 82);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Color get success {
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
0
frontend/app/lib/util/enums.dart
Normal file
@ -2,7 +2,7 @@ final emailRegExp = RegExp(r"^[a-zA-Z0-9.]+@[a-zA-Z0-9]+\.[a-zA-Z]+");
|
|||||||
final nameRegExp =
|
final nameRegExp =
|
||||||
RegExp(r"^\s*([A-Za-z]{1,}([\.,] |[-']| ))+[A-Za-z]+\.?\s*$");
|
RegExp(r"^\s*([A-Za-z]{1,}([\.,] |[-']| ))+[A-Za-z]+\.?\s*$");
|
||||||
final phoneRegExp = RegExp(r"^\+?0[0-9]{10}$");
|
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 {
|
extension valString on String {
|
||||||
bool get isValidEmail {
|
bool get isValidEmail {
|
||||||
@ -24,4 +24,10 @@ extension valString on String {
|
|||||||
bool get isValidPhone {
|
bool get isValidPhone {
|
||||||
return phoneRegExp.hasMatch(this);
|
return phoneRegExp.hasMatch(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String get titleCase {
|
||||||
|
return split(' ')
|
||||||
|
.map((str) => str[0].toUpperCase() + str.substring(1))
|
||||||
|
.join(' ');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,10 +7,13 @@ class BottomNavigation extends StatelessWidget {
|
|||||||
required this.children,
|
required this.children,
|
||||||
this.backgroundColor,
|
this.backgroundColor,
|
||||||
this.iconColor,
|
this.iconColor,
|
||||||
|
this.hideMenu,
|
||||||
}) {
|
}) {
|
||||||
|
hideMenu ??= false;
|
||||||
backgroundColor ??= Colors.black;
|
backgroundColor ??= Colors.black;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool? hideMenu;
|
||||||
List<Widget> children;
|
List<Widget> children;
|
||||||
Color? backgroundColor;
|
Color? backgroundColor;
|
||||||
Color? iconColor;
|
Color? iconColor;
|
||||||
@ -32,14 +35,18 @@ class BottomNavigation extends StatelessWidget {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
...children,
|
...children,
|
||||||
Builder(builder: (context) {
|
if (!hideMenu!)
|
||||||
return IconButton(
|
Builder(
|
||||||
onPressed: () => Scaffold.of(context).openDrawer(),
|
builder: (context) {
|
||||||
icon: const Icon(
|
return IconButton(
|
||||||
Icons.menu,
|
onPressed: () => Scaffold.of(context).openDrawer(),
|
||||||
color: Colors.white,
|
icon: const Icon(
|
||||||
));
|
Icons.menu,
|
||||||
}),
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -6,6 +6,10 @@
|
|||||||
|
|
||||||
#include "generated_plugin_registrant.h"
|
#include "generated_plugin_registrant.h"
|
||||||
|
|
||||||
|
#include <flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h>
|
||||||
|
|
||||||
void fl_register_plugins(FlPluginRegistry* registry) {
|
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);
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
list(APPEND FLUTTER_PLUGIN_LIST
|
list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
|
flutter_secure_storage_linux
|
||||||
)
|
)
|
||||||
|
|
||||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||||
|
@ -89,6 +89,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.1"
|
version: "1.3.1"
|
||||||
|
ffi:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: ffi
|
||||||
|
sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.0"
|
||||||
fixnum:
|
fixnum:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -110,11 +118,77 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.3"
|
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:
|
flutter_test:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
|
flutter_web_plugins:
|
||||||
|
dependency: transitive
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
googleapis_auth:
|
googleapis_auth:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -155,6 +229,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.0.2"
|
version: "4.0.2"
|
||||||
|
intl:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: intl
|
||||||
|
sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.18.1"
|
||||||
js:
|
js:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -171,6 +253,46 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.1"
|
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:
|
matcher:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -211,6 +333,70 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.8.3"
|
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:
|
pointycastle:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -336,6 +522,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.1.4-beta"
|
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:
|
sdks:
|
||||||
dart: ">=3.1.4 <4.0.0"
|
dart: ">=3.1.4 <4.0.0"
|
||||||
flutter: ">=3.3.0"
|
flutter: ">=3.10.0"
|
||||||
|
@ -28,11 +28,14 @@ environment:
|
|||||||
# the latest version available on pub.dev. To see which dependencies have newer
|
# the latest version available on pub.dev. To see which dependencies have newer
|
||||||
# versions available, run `flutter pub outdated`.
|
# versions available, run `flutter pub outdated`.
|
||||||
dependencies:
|
dependencies:
|
||||||
|
local_auth: ^2.1.7
|
||||||
async: ^2.2.0
|
async: ^2.2.0
|
||||||
protobuf: ^3.0.0
|
protobuf: ^3.0.0
|
||||||
collection: ^1.15.0-nullsafety.4
|
collection: ^1.15.0-nullsafety.4
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
flutter_localizations:
|
||||||
|
sdk: flutter
|
||||||
|
|
||||||
|
|
||||||
# The following adds the Cupertino Icons font to your application.
|
# The following adds the Cupertino Icons font to your application.
|
||||||
@ -43,6 +46,8 @@ dependencies:
|
|||||||
path: ^1.8.3
|
path: ^1.8.3
|
||||||
fixnum: ^1.1.0
|
fixnum: ^1.1.0
|
||||||
provider: ^6.0.5
|
provider: ^6.0.5
|
||||||
|
intl: ^0.18.1
|
||||||
|
flutter_secure_storage: ^9.0.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
lints: ^2.0.0
|
lints: ^2.0.0
|
||||||
@ -69,6 +74,8 @@ flutter:
|
|||||||
|
|
||||||
# To add assets to your application, add an assets section, like this:
|
# To add assets to your application, add an assets section, like this:
|
||||||
assets:
|
assets:
|
||||||
|
- assets/chat_bubbles.png
|
||||||
|
- assets/JPEG.jpg
|
||||||
- lib/assets/logo_300x200.png
|
- lib/assets/logo_300x200.png
|
||||||
- lib/assets/hero-pattern-300x200.png
|
- lib/assets/hero-pattern-300x200.png
|
||||||
# - images/a_dot_burr.jpeg
|
# - images/a_dot_burr.jpeg
|
||||||
|
@ -6,6 +6,12 @@
|
|||||||
|
|
||||||
#include "generated_plugin_registrant.h"
|
#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) {
|
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||||
|
FlutterSecureStorageWindowsPluginRegisterWithRegistrar(
|
||||||
|
registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin"));
|
||||||
|
LocalAuthPluginRegisterWithRegistrar(
|
||||||
|
registry->GetRegistrarForPlugin("LocalAuthPlugin"));
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,8 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
list(APPEND FLUTTER_PLUGIN_LIST
|
list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
|
flutter_secure_storage_windows
|
||||||
|
local_auth_windows
|
||||||
)
|
)
|
||||||
|
|
||||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||||
|