Merge branch 'ft/app'

This commit is contained in:
itsscb 2023-11-12 23:56:07 +01:00
commit 851dff40c8
49 changed files with 2340 additions and 97 deletions

View File

@ -45,7 +45,7 @@ android {
applicationId "com.example.app" 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

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 332 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

View File

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

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

@ -1,12 +1,20 @@
import 'package:app/pages/home_page.dart'; import 'package:app/model/services/auth_service.dart';
import 'package:app/model/services/storage_service.dart';
import 'package:app/pages/start_page.dart';
import 'package:app/util/colors.dart';
import 'package:flutter/material.dart'; import 'package:flutter/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();
}
}

View File

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

View File

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

View File

@ -14,6 +14,7 @@ import 'package:app/pb/rpc_get_person.pb.dart';
import 'package:app/pb/rpc_list_persons.pb.dart'; import 'package:app/pb/rpc_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;

View File

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

View File

@ -7,11 +7,12 @@ class AccountViewModel extends BaseViewModel {
AccountViewModel() { 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) {

View File

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

View File

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

View File

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

View File

@ -0,0 +1,191 @@
import 'package:app/model/services/storage_service.dart';
import 'package:app/pages/registration_page.dart';
import 'package:app/util/colors.dart';
import 'package:flutter/material.dart';
class NotificationsPage extends StatefulWidget {
const NotificationsPage({super.key});
@override
State<NotificationsPage> createState() => _NotificationsPageState();
}
class _NotificationsPageState extends State<NotificationsPage> {
final StorageService _storageService = StorageService();
bool _loading = true;
Future<void> _setNotificationSetting(bool enabled) async {
await _storageService.setNotificationSetting(enabled);
}
@override
void initState() {
_init();
super.initState();
}
void _init() async {
final accountLevel = await _storageService.accountLevel;
if (accountLevel > 2 && mounted) {
await Navigator.push(context,
MaterialPageRoute(builder: (builder) => const RegistrationPage()));
setState(() {
_loading = false;
});
} else {
setState(() {
_loading = false;
});
}
}
@override
Widget build(BuildContext context) {
return SafeArea(
child: _loading
? Center(
child: Column(
children: [
const SizedBox(
height: 150,
),
Hero(
tag: 'logo',
child: Image.asset(
'assets/JPEG.jpg',
height: 180,
),
),
CircularProgressIndicator(
color: CustomColors.primary,
),
],
),
)
: Scaffold(
resizeToAvoidBottomInset: false,
appBar: AppBar(
leading: BackButton(
color: CustomColors.primary,
onPressed: () async {
await _storageService.setAccountLevel(1);
if (mounted) {
Navigator.pop(context);
}
},
),
iconTheme: IconThemeData(
color: CustomColors.primary,
),
),
body: Padding(
padding: const EdgeInsets.fromLTRB(20, 20, 20, 16),
child: Column(
// mainAxisAlignment: MainAxisAlignment.center,
children: [
const SizedBox(
height: 80,
),
Image.asset('assets/chat_bubbles.png'),
const SizedBox(
height: 60,
),
const Text(
'Erhalte Mitteilungen',
textAlign: TextAlign.center,
style: TextStyle(
fontFamily: 'sans-serif',
fontWeight: FontWeight.bold,
letterSpacing: 2.0,
fontSize: 25,
),
),
const SizedBox(
height: 20,
),
const Text(
'Du erhältst z. B. eine Mitteilung sobald wir eine Digitale Spur gefunden haben.',
textAlign: TextAlign.center,
),
const SizedBox(
height: 20,
),
const Text(
'Du kannst die Mitteilungen jederzeit wieder deaktivieren.',
textAlign: TextAlign.center,
),
const Spacer(
flex: 2,
),
Hero(
tag: 'flow-button',
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: CustomColors.primary,
),
onPressed: () async {
await _setNotificationSetting(true);
await _storageService.setAccountLevel(3);
if (mounted) {
Navigator.push(
context,
MaterialPageRoute(
builder: (builder) => const RegistrationPage(),
// builder: (builder) => SecurityPage(),
),
);
}
},
child: const SizedBox(
height: 60,
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Expanded(
flex: 1,
child: Text(
'Mitteilungen erhalten',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 20,
),
),
),
],
),
),
),
),
const SizedBox(
height: 10,
),
TextButton(
onPressed: () async {
await _setNotificationSetting(false);
await _storageService.setAccountLevel(3);
if (mounted) {
Navigator.push(
context,
MaterialPageRoute(
builder: (builder) => const RegistrationPage(),
// builder: (builder) => SecurityPage(),
),
);
}
},
child: Text(
'Später',
style: TextStyle(color: CustomColors.primary),
),
),
const Spacer(
flex: 1,
),
],
),
),
),
);
}
}

