Merge branch 'ft/app'
@ -45,7 +45,7 @@ android {
|
||||
applicationId "com.example.app"
|
||||
// You can update the following values to match your application needs.
|
||||
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
|
||||
minSdkVersion flutter.minSdkVersion
|
||||
minSdkVersion 18
|
||||
targetSdkVersion flutter.targetSdkVersion
|
||||
versionCode flutterVersionCode.toInteger()
|
||||
versionName flutterVersionName
|
||||
|
@ -1,4 +1,6 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
|
||||
<!-- <uses-permission android:name="android.permission.USE_FINGERPRINT"/> -->
|
||||
<application
|
||||
android:label="app"
|
||||
android:name="${applicationName}"
|
||||
|
@ -1,6 +1,11 @@
|
||||
package com.example.app
|
||||
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
import io.flutter.embedding.android.FlutterFragmentActivity
|
||||
import io.flutter.embedding.engine.FlutterEngine
|
||||
import io.flutter.plugins.GeneratedPluginRegistrant
|
||||
|
||||
class MainActivity: FlutterActivity() {
|
||||
class MainActivity: FlutterFragmentActivity() {
|
||||
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
||||
GeneratedPluginRegistrant.registerWith(flutterEngine)
|
||||
}
|
||||
}
|
||||
|
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_localizations/flutter_localizations.dart'
|
||||
show GlobalMaterialLocalizations;
|
||||
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
runApp(
|
||||
MaterialApp(
|
||||
localizationsDelegates: const [GlobalMaterialLocalizations.delegate],
|
||||
supportedLocales: const [Locale('en'), Locale('de')],
|
||||
theme: ThemeData().copyWith(
|
||||
canvasColor: Colors.black,
|
||||
colorScheme: const ColorScheme(
|
||||
brightness: Brightness.dark,
|
||||
primary: Colors.white,
|
||||
@ -41,9 +49,64 @@ void main() async {
|
||||
backgroundColor: Colors.black,
|
||||
foregroundColor: Colors.white,
|
||||
)),
|
||||
home: HomePage(
|
||||
loggedOut: false,
|
||||
),
|
||||
home: const DigitalerFrieden(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class DigitalerFrieden extends StatefulWidget {
|
||||
const DigitalerFrieden({super.key});
|
||||
|
||||
@override
|
||||
State<DigitalerFrieden> createState() => _DigitalerFriedenState();
|
||||
}
|
||||
|
||||
class _DigitalerFriedenState extends State<DigitalerFrieden> {
|
||||
final StorageService _storageService = StorageService();
|
||||
int? accountLevel;
|
||||
bool? authenticated;
|
||||
bool _loading = true;
|
||||
@override
|
||||
void initState() {
|
||||
_init();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
void _init() async {
|
||||
accountLevel = await _storageService.accountLevel;
|
||||
if (accountLevel! > 0) {
|
||||
authenticated = await AuthService.authenticateWithBiometrics();
|
||||
}
|
||||
setState(() {
|
||||
_loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (_loading) {
|
||||
return SafeArea(
|
||||
child: Center(
|
||||
child: Column(
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 150,
|
||||
),
|
||||
Hero(
|
||||
tag: 'logo',
|
||||
child: Image.asset(
|
||||
'assets/JPEG.jpg',
|
||||
height: 180,
|
||||
),
|
||||
),
|
||||
CircularProgressIndicator(
|
||||
color: CustomColors.primary,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
return const StartPage();
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ class AppException implements Exception {
|
||||
|
||||
AppException([this._message, this._prefix]);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
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_login.pb.dart';
|
||||
import 'package:app/pb/rpc_refresh_token.pb.dart';
|
||||
import 'package:app/pb/rpc_update_person.pb.dart';
|
||||
import 'package:app/pb/service_df.pbgrpc.dart';
|
||||
import 'package:fixnum/fixnum.dart';
|
||||
import 'package:grpc/grpc.dart';
|
||||
@ -114,6 +115,10 @@ class BackendService {
|
||||
return true;
|
||||
}
|
||||
|
||||
static Future<Int64?> get accountId async {
|
||||
return (await Session.session).accountId;
|
||||
}
|
||||
|
||||
static Future<bool> createAccount(
|
||||
{required String email, required String password}) async {
|
||||
try {
|
||||
@ -126,7 +131,7 @@ class BackendService {
|
||||
} on SocketException {
|
||||
throw FetchDataException('Keine Internet Verbindung');
|
||||
} on GrpcError catch (err) {
|
||||
throw FetchDataException(err.message);
|
||||
throw FetchDataException('${err.message}');
|
||||
} catch (err) {
|
||||
throw InternalException(err.toString());
|
||||
}
|
||||
@ -207,14 +212,15 @@ class BackendService {
|
||||
}
|
||||
}
|
||||
|
||||
Future<Person> createPerson(
|
||||
{required String firstname,
|
||||
required String lastname,
|
||||
required String street,
|
||||
required String zip,
|
||||
required String city,
|
||||
required String country,
|
||||
required DateTime birthday}) async {
|
||||
Future<Person> updatePerson(
|
||||
{required Int64 id,
|
||||
String? firstname,
|
||||
String? lastname,
|
||||
String? street,
|
||||
String? zip,
|
||||
String? city,
|
||||
String? country,
|
||||
Timestamp? birthday}) async {
|
||||
Session session = await Session.session;
|
||||
if (session.accessTokenExpiresAt == null) {
|
||||
throw UnauthorizedException('Keine Siztung gefunden');
|
||||
@ -226,16 +232,74 @@ class BackendService {
|
||||
}
|
||||
}
|
||||
try {
|
||||
final CreatePersonResponse response = await _client.createPerson(
|
||||
CreatePersonRequest(
|
||||
accountId: session.accountId,
|
||||
lastname: lastname,
|
||||
firstname: firstname,
|
||||
street: street,
|
||||
zip: zip,
|
||||
country: country,
|
||||
birthday: Timestamp.fromDateTime(birthday),
|
||||
),
|
||||
final UpdatePersonRequest req = UpdatePersonRequest(
|
||||
id: id,
|
||||
);
|
||||
|
||||
if (lastname != null) {
|
||||
req.lastname = lastname;
|
||||
}
|
||||
if (firstname != null) {
|
||||
req.firstname = firstname;
|
||||
}
|
||||
if (street != null) {
|
||||
req.street = street;
|
||||
}
|
||||
if (city != null) {
|
||||
req.city = city;
|
||||
}
|
||||
if (zip != null) {
|
||||
req.zip = zip;
|
||||
}
|
||||
if (country != null) {
|
||||
req.country = country;
|
||||
}
|
||||
if (birthday != null) {
|
||||
req.birthday = birthday;
|
||||
}
|
||||
final UpdatePersonResponse response = await _client.updatePerson(req,
|
||||
options: CallOptions(
|
||||
metadata: {'Authorization': 'Bearer ${session.accessToken}'}));
|
||||
return response.person;
|
||||
} on SocketException {
|
||||
throw FetchDataException('Keine Internet Verbindung');
|
||||
} on GrpcError catch (err) {
|
||||
throw FetchDataException(err.message);
|
||||
} catch (err) {
|
||||
throw InternalException(err.toString());
|
||||
}
|
||||
}
|
||||
|
||||
Future<Person> createPerson(
|
||||
{required String firstname,
|
||||
required String lastname,
|
||||
required String street,
|
||||
required String zip,
|
||||
required String city,
|
||||
required String country,
|
||||
required Timestamp birthday}) async {
|
||||
Session session = await Session.session;
|
||||
if (session.accessTokenExpiresAt == null) {
|
||||
throw UnauthorizedException('Keine Siztung gefunden');
|
||||
}
|
||||
if (session.accessTokenExpiresAt!.toDateTime().isBefore(DateTime.now())) {
|
||||
session = await refreshToken(session);
|
||||
if (session.accessTokenExpiresAt == null) {
|
||||
throw UnauthorizedException('Sitzung ist abgelaufen');
|
||||
}
|
||||
}
|
||||
try {
|
||||
final CreatePersonRequest req = CreatePersonRequest(
|
||||
accountId: session.accountId,
|
||||
lastname: lastname,
|
||||
firstname: firstname,
|
||||
street: street,
|
||||
city: city,
|
||||
zip: zip,
|
||||
country: country,
|
||||
birthday: birthday,
|
||||
);
|
||||
final CreatePersonResponse response = await _client.createPerson(req,
|
||||
options: CallOptions(
|
||||
metadata: {'Authorization': 'Bearer ${session.accessToken}'}));
|
||||
return response.person;
|
||||
|
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() {
|
||||
_init();
|
||||
}
|
||||
ApiResponse _apiResponse = ApiResponse.initial('Keine Daten');
|
||||
final ApiResponse _apiResponse = ApiResponse.initial('Keine Daten');
|
||||
|
||||
final BackendService _service = BackendService();
|
||||
Account? _account;
|
||||
|
||||
@override
|
||||
ApiResponse get response {
|
||||
return _apiResponse;
|
||||
}
|
||||
@ -21,7 +22,7 @@ class AccountViewModel extends BaseViewModel {
|
||||
}
|
||||
|
||||
void _init() async {
|
||||
super.init();
|
||||
// super.init();
|
||||
// try {
|
||||
// _apiResponse = ApiResponse.completed(await _service.getAccount());
|
||||
// } catch (e) {
|
||||
|
@ -1,12 +1,12 @@
|
||||
import 'package:app/model/apis/api_response.dart';
|
||||
import 'package:app/model/services/backend_service.dart';
|
||||
import 'package:app/pages/home_page.dart';
|
||||
import 'package:app/pages_draft/home_page.dart';
|
||||
import 'package:app/util/colors.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class BaseViewModel with ChangeNotifier {
|
||||
BaseViewModel() {
|
||||
init();
|
||||
// init();
|
||||
}
|
||||
ApiResponse _apiResponse = ApiResponse.initial('Keine Daten');
|
||||
|
||||
@ -16,16 +16,16 @@ class BaseViewModel with ChangeNotifier {
|
||||
return _apiResponse;
|
||||
}
|
||||
|
||||
void init() async {
|
||||
// if (await BackendService.isLoggedIn) {
|
||||
try {
|
||||
_apiResponse = ApiResponse.completed(await _service.getAccount());
|
||||
} catch (e) {
|
||||
_apiResponse = ApiResponse.error(e.toString());
|
||||
}
|
||||
notifyListeners();
|
||||
// }
|
||||
}
|
||||
// void init() async {
|
||||
// // if (await BackendService.isLoggedIn) {
|
||||
// try {
|
||||
// _apiResponse = ApiResponse.completed(await _service.getAccount());
|
||||
// } catch (e) {
|
||||
// _apiResponse = ApiResponse.error(e.toString());
|
||||
// }
|
||||
// notifyListeners();
|
||||
// // }
|
||||
// }
|
||||
|
||||
Future<bool> isLoggedIn(BuildContext context) async {
|
||||
final messenger = ScaffoldMessenger.of(context);
|
||||
|
@ -1,6 +1,9 @@
|
||||
import 'package:app/model/apis/api_response.dart';
|
||||
import 'package:app/model/services/backend_service.dart';
|
||||
import 'package:app/pb/google/protobuf/timestamp.pb.dart';
|
||||
import 'package:app/pb/person.pb.dart';
|
||||
import 'package:app/util/colors.dart';
|
||||
import 'package:fixnum/fixnum.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class PersonsViewModel with ChangeNotifier {
|
||||
@ -35,8 +38,9 @@ class PersonsViewModel with ChangeNotifier {
|
||||
required String zip,
|
||||
required String city,
|
||||
required String country,
|
||||
required DateTime birthday}) async {
|
||||
required Timestamp birthday}) async {
|
||||
Person person = Person();
|
||||
final messenger = ScaffoldMessenger.of(context);
|
||||
_apiResponse = ApiResponse.loading('Erstelle Person');
|
||||
try {
|
||||
person = await _service.createPerson(
|
||||
@ -47,8 +51,102 @@ class PersonsViewModel with ChangeNotifier {
|
||||
city: city,
|
||||
country: country,
|
||||
birthday: birthday);
|
||||
messenger.showSnackBar(SnackBar(
|
||||
backgroundColor: CustomColors.success,
|
||||
content: const Text(
|
||||
'Gepeichert',
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
));
|
||||
_apiResponse = ApiResponse.completed(person);
|
||||
} catch (err) {
|
||||
messenger.showSnackBar(SnackBar(
|
||||
backgroundColor: CustomColors.error,
|
||||
content: const Text(
|
||||
'Fehler beim Speichern',
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
action: SnackBarAction(
|
||||
label: 'Details',
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
backgroundColor: Colors.black,
|
||||
icon: Icon(
|
||||
Icons.error,
|
||||
color: CustomColors.error,
|
||||
),
|
||||
content: Text(
|
||||
err.toString(),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
));
|
||||
},
|
||||
),
|
||||
));
|
||||
_apiResponse = ApiResponse.error(err.toString());
|
||||
}
|
||||
notifyListeners();
|
||||
return person;
|
||||
}
|
||||
|
||||
Future<Person> updatePerson(BuildContext context,
|
||||
{required Int64 id,
|
||||
String? firstname,
|
||||
String? lastname,
|
||||
String? street,
|
||||
String? zip,
|
||||
String? city,
|
||||
String? country,
|
||||
Timestamp? birthday}) async {
|
||||
Person person = Person();
|
||||
final messenger = ScaffoldMessenger.of(context);
|
||||
_apiResponse = ApiResponse.loading('Erstelle Person');
|
||||
try {
|
||||
person = await _service.updatePerson(
|
||||
id: id,
|
||||
firstname: firstname,
|
||||
lastname: lastname,
|
||||
street: street,
|
||||
zip: zip,
|
||||
city: city,
|
||||
country: country,
|
||||
birthday: birthday);
|
||||
messenger.showSnackBar(SnackBar(
|
||||
backgroundColor: CustomColors.success,
|
||||
content: const Text(
|
||||
'Gepeichert',
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
));
|
||||
_apiResponse = ApiResponse.completed(person);
|
||||
} catch (err) {
|
||||
messenger.showSnackBar(SnackBar(
|
||||
backgroundColor: CustomColors.error,
|
||||
content: const Text(
|
||||
'Fehler beim Speichern',
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
action: SnackBarAction(
|
||||
label: 'Details',
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
backgroundColor: Colors.black,
|
||||
icon: Icon(
|
||||
Icons.error,
|
||||
color: CustomColors.error,
|
||||
),
|
||||
content: Text(
|
||||
err.toString(),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
));
|
||||
},
|
||||
),
|
||||
));
|
||||
_apiResponse = ApiResponse.error(err.toString());
|
||||
}
|
||||
notifyListeners();
|
||||
|
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/view_model/account_vm.dart';
|
||||
import 'package:app/pages/login_overlay.dart';
|
||||
import 'package:app/pages/persons_page.dart';
|
||||
import 'package:app/model/view_model/base_vm.dart';
|
||||
import 'package:app/pages_draft/login_overlay.dart';
|
||||
import 'package:app/pages_draft/persons_page.dart';
|
||||
import 'package:app/widgets/background.dart';
|
||||
import 'package:app/widgets/bottom_navigation.dart';
|
||||
import 'package:app/widgets/bottom_navigation_item.dart';
|
||||
@ -48,7 +49,9 @@ class _HomePageState extends State<HomePage> {
|
||||
|
||||
_isLoggedIn(BuildContext context) async {
|
||||
bool logged = await vm.isLoggedIn(context);
|
||||
_loggedin = logged;
|
||||
setState(() {
|
||||
_loggedin = logged;
|
||||
});
|
||||
}
|
||||
|
||||
void _setLoading(bool loading) {
|
||||
@ -137,9 +140,10 @@ class _HomePageState extends State<HomePage> {
|
||||
),
|
||||
BottomNavigationItem(
|
||||
onPressed: () async {
|
||||
await showLogin(context);
|
||||
bool res = await showLogin(context);
|
||||
setState(() {
|
||||
vm.isLoggedIn(context);
|
||||
_loggedin = res;
|
||||
// vm.isLoggedIn(context);
|
||||
});
|
||||
},
|
||||
icon: Icons.login,
|
||||
@ -150,7 +154,7 @@ class _HomePageState extends State<HomePage> {
|
||||
BottomNavigationItem(
|
||||
onPressed: () async {
|
||||
final navigator = Navigator.of(context);
|
||||
if (await vm.isLoggedIn(context)) {
|
||||
if (_loggedin) {
|
||||
navigator.push(MaterialPageRoute(
|
||||
builder: (builder) => const PersonsPage()));
|
||||
} else {
|
||||
@ -176,10 +180,9 @@ class _HomePageState extends State<HomePage> {
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 45, 16, 16),
|
||||
child: Center(
|
||||
child: ChangeNotifierProvider<AccountViewModel>(
|
||||
create: (context) => AccountViewModel(),
|
||||
child:
|
||||
Consumer<AccountViewModel>(builder: (context, value, child) {
|
||||
child: ChangeNotifierProvider<BaseViewModel>(
|
||||
create: (context) => BaseViewModel(),
|
||||
child: Consumer<BaseViewModel>(builder: (context, value, child) {
|
||||
// _checkResponse(value.response);
|
||||
if (!widget.loggedOut) {
|
||||
_isLoggedIn(context);
|
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/services/backend_service.dart';
|
||||
import 'package:app/model/view_model/persons_vm.dart';
|
||||
import 'package:app/pages/home_page.dart';
|
||||
import 'package:app/pages_draft/home_page.dart';
|
||||
import 'package:app/pages_draft/person_details_page.dart';
|
||||
import 'package:app/pb/person.pb.dart';
|
||||
import 'package:app/util/validation.dart';
|
||||
import 'package:app/widgets/background.dart';
|
||||
import 'package:app/widgets/bottom_navigation.dart';
|
||||
import 'package:app/widgets/bottom_navigation_item.dart';
|
||||
@ -22,24 +24,25 @@ class _PersonsPageState extends State<PersonsPage> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_init();
|
||||
// _init();
|
||||
}
|
||||
|
||||
void _init() async {
|
||||
_setLoading(true);
|
||||
_loggedin = await BackendService.isLoggedIn;
|
||||
_setLoading(false);
|
||||
}
|
||||
// void _init() async {
|
||||
// _setLoading(true);
|
||||
// _loggedin = await BackendService.isLoggedIn;
|
||||
// _setLoading(false);
|
||||
// }
|
||||
|
||||
void _setLoading(bool loading) {
|
||||
setState(() {
|
||||
_loading = loading;
|
||||
});
|
||||
}
|
||||
// void _setLoading(bool loading) {
|
||||
// setState(() {
|
||||
// _loading = loading;
|
||||
// });
|
||||
// }
|
||||
|
||||
void _checkResponse(ApiResponse response) {
|
||||
if (response.status == Status.ERROR &&
|
||||
response.message!.contains('unauthenticated')) {
|
||||
(response.message!.contains('unauthenticated') ||
|
||||
response.message!.contains('blocked'))) {
|
||||
BackendService.logout();
|
||||
Navigator.of(context).pushAndRemoveUntil(
|
||||
MaterialPageRoute(
|
||||
@ -50,7 +53,7 @@ class _PersonsPageState extends State<PersonsPage> {
|
||||
}
|
||||
}
|
||||
|
||||
bool _loading = true;
|
||||
bool _loading = false;
|
||||
bool _loggedin = false;
|
||||
List<Person> persons = [];
|
||||
|
||||
@ -61,13 +64,58 @@ class _PersonsPageState extends State<PersonsPage> {
|
||||
}
|
||||
|
||||
List<Widget> _personsList(List<Person> persons) {
|
||||
persons.sort((a, b) {
|
||||
final comp = a.lastname.compareTo(b.lastname);
|
||||
if (comp != 0) {
|
||||
return comp;
|
||||
}
|
||||
return a.firstname.compareTo(b.firstname);
|
||||
});
|
||||
final List<Widget> list = [];
|
||||
for (var p in persons) {
|
||||
list.add(Card(
|
||||
color: Colors.black,
|
||||
child: Text(
|
||||
'${p.firstname} ${p.lastname}',
|
||||
style: const TextStyle(color: Colors.white),
|
||||
list.add(TextButton(
|
||||
onPressed: () async {
|
||||
final Person per = await showPerson(context, person: p);
|
||||
if (!per.id.isZero && !persons.contains(per)) {
|
||||
setState(() {
|
||||
this.persons.add(per);
|
||||
});
|
||||
}
|
||||
},
|
||||
child: Card(
|
||||
shape:
|
||||
RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
color: const Color.fromARGB(100, 89, 88, 88),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 14),
|
||||
child: Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 40,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
p.lastname.titleCase,
|
||||
style: const TextStyle(color: Colors.white),
|
||||
// overflow: TextOverflow.fade,
|
||||
textAlign: TextAlign.start,
|
||||
),
|
||||
const Spacer(),
|
||||
Text(
|
||||
p.firstname.titleCase,
|
||||
style: const TextStyle(color: Colors.white),
|
||||
textAlign: TextAlign.start,
|
||||
// overflow: TextOverflow.fade,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
const Text('STATUS')
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
));
|
||||
}
|
||||
@ -80,7 +128,14 @@ class _PersonsPageState extends State<PersonsPage> {
|
||||
child: Scaffold(
|
||||
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: () {},
|
||||
onPressed: () async {
|
||||
final p = await showPerson(context);
|
||||
if (!p.id.isZero && !persons.contains(p)) {
|
||||
setState(() {
|
||||
persons.add(p);
|
||||
});
|
||||
}
|
||||
},
|
||||
child: const Icon(Icons.add),
|
||||
),
|
||||
appBar: AppBar(
|
||||
@ -163,7 +218,9 @@ class _PersonsPageState extends State<PersonsPage> {
|
||||
child: Consumer<PersonsViewModel>(
|
||||
builder: (context, value, child) {
|
||||
_checkResponse(value.response);
|
||||
listPersons(context);
|
||||
if (persons.isEmpty) {
|
||||
listPersons(context);
|
||||
}
|
||||
return _loading
|
||||
? const CircularProgressIndicator(
|
||||
color: Colors.grey,
|
@ -1,9 +1,5 @@
|
||||
import 'package:app/gapi/client.dart';
|
||||
import 'package:app/pages_old/start_page.dart';
|
||||
import 'package:app/pb/account_info.pb.dart';
|
||||
import 'package:app/pb/rpc_get_account_info.pb.dart';
|
||||
import 'package:app/widgets/background.dart';
|
||||
import 'package:app/widgets/bottom_bar.dart';
|
||||
import 'package:app/widgets/loading_widget.dart';
|
||||
import 'package:app/widgets/side_drawer.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
@ -1,12 +1,9 @@
|
||||
import 'package:app/gapi/client.dart';
|
||||
import 'package:app/model/services/backend_service.dart';
|
||||
import 'package:app/pages_old/start_page.dart';
|
||||
import 'package:app/widgets/background.dart';
|
||||
import 'package:app/widgets/bottom_bar.dart';
|
||||
import 'package:app/widgets/loading_widget.dart';
|
||||
import 'package:app/widgets/side_drawer.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:grpc/grpc.dart';
|
||||
|
||||
// GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
|
||||
|
||||
|
@ -1,11 +1,8 @@
|
||||
import 'package:app/gapi/client.dart';
|
||||
import 'package:app/pages_old/start_page.dart';
|
||||
import 'package:app/widgets/background.dart';
|
||||
import 'package:app/widgets/bottom_bar.dart';
|
||||
import 'package:app/widgets/loading_widget.dart';
|
||||
import 'package:app/widgets/side_drawer.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:grpc/grpc.dart';
|
||||
|
||||
class RegisterPage extends StatefulWidget {
|
||||
const RegisterPage({
|
||||
|
@ -1,9 +1,6 @@
|
||||
import 'package:app/gapi/client.dart';
|
||||
import 'package:app/model/apis/api_response.dart';
|
||||
import 'package:app/model/view_model/account_vm.dart';
|
||||
import 'package:app/pages_old/dashboard_page.dart';
|
||||
import 'package:app/pages_old/login_page.dart';
|
||||
import 'package:app/pages_old/register_page.dart';
|
||||
import 'package:app/pb/account.pb.dart';
|
||||
import 'package:app/widgets/background.dart';
|
||||
import 'package:app/widgets/bottom_bar.dart';
|
||||
|
@ -2,10 +2,18 @@ import 'package:flutter/material.dart';
|
||||
|
||||
class CustomColors {
|
||||
static Color get error {
|
||||
return const Color.fromARGB(200, 255, 90, 90);
|
||||
return const Color.fromARGB(255, 255, 82, 82);
|
||||
}
|
||||
|
||||
static Color get success {
|
||||
return const Color.fromARGB(200, 55, 125, 55);
|
||||
return const Color.fromARGB(255, 51, 217, 178);
|
||||
}
|
||||
|
||||
static Color get primary {
|
||||
return const Color.fromARGB(255, 51, 217, 178);
|
||||
}
|
||||
|
||||
static Color get secondary {
|
||||
return const Color.fromARGB(255, 52, 172, 224);
|
||||
}
|
||||
}
|
||||
|
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 =
|
||||
RegExp(r"^\s*([A-Za-z]{1,}([\.,] |[-']| ))+[A-Za-z]+\.?\s*$");
|
||||
final phoneRegExp = RegExp(r"^\+?0[0-9]{10}$");
|
||||
final passwordRegExp = RegExp(r'^.+$');
|
||||
final passwordRegExp = RegExp(r'^[0-9a-zA-Z\-\_\.\,\*\+\=?!]{12,64}$');
|
||||
|
||||
extension valString on String {
|
||||
bool get isValidEmail {
|
||||
@ -24,4 +24,10 @@ extension valString on String {
|
||||
bool get isValidPhone {
|
||||
return phoneRegExp.hasMatch(this);
|
||||
}
|
||||
|
||||
String get titleCase {
|
||||
return split(' ')
|
||||
.map((str) => str[0].toUpperCase() + str.substring(1))
|
||||
.join(' ');
|
||||
}
|
||||
}
|
||||
|
@ -7,10 +7,13 @@ class BottomNavigation extends StatelessWidget {
|
||||
required this.children,
|
||||
this.backgroundColor,
|
||||
this.iconColor,
|
||||
this.hideMenu,
|
||||
}) {
|
||||
hideMenu ??= false;
|
||||
backgroundColor ??= Colors.black;
|
||||
}
|
||||
|
||||
bool? hideMenu;
|
||||
List<Widget> children;
|
||||
Color? backgroundColor;
|
||||
Color? iconColor;
|
||||
@ -32,14 +35,18 @@ class BottomNavigation extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
...children,
|
||||
Builder(builder: (context) {
|
||||
return IconButton(
|
||||
onPressed: () => Scaffold.of(context).openDrawer(),
|
||||
icon: const Icon(
|
||||
Icons.menu,
|
||||
color: Colors.white,
|
||||
));
|
||||
}),
|
||||
if (!hideMenu!)
|
||||
Builder(
|
||||
builder: (context) {
|
||||
return IconButton(
|
||||
onPressed: () => Scaffold.of(context).openDrawer(),
|
||||
icon: const Icon(
|
||||
Icons.menu,
|
||||
color: Colors.white,
|
||||
),
|
||||
);
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -6,6 +6,10 @@
|
||||
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h>
|
||||
|
||||
void fl_register_plugins(FlPluginRegistry* registry) {
|
||||
g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin");
|
||||
flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar);
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
#
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
flutter_secure_storage_linux
|
||||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
|
@ -89,6 +89,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.1"
|
||||
ffi:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: ffi
|
||||
sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
fixnum:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -110,11 +118,77 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.3"
|
||||
flutter_localizations:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_plugin_android_lifecycle:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_plugin_android_lifecycle
|
||||
sha256: b068ffc46f82a55844acfa4fdbb61fad72fa2aef0905548419d97f0f95c456da
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.17"
|
||||
flutter_secure_storage:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_secure_storage
|
||||
sha256: ffdbb60130e4665d2af814a0267c481bcf522c41ae2e43caf69fa0146876d685
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "9.0.0"
|
||||
flutter_secure_storage_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_secure_storage_linux
|
||||
sha256: "3d5032e314774ee0e1a7d0a9f5e2793486f0dff2dd9ef5a23f4e3fb2a0ae6a9e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
flutter_secure_storage_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_secure_storage_macos
|
||||
sha256: bd33935b4b628abd0b86c8ca20655c5b36275c3a3f5194769a7b3f37c905369c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
flutter_secure_storage_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_secure_storage_platform_interface
|
||||
sha256: "0d4d3a5dd4db28c96ae414d7ba3b8422fd735a8255642774803b2532c9a61d7e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.2"
|
||||
flutter_secure_storage_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_secure_storage_web
|
||||
sha256: "30f84f102df9dcdaa2241866a958c2ec976902ebdaa8883fbfe525f1f2f3cf20"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.2"
|
||||
flutter_secure_storage_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_secure_storage_windows
|
||||
sha256: "5809c66f9dd3b4b93b0a6e2e8561539405322ee767ac2f64d084e2ab5429d108"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.0"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_web_plugins:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
googleapis_auth:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -155,6 +229,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.2"
|
||||
intl:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: intl
|
||||
sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.18.1"
|
||||
js:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -171,6 +253,46 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
local_auth:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: local_auth
|
||||
sha256: "7e6c63082e399b61e4af71266b012e767a5d4525dd6e9ba41e174fd42d76e115"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.7"
|
||||
local_auth_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: local_auth_android
|
||||
sha256: df4ccb3193525b8a60c78a5ca7bf188a47705bcf77bcc837a6b2cf6da64ae0e2
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.35"
|
||||
local_auth_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: local_auth_ios
|
||||
sha256: "8293faf72ef0ac4710f209edd03916c2d4c1eeab0483bdcf9b2e659c2f7d737b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.5"
|
||||
local_auth_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: local_auth_platform_interface
|
||||
sha256: fc5bd537970a324260fda506cfb61b33ad7426f37a8ea5c461cf612161ebba54
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.8"
|
||||
local_auth_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: local_auth_windows
|
||||
sha256: "505ba3367ca781efb1c50d3132e44a2446bccc4163427bc203b9b4d8994d97ea"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.10"
|
||||
matcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -211,6 +333,70 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.8.3"
|
||||
path_provider:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider
|
||||
sha256: a1aa8aaa2542a6bc57e381f132af822420216c80d4781f7aa085ca3229208aaa
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
path_provider_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_android
|
||||
sha256: e595b98692943b4881b219f0a9e3945118d3c16bd7e2813f98ec6e532d905f72
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.1"
|
||||
path_provider_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_foundation
|
||||
sha256: "19314d595120f82aca0ba62787d58dde2cc6b5df7d2f0daf72489e38d1b57f2d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.1"
|
||||
path_provider_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_linux
|
||||
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.1"
|
||||
path_provider_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_platform_interface
|
||||
sha256: "94b1e0dd80970c1ce43d5d4e050a9918fce4f4a775e6142424c30a29a363265c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
path_provider_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_windows
|
||||
sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.1"
|
||||
platform:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: platform
|
||||
sha256: "0a279f0707af40c890e80b1e9df8bb761694c074ba7e1d4ab1bc4b728e200b59"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.3"
|
||||
plugin_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: plugin_platform_interface
|
||||
sha256: da3fdfeccc4d4ff2da8f8c556704c08f912542c5fb3cf2233ed75372384a034d
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.6"
|
||||
pointycastle:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -336,6 +522,22 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.4-beta"
|
||||
win32:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: win32
|
||||
sha256: "350a11abd2d1d97e0cc7a28a81b781c08002aa2864d9e3f192ca0ffa18b06ed3"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.0.9"
|
||||
xdg_directories:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: xdg_directories
|
||||
sha256: "589ada45ba9e39405c198fe34eb0f607cddb2108527e658136120892beac46d2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.3"
|
||||
sdks:
|
||||
dart: ">=3.1.4 <4.0.0"
|
||||
flutter: ">=3.3.0"
|
||||
flutter: ">=3.10.0"
|
||||
|
@ -28,11 +28,14 @@ environment:
|
||||
# the latest version available on pub.dev. To see which dependencies have newer
|
||||
# versions available, run `flutter pub outdated`.
|
||||
dependencies:
|
||||
local_auth: ^2.1.7
|
||||
async: ^2.2.0
|
||||
protobuf: ^3.0.0
|
||||
collection: ^1.15.0-nullsafety.4
|
||||
flutter:
|
||||
sdk: flutter
|
||||
flutter_localizations:
|
||||
sdk: flutter
|
||||
|
||||
|
||||
# The following adds the Cupertino Icons font to your application.
|
||||
@ -43,6 +46,8 @@ dependencies:
|
||||
path: ^1.8.3
|
||||
fixnum: ^1.1.0
|
||||
provider: ^6.0.5
|
||||
intl: ^0.18.1
|
||||
flutter_secure_storage: ^9.0.0
|
||||
|
||||
dev_dependencies:
|
||||
lints: ^2.0.0
|
||||
@ -69,6 +74,8 @@ flutter:
|
||||
|
||||
# To add assets to your application, add an assets section, like this:
|
||||
assets:
|
||||
- assets/chat_bubbles.png
|
||||
- assets/JPEG.jpg
|
||||
- lib/assets/logo_300x200.png
|
||||
- lib/assets/hero-pattern-300x200.png
|
||||
# - images/a_dot_burr.jpeg
|
||||
|
@ -6,6 +6,12 @@
|
||||
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h>
|
||||
#include <local_auth_windows/local_auth_plugin.h>
|
||||
|
||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
FlutterSecureStorageWindowsPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin"));
|
||||
LocalAuthPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("LocalAuthPlugin"));
|
||||
}
|
||||
|
@ -3,6 +3,8 @@
|
||||
#
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
flutter_secure_storage_windows
|
||||
local_auth_windows
|
||||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
|