View File

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

View File

@ -0,0 +1,215 @@
import 'package:app/model/services/storage_service.dart';
import 'package:app/pages/password_page.dart';
import 'package:app/pages/verify_email_page.dart';
import 'package:app/util/colors.dart';
import 'package:app/util/validation.dart';
import 'package:flutter/material.dart';
class RegistrationPage extends StatefulWidget {
const RegistrationPage({super.key});
@override
State<RegistrationPage> createState() => _RegistrationPageState();
}
class _RegistrationPageState extends State<RegistrationPage> {
final formKey = GlobalKey<FormState>();
final mailController = TextEditingController();
bool _loading = true;
final StorageService _storageService = StorageService();
@override
void initState() {
_init();
super.initState();
}
void _init() async {
final accountLevel = await _storageService.accountLevel;
if (accountLevel > 3 && mounted) {
await Navigator.push(context,
MaterialPageRoute(builder: (builder) => const VerifyEmailPage()));
setState(() {
_loading = false;
});
} else {
setState(() {
_loading = false;
});
}
}
@override
void dispose() {
mailController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return SafeArea(
child: _loading
? Center(
child: Column(
children: [
const SizedBox(
height: 150,
),
Hero(
tag: 'logo',
child: Image.asset(
'assets/JPEG.jpg',
height: 180,
),
),
CircularProgressIndicator(
color: CustomColors.primary,
),
],
),
)
: Scaffold(
appBar: AppBar(
leading: BackButton(
color: CustomColors.primary,
onPressed: () async {
await _storageService.setAccountLevel(2);
if (mounted) {
Navigator.pop(context);
}
},
),
iconTheme: IconThemeData(
color: CustomColors.primary,
),
),
body: Padding(
padding: const EdgeInsets.fromLTRB(20, 20, 20, 16),
child: Column(
// mainAxisAlignment: MainAxisAlignment.center,
children: [
const SizedBox(
height: 20,
),
const Text(
'Jetzt Registrieren',
textAlign: TextAlign.center,
style: TextStyle(
fontFamily: 'sans-serif',
fontWeight: FontWeight.bold,
letterSpacing: 2.0,
fontSize: 25,
),
),
const SizedBox(
height: 20,
),
const Text(
'Gib deine E-Mail Adresse ein.',
// textAlign: TextAlign.center,
),
const SizedBox(
height: 20,
),
Form(
key: formKey,
child: Column(
children: [
TextFormField(
autocorrect: false,
autofocus: true,
controller: mailController,
keyboardType: TextInputType.emailAddress,
decoration: const InputDecoration(
helperText: 'test',
label: Text('E-Mail Adresse'),
filled: true,
),
validator: (value) {
if (value == null || !value.isValidEmail) {
return 'Bitte eine valide E-Mail Adresse angeben';
} else {
return null;
}
},
),
],
),
),
const SizedBox(
height: 20,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
TextButton(
onPressed: () {
if (formKey.currentState!.validate()) {
Navigator.push(
context,
MaterialPageRoute(
builder: (builder) => PasswordPage(
email: mailController.text,
register: false,
),
),
);
}
},
child: Text(
'Stattdessen anmelden',
// textAlign: TextAlign.center,
style: TextStyle(
color: CustomColors.primary,
),
),
),
Hero(
tag: 'flow-button',
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: CustomColors.primary,
),
onPressed: () {
if (formKey.currentState!.validate()) {
Navigator.push(
context,
MaterialPageRoute(
builder: (builder) => PasswordPage(
email: mailController.text,
register: true,
),
),
);
}
},
child: const SizedBox(
height: 50,
width: 100,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Weiter',
style: TextStyle(
fontSize: 20,
),
),
],
),
),
),
),
],
),
const Spacer(
flex: 2,
),
],
),
),
),
);
}
}

View File

@ -0,0 +1,187 @@
import 'package:app/model/services/auth_service.dart';
import 'package:app/model/services/storage_service.dart';
import 'package:app/pages/notifications_page.dart';
import 'package:app/util/colors.dart';
import 'package:flutter/material.dart';
class SecurityPage extends StatefulWidget {
const SecurityPage({super.key});
@override
State<SecurityPage> createState() => _SecurityPageState();
}
class _SecurityPageState extends State<SecurityPage> {
final StorageService _storageService = StorageService();
bool _loading = true;
@override
void initState() {
_init();
super.initState();
}
void _init() async {
final accountLevel = await _storageService.accountLevel;
if (accountLevel > 1 && mounted) {
await Navigator.push(context,
MaterialPageRoute(builder: (builder) => const NotificationsPage()));
setState(() {
_loading = false;
});
} else {
setState(() {
_loading = false;
});
}
}
@override
Widget build(BuildContext context) {
return SafeArea(
child: _loading
? Center(
child: Column(
children: [
const SizedBox(
height: 150,
),
Hero(
tag: 'logo',
child: Image.asset(
'assets/JPEG.jpg',
height: 180,
),
),
CircularProgressIndicator(
color: CustomColors.primary,
),
],
),
)
: Scaffold(
appBar: AppBar(
leading: BackButton(
color: CustomColors.primary,
onPressed: () async {
await _storageService.setAccountLevel(0);
if (mounted) {
Navigator.pop(context);
}
},
),
iconTheme: IconThemeData(color: CustomColors.primary),
),
body: Padding(
padding: const EdgeInsets.fromLTRB(16, 20, 16, 16),
child: Center(
child: Column(
children: [
const Spacer(),
const Hero(
tag: 'flow-icon',
child: Icon(
Icons.fingerprint,
color: Colors.white,
size: 200,
),
),
const Spacer(),
const Text(
'Deine Sicherheit kommt an erster Stelle',
textAlign: TextAlign.center,
style: TextStyle(
fontFamily: 'sans-serif',
fontSize: 25,
fontWeight: FontWeight.bold),
),
const SizedBox(
height: 30,
),
const Text(
'Schütze dein Konto mit der biometrischen Erkennung deines Geräts oder lege einen Code fest.',
textAlign: TextAlign.center,
style: TextStyle(
fontFamily: 'sans-serif',
fontSize: 18,
fontWeight: FontWeight.bold),
),
const Spacer(),
Hero(
tag: 'flow-button',
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: CustomColors.primary,
),
onPressed: () async {
bool isAuthenticated =
await AuthService.authenticateWithBiometrics();
if (isAuthenticated) {
await _storageService.setAccountLevel(2);
// ignore: use_build_context_synchronously
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
const NotificationsPage()),
);
}
},
child: const SizedBox(
height: 60,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'App absichern',
style: TextStyle(
fontSize: 20,
),
),
],
),
),
),
),
// const SizedBox(
// height: 10,
// ),
// ElevatedButton(
// style: ElevatedButton.styleFrom(
// backgroundColor: CustomColors.secondary,
// ),
// onPressed: () {
// Navigator.push(
// context,
// MaterialPageRoute(
// builder: (builder) => SecurityPage(),
// ),
// );
// },
// child: const SizedBox(
// height: 60,
// child: Row(
// mainAxisAlignment: MainAxisAlignment.center,
// children: [
// Text(
// 'Eigenen Code festlegen',
// style: TextStyle(
// color: Colors.white,
// fontSize: 22,
// ),
// ),
// ],
// ),
// ),
// ),
const Spacer(
flex: 2,
),
],
),
),
),
),
);
}
}

View File

@ -0,0 +1,166 @@
import 'package:app/model/services/storage_service.dart';
import 'package:app/pages/agb_page.dart';
import 'package:app/pages/security_page.dart';
import 'package:app/util/colors.dart';
import 'package:flutter/material.dart';
class StartPage extends StatefulWidget {
const StartPage({super.key});
@override
State<StartPage> createState() => _StartPageState();
}
class _StartPageState extends State<StartPage> {
final StorageService _storageService = StorageService();
bool _loading = true;
@override
void initState() {
_init();
super.initState();
}
void _init() async {
int accountLevel = await _storageService.accountLevel;
if (accountLevel > 0 && mounted) {
await Navigator.push(context,
MaterialPageRoute(builder: (builder) => const SecurityPage()));
setState(() {
_loading = false;
});
} else {
setState(() {
_loading = false;
});
}
}
@override
Widget build(BuildContext context) {
return SafeArea(
child: _loading
? Center(
child: Column(
children: [
const SizedBox(
height: 150,
),
Hero(
tag: 'logo',
child: Image.asset(
'assets/JPEG.jpg',
height: 180,
),
),
CircularProgressIndicator(
color: CustomColors.primary,
),
],
),
)
: Scaffold(
appBar: AppBar(
iconTheme: IconThemeData(color: CustomColors.primary),
),
body: Padding(
padding: const EdgeInsets.fromLTRB(16, 20, 16, 16),
child: Column(
children: [
Hero(
tag: 'flow-icon',
child: Image.asset(
'assets/JPEG.jpg',
height: 180,
),
),
const SizedBox(
height: 30,
),
const Text(
'Hallo. Digitale Spuren\nentfernen\nper Knopfdruck.',
textAlign: TextAlign.center,
style: TextStyle(
fontFamily: 'sans-serif',
fontWeight: FontWeight.bold,
letterSpacing: 2.0,
fontSize: 25,
),
),
const SizedBox(
height: 20,
),
const Text(
'Mit uns finden Sie Ihre Digitalen Spuren und können diese entfernen.',
textAlign: TextAlign.center,
style: TextStyle(
fontFamily: 'sans-serif',
fontSize: 18,
),
),
const Spacer(
flex: 1,
),
Hero(
tag: 'flow-button',
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: CustomColors.primary,
),
onPressed: () async {
await _storageService.setAccountLevel(1);
if (mounted) {
Navigator.push(
context,
MaterialPageRoute(
builder: (builder) => const SecurityPage(),
// builder: (builder) => SecurityPage(),
),
);
}
},
child: const SizedBox(
height: 60,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Weiter',
style: TextStyle(
fontSize: 20,
),
),
],
),
),
),
),
const Spacer(
flex: 1,
),
const Text(
'Mit der weiteren Nutzung stimmst du den folgenden Bedingungen zu:',
textAlign: TextAlign.center,
style: TextStyle(
fontFamily: 'sans-serif',
fontSize: 16,
),
),
TextButton(
onPressed: () {
showDialog(
context: context,
builder: (builder) => AgbPage());
},
child: Text(
'AGB - Datenschutzerklärung',
textAlign: TextAlign.center,
style: TextStyle(color: CustomColors.primary),
))
],
),
),
),
);
}
}

View File

@ -0,0 +1,156 @@
import 'package:app/model/services/storage_service.dart';
import 'package:app/util/colors.dart';
import 'package:flutter/material.dart';
class VerifyEmailPage extends StatefulWidget {
const VerifyEmailPage({super.key});
@override
State<VerifyEmailPage> createState() => _VerifyEmailPageState();
}
class _VerifyEmailPageState extends State<VerifyEmailPage> {
final StorageService _storageService = StorageService();
bool _loading = true;
@override
void initState() {
super.initState();
setState(() {
_loading = false;
});
}
@override
Widget build(BuildContext context) {
return SafeArea(
child: _loading
? Center(
child: Column(
children: [
const SizedBox(
height: 150,
),
Hero(
tag: 'logo',
child: Image.asset(
'assets/JPEG.jpg',
height: 180,
),
),
CircularProgressIndicator(
color: CustomColors.primary,
),
],
),
)
: Scaffold(
appBar: AppBar(
leading: BackButton(
color: CustomColors.primary,
onPressed: () async {
await _storageService.setAccountLevel(3);
if (mounted) {
Navigator.pop(context);
}
},
),
iconTheme: IconThemeData(
color: CustomColors.primary,
),
),
body: Padding(
padding: const EdgeInsets.fromLTRB(20, 20, 20, 16),
child: Column(
// mainAxisAlignment: MainAxisAlignment.center,
children: [
const SizedBox(
height: 80,
),
const Text(
'Verifizieren',
textAlign: TextAlign.center,
style: TextStyle(
fontFamily: 'sans-serif',
fontWeight: FontWeight.bold,
letterSpacing: 2.0,
fontSize: 25,
),
),
const SizedBox(
height: 50,
),
const Text(
'Wir haben dir eine E-Mail geschickt.',
textAlign: TextAlign.center,
// textAlign: TextAlign.center,
),
const SizedBox(
height: 20,
),
const Text(
'Bitte verifiziere deine E-Mail Adresse, dann geht es weiter.',
textAlign: TextAlign.center,
// textAlign: TextAlign.center,
),
const SizedBox(
height: 80,
),
Hero(
tag: 'flow-button',
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: CustomColors.primary,
),
onPressed: () {},
child: const SizedBox(
height: 50,
width: 100,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Weiter',
style: TextStyle(
fontSize: 20,
),
),
],
),
),
),
),
const SizedBox(
height: 60,
),
const Text(
'Noch keine E-Mail erhalten?',
// textAlign: TextAlign.center,
),
const Text(
'Schon im Spam-Ordner nachgeschaut?',
// textAlign: TextAlign.center,
),
const SizedBox(
height: 20,
),
TextButton(
onPressed: () {},
child: Text(
'Erneut senden',
// textAlign: TextAlign.center,
style: TextStyle(
color: CustomColors.primary,
),
),
),
const Spacer(
flex: 2,
),
],
),
),
),
);
}
}

View File

@ -1,7 +1,8 @@
import 'package:app/model/services/backend_service.dart'; import 'package:app/model/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);

View File

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

View File

@ -1,8 +1,10 @@
import 'package:app/model/apis/api_response.dart'; import 'package:app/model/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,

View File

@ -1,9 +1,5 @@
import 'package:app/gapi/client.dart';
import 'package:app/pages_old/start_page.dart';
import 'package:app/pb/account_info.pb.dart'; import 'package:app/pb/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';

View File

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

View File

@ -1,11 +1,8 @@
import 'package:app/gapi/client.dart';
import 'package:app/pages_old/start_page.dart';
import 'package:app/widgets/background.dart'; import 'package:app/widgets/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({

View File

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

View File

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

View File

View File

@ -2,7 +2,7 @@ final emailRegExp = RegExp(r"^[a-zA-Z0-9.]+@[a-zA-Z0-9]+\.[a-zA-Z]+");
final nameRegExp = 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(' ');
}
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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