Merge pull request #96 from itsscb:rf/app-from-scratch-with-backend-service

Rf/app-from-scratch-with-backend-service
This commit is contained in:
itsscb 2023-11-07 00:22:49 +01:00 committed by GitHub
commit bc80e10775
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 2586 additions and 1087 deletions

View File

@ -13,9 +13,7 @@ class Session {
this.refreshToken,
this.refreshTokenExpiresAt,
this.accountId,
}) {
_init();
}
});
String? sessionId;
String? accessToken;
@ -40,6 +38,12 @@ class Session {
);
Session s = Session();
s._database = db;
final sessions = await s.getSessions();
if (sessions.isNotEmpty) {
final session = sessions[0];
session._database = db;
return session;
}
return s;
}
@ -90,6 +94,41 @@ class Session {
return 'Session{accountId: $accountId, sessionId: $sessionId, accessToken: $accessToken, accessTokenExpiresAt: ${accessTokenExpiresAt.toString()}, refreshToken: $refreshToken, refreshTokenExpiresAt: ${refreshTokenExpiresAt.toString()}}';
}
static newSession(Session session) async {
final db = await openDatabase(
join(await getDatabasesPath(), 'df_database.db'),
onCreate: (db, version) {
return db.execute(
'CREATE TABLE sessions(accountId INTEGER PRIMARY KEY, sessionId TEXT, accessToken TEXT, accessTokenExpiresAt TEXT, refreshToken TEXT, refreshTokenExpiresAt TEXT)',
);
},
version: 1,
);
await db.insert(
'sessions',
session.toMap(),
conflictAlgorithm: ConflictAlgorithm.replace,
);
}
static Future<Session> updateToken(Session s) async {
final db = await openDatabase(
join(await getDatabasesPath(), 'df_database.db'),
onCreate: (db, version) {
return db.execute(
'CREATE TABLE sessions(accountId INTEGER PRIMARY KEY, sessionId TEXT, accessToken TEXT, accessTokenExpiresAt TEXT, refreshToken TEXT, refreshTokenExpiresAt TEXT)',
);
},
version: 1,
);
await db.update(
'sessions',
s.toMap(),
);
return s; //await getSession(s.accountId!);
}
Future<void> insertSession(Session session) async {
// print('INSERTING SESSION: ${session.sessionId}');
final db = _database;
@ -101,6 +140,17 @@ class Session {
// print('INSERT RESULT: $result');
}
Future<Session> updateSession(Session session) async {
sessionId = session.sessionId;
accessToken = session.accessToken;
accessTokenExpiresAt = session.accessTokenExpiresAt;
refreshToken = session.refreshToken;
refreshTokenExpiresAt = session.refreshTokenExpiresAt;
final db = _database;
await db.update('sessions', session.toMap());
return session;
}
Future<void> removeSession(String sessionId) async {
final db = _database;
await db.delete('sessions', where: 'sessionId = ?', whereArgs: [sessionId]);
@ -116,12 +166,14 @@ class Session {
final db = await database;
final List<Map<String, Object?>> maps = await db.query('sessions');
// print(maps);
final List<Session> sessions = List.generate(
maps.length,
(i) {
// print('GOT MAP: ${maps[i]}');
if (maps[i]['sessionId'] == null) {
return Session();
}
return Session(
sessionId: maps[i]['sessionId'] as String,
accessToken: maps[i]['accessToken'] as String,

View File

@ -1,6 +1,4 @@
import 'package:app/gapi/client.dart';
import 'package:app/pages/start_page.dart';
import 'package:app/widgets/background.dart';
import 'package:app/pages/home_page.dart';
import 'package:flutter/material.dart';
void main() async {
@ -43,10 +41,8 @@ void main() async {
backgroundColor: Colors.black,
foregroundColor: Colors.white,
)),
home: Background(
child: StartPage(
client: await GClient.client,
),
home: HomePage(
loggedOut: false,
),
),
);

View File

@ -0,0 +1,17 @@
class ApiResponse<T> {
Status status;
T? data;
String? message;
ApiResponse.initial(this.message) : status = Status.INITIAL;
ApiResponse.loading(this.message) : status = Status.LOADING;
ApiResponse.completed(this.data) : status = Status.COMPLETED;
ApiResponse.error(this.message) : status = Status.ERROR;
@override
String toString() {
return "Status : $status \n Message: $message \n Data : $data";
}
}
enum Status { INITIAL, LOADING, COMPLETED, ERROR }

View File

@ -0,0 +1,32 @@
class AppException implements Exception {
final _message;
final _prefix;
AppException([this._message, this._prefix]);
String toString() {
return "$_prefix$_message";
}
}
class FetchDataException extends AppException {
FetchDataException([String? message])
: super(message, 'Fehler bei der Kommunikation: ');
}
class BadRequestException extends AppException {
BadRequestException([message]) : super(message, 'Ungültige Anfrage; ');
}
class UnauthorizedException extends AppException {
UnauthorizedException([message])
: super(message, 'Nicht authorisierte Anfrage; ');
}
class InvalidInputException extends AppException {
InvalidInputException([message]) : super(message, 'Ungültige Eingabe; ');
}
class InternalException extends AppException {
InternalException([message]) : super(message, 'Interner Fehler; ');
}

View File

@ -0,0 +1,345 @@
import 'dart:io';
import 'package:app/model/apis/app_exception.dart';
import 'package:app/pb/account.pb.dart';
import 'package:app/pb/account_info.pb.dart';
import 'package:app/pb/google/protobuf/timestamp.pb.dart';
import 'package:app/pb/person.pb.dart';
import 'package:app/data/database.dart';
import 'package:app/pb/rpc_create_account.pb.dart';
import 'package:app/pb/rpc_create_person.pb.dart';
import 'package:app/pb/rpc_get_account.pb.dart';
import 'package:app/pb/rpc_get_account_info.pb.dart';
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/service_df.pbgrpc.dart';
import 'package:fixnum/fixnum.dart';
import 'package:grpc/grpc.dart';
class BackendService {
BackendService() {
_init();
}
final String baseUrl = '10.0.0.2';
final String port = '9090';
late Session _session;
final dfClient _client = dfClient(
ClientChannel('10.0.2.2',
port: 9090,
options: const ChannelOptions(
credentials: ChannelCredentials.insecure(),
)),
options: CallOptions(
timeout: const Duration(seconds: 5),
),
);
static get client => dfClient(
ClientChannel('10.0.2.2',
port: 9090,
options: const ChannelOptions(
credentials: ChannelCredentials.insecure(),
)),
options: CallOptions(
timeout: const Duration(seconds: 5),
),
);
void _init() {
Session.session.then((value) => _session = value);
}
Future<Session?> _isLoggedIn() async {
Session session = await Session.session;
if (session.accessToken == null ||
session.refreshToken == null ||
session.accountId == null) {
return null;
}
if (session.accessTokenExpiresAt == null) {
return null;
}
if (session.refreshTokenExpiresAt == null) {
return null;
}
if (session.refreshTokenExpiresAt!.toDateTime().isBefore(DateTime.now())) {
return null;
} else {
if (session.refreshToken != null &&
session.accessTokenExpiresAt!.toDateTime().isBefore(DateTime.now())) {
session = await refreshToken(session);
}
}
return session;
}
static Future<bool> get isLoggedIn async {
Session session = await Session.session;
if (session.accessToken == null ||
session.refreshToken == null ||
session.accountId == null) {
return false;
}
if (session.accessTokenExpiresAt == null) {
await logout();
return false;
}
if (session.refreshTokenExpiresAt == null) {
await logout();
return false;
}
if (session.refreshTokenExpiresAt!.toDateTime().isBefore(DateTime.now())) {
await logout();
return false;
}
if (session.accessTokenExpiresAt!.toDateTime().isBefore(DateTime.now())) {
Session s = await BackendService.refreshToken(session);
if (s == session) {
return false;
}
}
return true;
}
static Future<bool> createAccount(
{required String email, required String password}) async {
try {
await BackendService.client.createAccount(CreateAccountRequest(
email: email,
password: password,
));
return await login(email: email, password: password);
} on SocketException {
throw FetchDataException('Keine Internet Verbindung');
} on GrpcError catch (err) {
throw FetchDataException(err.message);
} catch (err) {
throw InternalException(err.toString());
}
}
Future<Account> getAccount() async {
Session? session = await _isLoggedIn();
if (session == null) {
throw UnauthorizedException('Sitzung ist abgelaufen');
}
try {
final GetAccountResponse response = await _client.getAccount(
GetAccountRequest(id: session.accountId),
options: CallOptions(
metadata: {'Authorization': 'Bearer ${session.accessToken}'}));
return response.account;
} on SocketException {
throw FetchDataException('Keine Internet Verbindung');
} on GrpcError catch (err) {
if (err.code == 16) {
throw UnauthorizedException(err.message);
}
throw FetchDataException(err.message);
} catch (err) {
throw InternalException(err.toString());
}
}
Future<AccountInfo> getAccountInfo() 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!.toDateTime().isBefore(DateTime.now())) {
throw UnauthorizedException('Sitzung ist abgelaufen');
}
}
try {
final GetAccountInfoResponse response = await _client
.getAccountInfo(GetAccountInfoRequest(accountId: _session.accountId));
return response.accountInfo;
} on SocketException {
throw FetchDataException('Keine Internet Verbindung');
} on GrpcError catch (err) {
if (err.code == 16) {
await refreshToken(session);
return getAccountInfo();
}
throw FetchDataException(err.message);
} catch (err) {
throw InternalException(err.toString());
}
}
Future<Person> getPerson(Int64 personId) 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 GetPersonResponse response =
await _client.getPerson(GetPersonRequest(id: personId));
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 DateTime 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 CreatePersonResponse response = await _client.createPerson(
CreatePersonRequest(
accountId: session.accountId,
lastname: lastname,
firstname: firstname,
street: street,
zip: zip,
country: country,
birthday: Timestamp.fromDateTime(birthday),
),
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<List<Person>> listPersons() 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 ListPersonsResponse response = await _client.listPersons(
ListPersonsRequest(
accountId: session.accountId,
),
options: CallOptions(
metadata: {'Authorization': 'Bearer ${session.accessToken}'}));
return response.persons;
} on SocketException {
throw FetchDataException('Keine Internet Verbindung');
} on GrpcError catch (err) {
throw FetchDataException(err.message);
} catch (err) {
throw InternalException(err.toString());
}
}
// Future<List<Person>> listPersons() async {
// if (_session.accessToken == null) {
// refreshToken();
// }
// try {
// ListPersonsResponse response =
// await _client.listPersons(ListPersonsRequest(accountId: _session.accountId));
// return response.persons;
// } on SocketException {
// throw FetchDataException('Keine Internet Verbindung');
// } on GrpcError catch (err) {
// throw FetchDataException(err.message);
// } catch (err) {
// throw InternalException(err.toString());
// }
// }
static Future<bool> login(
{required String email, required String password}) async {
try {
final LoginResponse response = await BackendService.client.login(
LoginRequest(
email: email,
password: password,
),
);
Session s = Session(
accessToken: response.accessToken,
sessionId: response.sessionId,
accessTokenExpiresAt: response.accessTokenExpiresAt,
refreshToken: response.refreshToken,
refreshTokenExpiresAt: response.refreshTokenExpiresAt,
accountId: response.accountId,
);
await Session.newSession(s);
return response.accessToken != '';
} on SocketException {
throw FetchDataException('Keine Internet Verbindung');
} on GrpcError catch (err) {
throw FetchDataException(err.message);
} catch (err) {
throw InternalException(err.toString());
}
}
static Future<Session> refreshToken(Session session) async {
try {
final RefreshTokenResponse response = await BackendService.client
.refreshToken(
RefreshTokenRequest(refreshToken: session.refreshToken));
session.accessToken = response.accessToken;
session.accessTokenExpiresAt = response.accessTokenExpiresAt;
session = await Session.updateToken(session);
return session;
} on SocketException {
throw FetchDataException('Keine Internet Verbindung');
} on GrpcError catch (err) {
throw FetchDataException(err.message);
} catch (err) {
throw InternalException(err.toString());
}
}
static Future<void> logout() async {
Session session = await Session.session;
session.reset();
}
}

View File

@ -0,0 +1,32 @@
import 'package:app/model/apis/api_response.dart';
import 'package:app/model/services/backend_service.dart';
import 'package:app/model/view_model/base_vm.dart';
import 'package:app/pb/account.pb.dart';
class AccountViewModel extends BaseViewModel {
AccountViewModel() {
_init();
}
ApiResponse _apiResponse = ApiResponse.initial('Keine Daten');
final BackendService _service = BackendService();
Account? _account;
ApiResponse get response {
return _apiResponse;
}
Account? get account {
return _account;
}
void _init() async {
super.init();
// try {
// _apiResponse = ApiResponse.completed(await _service.getAccount());
// } catch (e) {
// _apiResponse = ApiResponse.error(e.toString());
// }
// notifyListeners();
}
}

View File

@ -0,0 +1,254 @@
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/util/colors.dart';
import 'package:flutter/material.dart';
class BaseViewModel with ChangeNotifier {
BaseViewModel() {
init();
}
ApiResponse _apiResponse = ApiResponse.initial('Keine Daten');
final BackendService _service = BackendService();
ApiResponse get response {
return _apiResponse;
}
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);
final navigator = Navigator.of(context);
bool loggedIn = false;
try {
loggedIn = await BackendService.isLoggedIn;
} catch (err) {
if (err.toString().contains('session is blocked')) {
_apiResponse = ApiResponse.error('Sitzung ist abgelaufen');
navigator.pushAndRemoveUntil(
MaterialPageRoute(
builder: (builder) => HomePage(
loggedOut: true,
)),
(route) => false);
messenger.showSnackBar(SnackBar(
backgroundColor: CustomColors.error,
content: const Text(
'Sitzung ist abgelaufen',
style: TextStyle(color: Colors.white),
),
// action: SnackBarAction(
// label: 'Details',
// onPressed: () {
// if (context.mounted) {
// showDialog(
// context: context,
// builder: (context) => AlertDialog(
// backgroundColor: Colors.black,
// icon: Icon(
// Icons.error,
// color: CustomColors.error,
// ),
// content: Text(
// err.toString(),
// textAlign: TextAlign.center,
// ),
// ));
// }
// },
// ),
));
}
}
return loggedIn;
}
Future<void> getAccount(BuildContext context) async {
_apiResponse = ApiResponse.loading('Lade Daten');
notifyListeners();
final messenger = ScaffoldMessenger.of(context);
try {
_apiResponse = ApiResponse.completed(await _service.getAccount());
} catch (e) {
if (e.toString().contains('session is blocked')) {
_apiResponse = ApiResponse.error('Sitzung ist abgelaufen');
messenger.showSnackBar(SnackBar(
backgroundColor: CustomColors.error,
content: const Text(
'Sitzung ist abgelaufen',
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(
e.toString(),
textAlign: TextAlign.center,
),
));
},
),
));
}
messenger.showSnackBar(SnackBar(
backgroundColor: CustomColors.error,
content: const Text(
'Sitzung ist abgelaufen',
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(
e.toString(),
textAlign: TextAlign.center,
),
));
},
),
));
_apiResponse = ApiResponse.error(e.toString());
}
notifyListeners();
}
Future<void> logout() async {
_apiResponse = ApiResponse.loading('Logge aus');
notifyListeners();
try {
await BackendService.logout();
_apiResponse = ApiResponse.completed(true);
} catch (e) {
_apiResponse = ApiResponse.error(e.toString());
}
print(_apiResponse.message);
notifyListeners();
}
Future<bool> login(BuildContext context,
{required String email, required String password}) async {
bool resp = false;
_apiResponse = ApiResponse.loading('Logge ein');
notifyListeners();
final messenger = ScaffoldMessenger.of(context);
try {
resp = await BackendService.login(email: email, password: password);
_apiResponse = ApiResponse.completed(resp);
messenger.showSnackBar(SnackBar(
backgroundColor: CustomColors.success,
content: const Text(
'Erfolgreich eingeloggt',
style: TextStyle(color: Colors.white),
),
));
} catch (e) {
messenger.showSnackBar(SnackBar(
backgroundColor: CustomColors.error,
content: const Text(
'Login fehlgeschlagen',
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(
e.toString(),
textAlign: TextAlign.center,
),
));
},
),
));
_apiResponse = ApiResponse.error(e.toString());
}
print(_apiResponse.message);
notifyListeners();
return resp;
}
Future<bool> createAccount(BuildContext context,
{required String email, required String password}) async {
bool resp = false;
final messenger = ScaffoldMessenger.of(context);
_apiResponse = ApiResponse.loading('Logge ein');
notifyListeners();
try {
resp =
await BackendService.createAccount(email: email, password: password);
messenger.showSnackBar(SnackBar(
backgroundColor: CustomColors.success,
content: const Text(
'Account angelegt',
style: TextStyle(color: Colors.white),
),
));
_apiResponse = ApiResponse.completed(resp);
} catch (e) {
messenger.showSnackBar(SnackBar(
backgroundColor: CustomColors.error,
content: const Text(
'Account anlegen fehlgeschlagen',
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(
e.toString(),
textAlign: TextAlign.center,
),
));
},
),
));
_apiResponse = ApiResponse.error(e.toString());
}
print(_apiResponse.message);
notifyListeners();
return resp;
}
}

View File

@ -0,0 +1,57 @@
import 'package:app/model/apis/api_response.dart';
import 'package:app/model/services/backend_service.dart';
import 'package:app/pb/person.pb.dart';
import 'package:flutter/material.dart';
class PersonsViewModel with ChangeNotifier {
PersonsViewModel() {
listPersons();
}
ApiResponse _apiResponse = ApiResponse.initial('Keine Daten');
final BackendService _service = BackendService();
ApiResponse get response {
return _apiResponse;
}
Future<List<Person>> listPersons() async {
List<Person> persons = [];
_apiResponse = ApiResponse.loading('Lade Daten');
try {
persons = await _service.listPersons();
_apiResponse = ApiResponse.completed(persons);
} catch (e) {
_apiResponse = ApiResponse.error(e.toString());
}
notifyListeners();
return persons;
}
Future<Person> createPerson(BuildContext context,
{required String firstname,
required String lastname,
required String street,
required String zip,
required String city,
required String country,
required DateTime birthday}) async {
Person person = Person();
_apiResponse = ApiResponse.loading('Erstelle Person');
try {
person = await _service.createPerson(
firstname: firstname,
lastname: lastname,
street: street,
zip: zip,
city: city,
country: country,
birthday: birthday);
_apiResponse = ApiResponse.completed(person);
} catch (err) {
_apiResponse = ApiResponse.error(err.toString());
}
notifyListeners();
return person;
}
}

View File

@ -1,302 +0,0 @@
import 'package:app/gapi/client.dart';
import 'package:app/pages/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';
class DashboardPage extends StatefulWidget {
const DashboardPage({
super.key,
required this.client,
});
final GClient client;
@override
State<DashboardPage> createState() => _DashboardPageState();
}
class _DashboardPageState extends State<DashboardPage> {
bool _loading = false;
late AccountInfo accountInfo;
void _setLoading(bool loading) {
setState(() {
_loading = loading;
});
}
@override
void initState() {
super.initState();
_setLoading(true);
widget.client.getAccountInfo(
GetAccountInfoRequest(
accountId: widget.client.session.accountId,
),
onError: ({String? msg}) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: const Text('AccountInfo konnte nicht geladen werden'),
action: msg != null
? SnackBarAction(
label: 'Details',
textColor: Colors.grey,
onPressed: () {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
content: Text(
msg,
textAlign: TextAlign.center,
style: const TextStyle(color: Colors.black),
),
icon: const Icon(
Icons.warning,
color: Colors.red,
),
);
},
);
})
: null,
),
);
},
).then((value) {
accountInfo = value.accountInfo;
_setLoading(false);
});
}
@override
Widget build(BuildContext context) {
return Background(
child: Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
flexibleSpace: Image.asset(
'lib/assets/logo_300x200.png',
height: 80,
),
),
drawer: Builder(builder: (context) {
return SideDrawer(
children: [
const Spacer(),
TextButton(
onPressed: () {
Scaffold.of(context).closeDrawer();
},
child: const Row(
children: [
Text(
'About',
style: TextStyle(fontSize: 20),
),
Spacer(),
Icon(
Icons.question_answer,
color: Colors.white,
),
],
),
),
TextButton(
onPressed: () {
Scaffold.of(context).closeDrawer();
},
child: const Row(
children: [
Text(
'Datenschutz',
style: TextStyle(fontSize: 20),
),
Spacer(),
Icon(
Icons.privacy_tip,
color: Colors.white,
),
],
),
),
TextButton(
onPressed: () {
Scaffold.of(context).closeDrawer();
},
child: const Row(
children: [
Text(
'Impressum',
style: TextStyle(fontSize: 20),
),
Spacer(),
Icon(
Icons.apartment,
color: Colors.white,
),
],
),
),
if (widget.client.session.accessToken != null)
TextButton(
onPressed: () {
widget.client.session.accessToken = null;
widget.client.session
.removeSession(widget.client.session.sessionId!);
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(
builder: (context) =>
StartPage(client: widget.client),
),
(route) => false);
},
child: const Row(
children: [
Text(
'Log out',
style: TextStyle(fontSize: 20),
),
Spacer(),
Icon(
Icons.logout,
color: Colors.white,
),
],
),
),
const SizedBox(
height: 250,
)
],
);
}),
bottomNavigationBar: Builder(
builder: (context) {
return BottomBar(
children: widget.client.session.accessToken != null
? [
BottomNavigationBarItem(
backgroundColor: Colors.white,
label: 'Personen',
icon: Column(
children: [
IconButton(
onPressed: () =>
Scaffold.of(context).openDrawer(),
icon: const Icon(
Icons.group,
color: Colors.white,
),
),
const Text(
'Personen',
style: TextStyle(
color: Colors.white,
fontSize: 16,
),
)
],
),
),
BottomNavigationBarItem(
backgroundColor: Colors.white,
label: 'Home',
icon: Column(
children: [
IconButton(
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => StartPage(
client: widget.client,
),
),
);
},
icon: const Icon(
Icons.home,
color: Colors.white,
),
),
const Text(
'Home',
style: TextStyle(
color: Colors.white,
fontSize: 16,
),
)
],
),
),
BottomNavigationBarItem(
backgroundColor: Colors.white,
label: 'Menu',
icon: IconButton(
onPressed: () {
Scaffold.of(context).openDrawer();
},
icon: const Icon(
Icons.menu,
color: Colors.white,
),
),
)
]
: [
BottomNavigationBarItem(
label: 'back',
backgroundColor: Colors.white,
icon: IconButton(
onPressed: () {},
icon: const Icon(
Icons.arrow_back,
color: Colors.white,
),
),
),
BottomNavigationBarItem(
backgroundColor: Colors.white,
label: 'Menu',
icon: IconButton(
onPressed: () => Scaffold.of(context).openDrawer(),
icon: const Icon(
Icons.menu,
color: Colors.white,
),
),
),
],
);
},
),
body: !_loading
? Background(
child: Center(
child: Column(
children: [
const SizedBox(
height: 48,
),
Text(
'Willkommen ${accountInfo.firstname} ${accountInfo.lastname}!',
style: const TextStyle(
fontSize: 24,
),
),
],
),
),
)
: const LoadingWidget(),
),
);
}
}

View File

@ -0,0 +1,218 @@
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/widgets/background.dart';
import 'package:app/widgets/bottom_navigation.dart';
import 'package:app/widgets/bottom_navigation_item.dart';
import 'package:app/widgets/drawer.dart';
import 'package:app/widgets/side_drawer_item.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
// ignore: must_be_immutable
class HomePage extends StatefulWidget {
HomePage({super.key, required this.loggedOut});
bool loggedOut;
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
@override
void initState() {
super.initState();
_init();
}
AccountViewModel vm = AccountViewModel();
void _init() async {
// _setLoading(true);
// _setLoading(widget.loggedOut);
// _loading = widget.loggedOut;
// _loggedin = await BackendService.isLoggedIn;
// if (!_loggedin) {
// await BackendService.logout();
// final navigator = Navigator.of(context);
// navigator.pushAndRemoveUntil(
// MaterialPageRoute(
// builder: (builder) => HomePage(
// loggedOut: true,
// )),
// (route) => false);
// }
_setLoading(false);
}
_isLoggedIn(BuildContext context) async {
bool logged = await vm.isLoggedIn(context);
_loggedin = logged;
}
void _setLoading(bool loading) {
setState(() {
_loading = loading;
});
}
bool _loading = true;
bool _loggedin = false;
@override
Widget build(BuildContext context) {
return Background(
child: Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
// flexibleSpace: Image.asset(
// 'lib/assets/logo_300x200.png',
// // height: 400,
// ),
),
drawer: SideDrawer(
children: [
const Spacer(
flex: 3,
),
SideDrawerItem(
onPressed: () {},
icon: Icons.question_answer,
color: Colors.white,
label: 'About',
),
SideDrawerItem(
onPressed: () {},
icon: Icons.privacy_tip,
color: Colors.white,
label: 'Datenschutz',
),
SideDrawerItem(
onPressed: () {},
icon: Icons.apartment,
color: Colors.white,
label: 'Impressum',
),
const Spacer(
flex: 1,
),
if (_loggedin)
SideDrawerItem(
onPressed: () async {
setState(() {
_loading = true;
});
await BackendService.logout();
// ignore: use_build_context_synchronously
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(
builder: (builder) => HomePage(
loggedOut: true,
)),
(route) => false);
setState(() {
_loggedin = false;
_loading = false;
});
},
icon: Icons.logout,
color: Colors.white,
label: 'Logout',
),
],
),
bottomNavigationBar: BottomNavigation(
children: [
if (!_loggedin) ...[
BottomNavigationItem(
onPressed: () async {
final bool res = await showLogin(context, registration: true);
setState(() {
_loggedin = res;
});
},
icon: Icons.person_add_alt,
color: Colors.white,
label: 'Registrieren',
),
BottomNavigationItem(
onPressed: () async {
await showLogin(context);
setState(() {
vm.isLoggedIn(context);
});
},
icon: Icons.login,
color: Colors.white,
label: 'Login',
),
] else
BottomNavigationItem(
onPressed: () async {
final navigator = Navigator.of(context);
if (await vm.isLoggedIn(context)) {
navigator.push(MaterialPageRoute(
builder: (builder) => const PersonsPage()));
} else {
navigator.pushAndRemoveUntil(
MaterialPageRoute(
builder: (builder) => const PersonsPage()),
(route) => false);
}
},
icon: Icons.person_search,
color: Colors.white,
label: 'Personen',
),
BottomNavigationItem(
onPressed: () {},
icon: Icons.dashboard,
color: Colors.white,
label: 'Dashboard',
),
...[]
],
),
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) {
// _checkResponse(value.response);
if (!widget.loggedOut) {
_isLoggedIn(context);
}
return _loading
? const CircularProgressIndicator(
color: Colors.grey,
)
: Column(
children: [
Image.asset(
'lib/assets/logo_300x200.png',
),
const SizedBox(
height: 40,
),
Text(
'Digitale Spuren auf Knopfdruck entfernen'
.toUpperCase(),
textAlign: TextAlign.center,
style: const TextStyle(
fontFamily: 'sans-serif',
fontSize: 24,
height: 1.6,
fontWeight: FontWeight.normal,
letterSpacing: 6,
),
),
],
);
})),
),
),
));
}
}

View File

@ -0,0 +1,239 @@
import 'package:app/model/view_model/base_vm.dart';
import 'package:app/widgets/background.dart';
import 'package:app/widgets/bottom_navigation.dart';
import 'package:app/widgets/bottom_navigation_item.dart';
import 'package:app/widgets/side_drawer.dart';
import 'package:app/widgets/side_drawer_item.dart';
import 'package:flutter/material.dart';
import 'package:app/util/validation.dart';
import 'package:provider/provider.dart';
Future<bool> showLogin(BuildContext context,
{bool registration = false}) async {
final formKey = GlobalKey<FormState>();
final mailController = TextEditingController();
final passwordController = TextEditingController();
BaseViewModel vm = BaseViewModel();
bool submitted = false;
bool loggedin = false;
void login(BuildContext context) {
if (formKey.currentState!.validate()) {
submitted = true;
FocusScope.of(context).unfocus();
vm
.login(context,
email: mailController.text, password: passwordController.text)
.then(
(r) {
if (r) {
loggedin = r;
Navigator.pop(context, true);
}
},
);
passwordController.clear();
submitted = false;
}
}
void register(BuildContext context) {
if (formKey.currentState!.validate()) {
submitted = true;
vm
.createAccount(
context,
email: mailController.text,
password: passwordController.text,
)
.then(
(r) {
if (r) {
loggedin = r;
Navigator.pop(context, true);
}
},
);
}
}
await showModalBottomSheet(
useSafeArea: true,
isScrollControlled: true,
backgroundColor: Colors.black,
context: context,
builder: (builder) {
return Background(
child: Scaffold(
drawer: SideDrawer(
children: [
const Spacer(
flex: 3,
),
SideDrawerItem(
onPressed: () {},
icon: Icons.question_answer,
color: Colors.white,
label: 'About',
),
SideDrawerItem(
onPressed: () {},
icon: Icons.privacy_tip,
color: Colors.white,
label: 'Datenschutz',
),
SideDrawerItem(
onPressed: () {},
icon: Icons.apartment,
color: Colors.white,
label: 'Impressum',
),
const Spacer(
flex: 1,
),
],
),
bottomNavigationBar: BottomNavigation(
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,
),
const Image(
width: 180,
image: AssetImage(
'lib/assets/logo_300x200.png',
),
),
const SizedBox(
height: 30,
),
Text(
registration ? 'Registrieren' : 'Login',
style: const TextStyle(
fontFamily: 'sans-serif',
fontSize: 24,
height: 1.6,
fontWeight: FontWeight.normal,
letterSpacing: 6,
),
),
ChangeNotifierProvider<BaseViewModel>(
create: (context) => BaseViewModel(),
child: Consumer<BaseViewModel>(
builder: (context, value, child) => Form(
key: formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const SizedBox(
height: 40,
),
TextFormField(
// autofocus: true,
// inputFormatters: [
// FilteringTextInputFormatter.allow(
// emailRegExp,
// ),
// ],
controller: mailController,
decoration: const InputDecoration(
fillColor: Color.fromARGB(30, 255, 255, 255),
filled: true,
hintStyle: TextStyle(
color: Colors.white38,
),
hintText: 'E-Mail Adresse',
),
keyboardType: TextInputType.emailAddress,
validator: (value) {
if (value == null || !value.isValidEmail) {
return 'Bitte eine gültige E-Mail Adresse eingeben';
}
return null;
},
),
TextFormField(
style: const TextStyle(
color: Colors.white,
),
// inputFormatters: [
// FilteringTextInputFormatter.allow(
// passwordRegExp,
// ),
// ],
controller: passwordController,
decoration: const InputDecoration(
fillColor: Color.fromARGB(30, 255, 255, 255),
filled: true,
hintStyle: TextStyle(
color: Colors.white38,
),
hintText: 'Passwort',
),
keyboardType: TextInputType.visiblePassword,
obscureText: true,
validator: (value) {
if (value == null || !value.isValidPassword) {
return 'Bitte geben Sie Ihr Passwort ein';
}
return null;
},
),
const SizedBox(
height: 15,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
onPressed: !submitted
? !registration
? () {
login(context);
}
: () {
register(context);
}
: null,
child: const Icon(Icons.login),
),
],
)
],
),
),
),
),
const Spacer(),
],
),
),
),
);
});
return loggedin;
}

View File

@ -1 +0,0 @@
enum Pages { start, login, about, persons, dashboard }

View File

@ -0,0 +1,184 @@
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/pb/person.pb.dart';
import 'package:app/widgets/background.dart';
import 'package:app/widgets/bottom_navigation.dart';
import 'package:app/widgets/bottom_navigation_item.dart';
import 'package:app/widgets/side_drawer.dart';
import 'package:app/widgets/side_drawer_item.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class PersonsPage extends StatefulWidget {
const PersonsPage({super.key});
@override
State<PersonsPage> createState() => _PersonsPageState();
}
class _PersonsPageState extends State<PersonsPage> {
@override
void initState() {
super.initState();
_init();
}
void _init() async {
_setLoading(true);
_loggedin = await BackendService.isLoggedIn;
_setLoading(false);
}
void _setLoading(bool loading) {
setState(() {
_loading = loading;
});
}
void _checkResponse(ApiResponse response) {
if (response.status == Status.ERROR &&
response.message!.contains('unauthenticated')) {
BackendService.logout();
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(
builder: (builder) => HomePage(
loggedOut: true,
)),
(route) => false);
}
}
bool _loading = true;
bool _loggedin = false;
List<Person> persons = [];
PersonsViewModel vm = PersonsViewModel();
void listPersons(BuildContext context) async {
persons = await vm.listPersons();
}
List<Widget> _personsList(List<Person> persons) {
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),
),
));
}
return list;
}
@override
Widget build(BuildContext context) {
return Background(
child: Scaffold(
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
floatingActionButton: FloatingActionButton(
onPressed: () {},
child: const Icon(Icons.add),
),
appBar: AppBar(
automaticallyImplyLeading: false,
),
drawer: SideDrawer(
children: [
const Spacer(
flex: 3,
),
SideDrawerItem(
onPressed: () {},
icon: Icons.question_answer,
color: Colors.white,
label: 'About',
),
SideDrawerItem(
onPressed: () {},
icon: Icons.privacy_tip,
color: Colors.white,
label: 'Datenschutz',
),
SideDrawerItem(
onPressed: () {},
icon: Icons.apartment,
color: Colors.white,
label: 'Impressum',
),
const Spacer(
flex: 1,
),
if (_loggedin)
SideDrawerItem(
onPressed: () async {
setState(() {
_loading = true;
});
await BackendService.logout();
// ignore: use_build_context_synchronously
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(
builder: (builder) => HomePage(
loggedOut: true,
)),
(route) => false);
setState(() {
_loggedin = false;
_loading = false;
});
},
icon: Icons.logout,
color: Colors.white,
label: 'Logout',
),
],
),
bottomNavigationBar: BottomNavigation(
children: [
BottomNavigationItem(
onPressed: () {},
icon: Icons.dashboard,
color: Colors.white,
label: 'Dashboard',
),
BottomNavigationItem(
onPressed: () {
Navigator.of(context).pop();
},
icon: Icons.home,
color: Colors.white,
label: 'Home',
),
],
),
body: Padding(
padding: const EdgeInsets.all(16),
child: Center(
child: ChangeNotifierProvider<PersonsViewModel>(
create: (context) => PersonsViewModel(),
child: Consumer<PersonsViewModel>(
builder: (context, value, child) {
_checkResponse(value.response);
listPersons(context);
return _loading
? const CircularProgressIndicator(
color: Colors.grey,
)
: value.response.status == Status.COMPLETED
? value.response.data.length > 0
? ListView(children: _personsList(persons))
: const Text('Noch keine Personen angelegt')
: const Text('Lade Daten...');
},
),
),
),
),
),
);
}
}

View File

@ -1,360 +0,0 @@
import 'package:app/gapi/client.dart';
import 'package:app/pages/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({
super.key,
required this.client,
});
final GClient client;
@override
State<RegisterPage> createState() => _RegisterPageState();
}
class _RegisterPageState extends State<RegisterPage> {
bool _loading = false;
final _formKey = GlobalKey<FormState>();
final mailController = TextEditingController();
final passwordController = TextEditingController();
void _setLoading(bool loading) {
setState(() {
_loading = loading;
});
}
@override
Widget build(BuildContext context) {
return Background(
child: Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
flexibleSpace: Image.asset(
'lib/assets/logo_300x200.png',
height: 80,
),
),
drawer: Builder(builder: (context) {
return SideDrawer(
children: [
const Spacer(),
TextButton(
onPressed: () {
Scaffold.of(context).closeDrawer();
},
child: const Row(
children: [
Text(
'About',
style: TextStyle(fontSize: 20),
),
Spacer(),
Icon(
Icons.question_answer,
color: Colors.white,
),
],
),
),
TextButton(
onPressed: () {
Scaffold.of(context).closeDrawer();
},
child: const Row(
children: [
Text(
'Datenschutz',
style: TextStyle(fontSize: 20),
),
Spacer(),
Icon(
Icons.privacy_tip,
color: Colors.white,
),
],
),
),
TextButton(
onPressed: () {
Scaffold.of(context).closeDrawer();
},
child: const Row(
children: [
Text(
'Impressum',
style: TextStyle(fontSize: 20),
),
Spacer(),
Icon(
Icons.apartment,
color: Colors.white,
),
],
),
),
const SizedBox(
height: 250,
)
],
);
}),
bottomNavigationBar: BottomBar(
children: [
BottomNavigationBarItem(
label: 'back',
backgroundColor: Colors.white,
icon: IconButton(
onPressed: () => Navigator.of(context).pop(widget.client),
icon: const Icon(
Icons.arrow_back,
color: Colors.white,
),
),
),
BottomNavigationBarItem(
backgroundColor: Colors.white,
label: 'Menu',
icon: IconButton(
onPressed: () => Scaffold.of(context).openDrawer(),
icon: const Icon(
Icons.menu,
color: Colors.white,
),
),
),
],
),
body: !_loading
? Form(
key: _formKey,
child: Padding(
padding: const EdgeInsets.fromLTRB(16, 100, 16, 16),
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Image(
width: 180,
image: AssetImage(
'lib/assets/logo_300x200.png',
),
),
const SizedBox(
height: 30,
),
const Text(
'Registrieren',
style: TextStyle(
fontFamily: 'sans-serif',
fontSize: 24,
height: 1.6,
fontWeight: FontWeight.normal,
letterSpacing: 6,
),
),
const SizedBox(
height: 20,
),
TextFormField(
controller: mailController,
decoration: const InputDecoration(
fillColor: Color.fromARGB(30, 255, 255, 255),
filled: true,
hintStyle: TextStyle(
color: Colors.white38,
),
hintText: 'E-Mail Adresse',
),
keyboardType: TextInputType.emailAddress,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Bitte eine gültige E-Mail Adresse eingeben';
}
return null;
},
),
TextFormField(
style: const TextStyle(
color: Colors.white,
),
controller: passwordController,
decoration: const InputDecoration(
fillColor: Color.fromARGB(30, 255, 255, 255),
filled: true,
hintStyle: TextStyle(
color: Colors.white38,
),
hintText: 'Passwort',
),
keyboardType: TextInputType.visiblePassword,
obscureText: true,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Bitte geben Sie Ihr Passwort ein';
}
return null;
},
),
const SizedBox(
height: 15,
),
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
_setLoading(true);
widget.client
.createAccount(
email: mailController.text,
password: passwordController.text,
onError: ({GrpcError? error}) {
_setLoading(false);
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(
content: const Text(
'Login fehlgeschlagen',
),
action: SnackBarAction(
textColor: Colors.grey,
label: 'Details',
onPressed: () {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
content: error != null
? Text(
'Fehler: ${error.message}',
textAlign:
TextAlign
.center,
style: const TextStyle(
color: Colors
.black),
)
: const Text(
'Interner Fehler',
textAlign:
TextAlign
.center,
style: TextStyle(
color: Colors
.black),
),
icon: const Icon(
Icons.error,
color: Colors.black,
),
);
},
);
}),
));
})
.then(
(r) {
if (r.account.secretKey != '') {
widget.client
.login(
email: mailController.text,
password:
passwordController.text,
onError: (
{GrpcError? error}) {},
onSuccess: () {})
.then((resp) {
widget.client.getAccount(
accountId: r.account.id,
onError: ({GrpcError? err}) {
_setLoading(false);
ScaffoldMessenger.of(context)
.showSnackBar(
SnackBar(
content: const Text(
'Login fehlgeschlagen',
),
action: SnackBarAction(
textColor: Colors.grey,
label: 'Details',
onPressed: () {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
content: err !=
null
? Text(
'Hoppla! Da ist etwas schief gelaufen..\n\n${err.message}',
textAlign:
TextAlign.center,
style: const TextStyle(
color:
Colors.black),
)
: const Text(
'Interner Fehler',
textAlign:
TextAlign.center,
style: TextStyle(
color:
Colors.black),
),
icon:
const Icon(
Icons.error,
color: Colors
.black,
),
);
},
);
}),
),
);
});
});
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(
builder: (builder) => StartPage(
client: widget.client)),
(route) => false);
showDialog(
context: context,
builder: (builder) {
return const AlertDialog(
content: Text(
'Account wurde angelegt',
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.black),
),
icon: Icon(
Icons.done,
size: 50,
),
iconColor: Colors.green,
);
});
}
},
);
}
},
child: const Icon(Icons.login))
],
),
),
),
)
: const LoadingWidget()),
);
}
}

View File

@ -1,344 +0,0 @@
import 'package:app/gapi/client.dart';
import 'package:app/pages/dashboard_page.dart';
import 'package:app/pages/login_page.dart';
import 'package:app/pages/register_page.dart';
import 'package:app/widgets/background.dart';
import 'package:app/widgets/bottom_bar.dart';
import 'package:app/widgets/side_drawer.dart';
import 'package:flutter/material.dart';
import 'dart:core';
// ignore: must_be_immutable
class StartPage extends StatefulWidget {
StartPage({
super.key,
required this.client,
});
GClient client;
@override
State<StartPage> createState() => _StartPageState();
}
class _StartPageState extends State<StartPage> {
final List<BottomNavigationBarItem> bottombarButtons = [];
void _updateClient(GClient c) {
setState(() {
widget.client = c;
});
}
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return Background(
child: Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
),
drawer: Builder(builder: (context) {
return SideDrawer(children: [
const Spacer(),
TextButton(
onPressed: () {
Scaffold.of(context).closeDrawer();
},
child: const Row(
children: [
Text(
'About',
style: TextStyle(fontSize: 20),
),
Spacer(),
Icon(
Icons.question_answer,
color: Colors.white,
),
],
),
),
TextButton(
onPressed: () {
Scaffold.of(context).closeDrawer();
},
child: const Row(
children: [
Text(
'Datenschutz',
style: TextStyle(fontSize: 20),
),
Spacer(),
Icon(
Icons.privacy_tip,
color: Colors.white,
),
],
),
),
TextButton(
onPressed: () {
Scaffold.of(context).closeDrawer();
},
child: const Row(
children: [
Text(
'Impressum',
style: TextStyle(fontSize: 20),
),
Spacer(),
Icon(
Icons.apartment,
color: Colors.white,
),
],
),
),
TextButton(
onPressed: () {
setState(() {
widget.client.session.accessToken = null;
widget.client.session
.removeSession(widget.client.session.sessionId!);
});
},
child: const Row(
children: [
Text(
'Log out',
style: TextStyle(fontSize: 20),
),
Spacer(),
Icon(
Icons.logout,
color: Colors.white,
),
],
),
),
const SizedBox(
height: 250,
)
]);
}),
bottomNavigationBar: Builder(builder: (context) {
return BottomBar(
// onTap: (value) => _bottomBarAction(value),
children: widget.client.session.accessToken != null
? [
BottomNavigationBarItem(
backgroundColor: Colors.white,
label: 'Personen',
icon: Column(
children: [
IconButton(
onPressed: () => Scaffold.of(context).openDrawer(),
icon: const Icon(
Icons.group,
color: Colors.white,
),
),
const Text(
'Personen',
style: TextStyle(
color: Colors.white,
fontSize: 16,
),
)
],
),
),
BottomNavigationBarItem(
backgroundColor: Colors.white,
label: 'Dashboard',
icon: Column(
children: [
IconButton(
onPressed: () async {
if (widget.client.session.accessTokenExpiresAt!
.toDateTime()
.isBefore(DateTime.now())) {
await widget.client.refreshToken();
}
if (!widget.client.isLoggedIn &&
context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Sitzung ist abgelaufen.'),
),
);
if (context.mounted) {
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(
builder: (context) =>
LoginPage(client: widget.client)),
(route) => false);
}
} else {
final ctx = context;
// ignore: use_build_context_synchronously
GClient c = await Navigator.push(
ctx,
MaterialPageRoute(
builder: (context) => DashboardPage(
client: widget.client,
),
),
);
_updateClient(c);
}
},
icon: const Icon(
Icons.dashboard,
color: Colors.white,
),
),
const Text(
'Dashboard',
style: TextStyle(
color: Colors.white,
fontSize: 16,
),
)
],
),
),
BottomNavigationBarItem(
backgroundColor: Colors.white,
label: 'Menu',
icon: IconButton(
onPressed: () {
Scaffold.of(context).openDrawer();
},
icon: const Icon(
Icons.menu,
color: Colors.white,
),
),
)
]
: [
BottomNavigationBarItem(
label: 'register',
backgroundColor: Colors.white,
icon: Column(
children: [
IconButton(
onPressed: () async {
widget.client = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => RegisterPage(
client: widget.client,
)));
},
icon: const Icon(
Icons.login,
color: Colors.white,
),
),
const Text(
'Registrieren',
style: TextStyle(
color: Colors.white,
fontSize: 16,
),
)
],
),
),
BottomNavigationBarItem(
label: 'login',
backgroundColor: Colors.white,
icon: Column(
children: [
IconButton(
onPressed: () async {
final c = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => LoginPage(
client: widget.client,
),
));
_updateClient(c);
},
icon: const Icon(
Icons.login,
color: Colors.white,
),
),
const Text(
'Login',
style: TextStyle(
color: Colors.white,
fontSize: 16,
),
)
],
),
),
BottomNavigationBarItem(
backgroundColor: Colors.white,
label: 'Menu',
icon: IconButton(
onPressed: () => Scaffold.of(context).openDrawer(),
icon: const Icon(
Icons.menu,
color: Colors.white,
),
),
),
],
);
}),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Image(
image: AssetImage(
'lib/assets/logo_300x200.png',
),
),
if (widget.client.account != null &&
!widget.client.account!.emailVerified)
Container(
height: 120,
width: double.infinity,
padding:
const EdgeInsets.symmetric(vertical: 20, horizontal: 10),
child: Card(
color: Colors.brown.shade300,
child: const Text(
'Deine E-Mail Adresse ist noch nicht validiert.'),
),
),
const SizedBox(
height: 40,
),
Text(
'Digitale Spuren auf Knopfdruck entfernen'.toUpperCase(),
textAlign: TextAlign.center,
style: const TextStyle(
fontFamily: 'sans-serif',
fontSize: 24,
height: 1.6,
fontWeight: FontWeight.normal,
letterSpacing: 6,
),
),
],
),
),
),
);
}
}

View File

@ -0,0 +1,302 @@
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';
class DashboardPage extends StatefulWidget {
const DashboardPage({
super.key,
// required this.client,
});
// final GClient client;
@override
State<DashboardPage> createState() => _DashboardPageState();
}
class _DashboardPageState extends State<DashboardPage> {
bool _loading = false;
late AccountInfo accountInfo;
void _setLoading(bool loading) {
setState(() {
_loading = loading;
});
}
@override
void initState() {
super.initState();
_setLoading(true);
// widget.client.getAccountInfo(
// GetAccountInfoRequest(
// accountId: widget.client.session.accountId,
// ),
// onError: ({String? msg}) {
// ScaffoldMessenger.of(context).showSnackBar(
// SnackBar(
// content: const Text('AccountInfo konnte nicht geladen werden'),
// action: msg != null
// ? SnackBarAction(
// label: 'Details',
// textColor: Colors.grey,
// onPressed: () {
// showDialog(
// context: context,
// builder: (context) {
// return AlertDialog(
// content: Text(
// msg,
// textAlign: TextAlign.center,
// style: const TextStyle(color: Colors.black),
// ),
// icon: const Icon(
// Icons.warning,
// color: Colors.red,
// ),
// );
// },
// );
// })
// : null,
// ),
// );
// },
// ).then((value) {
// accountInfo = value.accountInfo;
// _setLoading(false);
// });
}
@override
Widget build(BuildContext context) {
return Background(
child: Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
flexibleSpace: Image.asset(
'lib/assets/logo_300x200.png',
height: 80,
),
),
drawer: Builder(builder: (context) {
return SideDrawer(
children: [
const Spacer(),
TextButton(
onPressed: () {
Scaffold.of(context).closeDrawer();
},
child: const Row(
children: [
Text(
'About',
style: TextStyle(fontSize: 20),
),
Spacer(),
Icon(
Icons.question_answer,
color: Colors.white,
),
],
),
),
TextButton(
onPressed: () {
Scaffold.of(context).closeDrawer();
},
child: const Row(
children: [
Text(
'Datenschutz',
style: TextStyle(fontSize: 20),
),
Spacer(),
Icon(
Icons.privacy_tip,
color: Colors.white,
),
],
),
),
TextButton(
onPressed: () {
Scaffold.of(context).closeDrawer();
},
child: const Row(
children: [
Text(
'Impressum',
style: TextStyle(fontSize: 20),
),
Spacer(),
Icon(
Icons.apartment,
color: Colors.white,
),
],
),
),
// if (widget.client.session.accessToken != null)
// TextButton(
// onPressed: () {
// widget.client.session.accessToken = null;
// widget.client.session
// .removeSession(widget.client.session.sessionId!);
// Navigator.of(context).pushAndRemoveUntil(
// MaterialPageRoute(
// builder: (context) =>
// StartPage(client: widget.client),
// ),
// (route) => false);
// },
// child: const Row(
// children: [
// Text(
// 'Log out',
// style: TextStyle(fontSize: 20),
// ),
// Spacer(),
// Icon(
// Icons.logout,
// color: Colors.white,
// ),
// ],
// ),
// ),
const SizedBox(
height: 250,
)
],
);
}),
// bottomNavigationBar: Builder(
// builder: (context) {
// return BottomBar(
// children: widget.client.session.accessToken != null
// ? [
// BottomNavigationBarItem(
// backgroundColor: Colors.white,
// label: 'Personen',
// icon: Column(
// children: [
// IconButton(
// onPressed: () =>
// Scaffold.of(context).openDrawer(),
// icon: const Icon(
// Icons.group,
// color: Colors.white,
// ),
// ),
// const Text(
// 'Personen',
// style: TextStyle(
// color: Colors.white,
// fontSize: 16,
// ),
// )
// ],
// ),
// ),
// BottomNavigationBarItem(
// backgroundColor: Colors.white,
// label: 'Home',
// icon: Column(
// children: [
// IconButton(
// onPressed: () {
// Navigator.of(context).push(
// MaterialPageRoute(
// builder: (context) => StartPage(
// client: widget.client,
// ),
// ),
// );
// },
// icon: const Icon(
// Icons.home,
// color: Colors.white,
// ),
// ),
// const Text(
// 'Home',
// style: TextStyle(
// color: Colors.white,
// fontSize: 16,
// ),
// )
// ],
// ),
// ),
// BottomNavigationBarItem(
// backgroundColor: Colors.white,
// label: 'Menu',
// icon: IconButton(
// onPressed: () {
// Scaffold.of(context).openDrawer();
// },
// icon: const Icon(
// Icons.menu,
// color: Colors.white,
// ),
// ),
// )
// ]
// : [
// BottomNavigationBarItem(
// label: 'back',
// backgroundColor: Colors.white,
// icon: IconButton(
// onPressed: () {},
// icon: const Icon(
// Icons.arrow_back,
// color: Colors.white,
// ),
// ),
// ),
// BottomNavigationBarItem(
// backgroundColor: Colors.white,
// label: 'Menu',
// icon: IconButton(
// onPressed: () => Scaffold.of(context).openDrawer(),
// icon: const Icon(
// Icons.menu,
// color: Colors.white,
// ),
// ),
// ),
// ],
// );
// },
// ),
body: !_loading
? Background(
child: Center(
child: Column(
children: [
const SizedBox(
height: 48,
),
Text(
'Willkommen ${accountInfo.firstname} ${accountInfo.lastname}!',
style: const TextStyle(
fontSize: 24,
),
),
],
),
),
)
: const LoadingWidget(),
),
);
}
}

View File

@ -1,5 +1,6 @@
import 'package:app/gapi/client.dart';
import 'package:app/pages/start_page.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';
@ -12,11 +13,11 @@ import 'package:grpc/grpc.dart';
class LoginPage extends StatefulWidget {
const LoginPage({
super.key,
required this.client,
// required this.client,
// required this.onChangePage,
});
final GClient client;
// final GClient client;
// void Function(Pages page) onChangePage;
@override
@ -87,7 +88,7 @@ class _LoginPageState extends State<LoginPage> {
label: 'back',
backgroundColor: Colors.white,
icon: IconButton(
onPressed: () => Navigator.of(context).pop(widget.client),
onPressed: () => Navigator.of(context).pop(),
icon: const Icon(
Icons.arrow_back,
color: Colors.white,
@ -250,73 +251,23 @@ class _LoginPageState extends State<LoginPage> {
if (_formKey.currentState!.validate()) {
// final navigator = Navigator.of(context);
_setLoading(true);
widget.client
.login(
BackendService.login(
email: mailController.text,
password: passwordController.text,
onError: ({GrpcError? error}) {
_setLoading(false);
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(
content: const Text(
'Login fehlgeschlagen',
),
action: SnackBarAction(
textColor: Colors.grey,
label: 'Details',
onPressed: () {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
content: error != null
? Text(
'Fehler: ${error.message}',
textAlign:
TextAlign.center,
style:
const TextStyle(
color: Colors
.black),
)
: const Text(
'Interner Fehler',
textAlign:
TextAlign.center,
style: TextStyle(
color:
Colors.black),
),
icon: const Icon(
Icons.error,
color: Colors.black,
),
);
},
);
}),
));
},
onSuccess: () {
// _setLoading(false);
ScaffoldMessenger.of(context)
.showSnackBar(const SnackBar(
content: Text('Login erfolgreich'),
));
},
)
.then(
).then(
(r) {
if (r.accessToken != '') {
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(
builder: (ctx) => StartPage(
client: widget.client,
),
),
(ctx) => false,
);
if (r) {
Navigator.pop(context);
Navigator.pop(context);
// Navigator.pushAndRemoveUntil(
// context,
// MaterialPageRoute(
// builder: (ctx) => const StartPage(
// // client: widget.client,
// ),
// ),
// (ctx) => false,
// );
// widget.onChangePage(
// Pages.dashboard,
// );

View File

@ -0,0 +1,224 @@
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({
super.key,
// required this.client,
});
// final GClient client;
@override
State<RegisterPage> createState() => _RegisterPageState();
}
class _RegisterPageState extends State<RegisterPage> {
bool _loading = false;
final _formKey = GlobalKey<FormState>();
final mailController = TextEditingController();
final passwordController = TextEditingController();
void _setLoading(bool loading) {
setState(() {
_loading = loading;
});
}
@override
Widget build(BuildContext context) {
return Background(
child: Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
flexibleSpace: Image.asset(
'lib/assets/logo_300x200.png',
height: 80,
),
),
drawer: Builder(builder: (context) {
return SideDrawer(
children: [
const Spacer(),
TextButton(
onPressed: () {
Scaffold.of(context).closeDrawer();
},
child: const Row(
children: [
Text(
'About',
style: TextStyle(fontSize: 20),
),
Spacer(),
Icon(
Icons.question_answer,
color: Colors.white,
),
],
),
),
TextButton(
onPressed: () {
Scaffold.of(context).closeDrawer();
},
child: const Row(
children: [
Text(
'Datenschutz',
style: TextStyle(fontSize: 20),
),
Spacer(),
Icon(
Icons.privacy_tip,
color: Colors.white,
),
],
),
),
TextButton(
onPressed: () {
Scaffold.of(context).closeDrawer();
},
child: const Row(
children: [
Text(
'Impressum',
style: TextStyle(fontSize: 20),
),
Spacer(),
Icon(
Icons.apartment,
color: Colors.white,
),
],
),
),
const SizedBox(
height: 250,
)
],
);
}),
bottomNavigationBar: BottomBar(
children: [
BottomNavigationBarItem(
label: 'back',
backgroundColor: Colors.white,
icon: IconButton(
onPressed: () => Navigator.of(context).pop(),
icon: const Icon(
Icons.arrow_back,
color: Colors.white,
),
),
),
BottomNavigationBarItem(
backgroundColor: Colors.white,
label: 'Menu',
icon: IconButton(
onPressed: () => Scaffold.of(context).openDrawer(),
icon: const Icon(
Icons.menu,
color: Colors.white,
),
),
),
],
),
body: !_loading
? Form(
key: _formKey,
child: Padding(
padding: const EdgeInsets.fromLTRB(16, 100, 16, 16),
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Image(
width: 180,
image: AssetImage(
'lib/assets/logo_300x200.png',
),
),
const SizedBox(
height: 30,
),
const Text(
'Registrieren',
style: TextStyle(
fontFamily: 'sans-serif',
fontSize: 24,
height: 1.6,
fontWeight: FontWeight.normal,
letterSpacing: 6,
),
),
const SizedBox(
height: 20,
),
TextFormField(
controller: mailController,
decoration: const InputDecoration(
fillColor: Color.fromARGB(30, 255, 255, 255),
filled: true,
hintStyle: TextStyle(
color: Colors.white38,
),
hintText: 'E-Mail Adresse',
),
keyboardType: TextInputType.emailAddress,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Bitte eine gültige E-Mail Adresse eingeben';
}
return null;
},
),
TextFormField(
style: const TextStyle(
color: Colors.white,
),
controller: passwordController,
decoration: const InputDecoration(
fillColor: Color.fromARGB(30, 255, 255, 255),
filled: true,
hintStyle: TextStyle(
color: Colors.white38,
),
hintText: 'Passwort',
),
keyboardType: TextInputType.visiblePassword,
obscureText: true,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Bitte geben Sie Ihr Passwort ein';
}
return null;
},
),
const SizedBox(
height: 15,
),
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
// _setLoading(true);
}
},
child: const Icon(Icons.login))
],
),
),
),
)
: const LoadingWidget()),
);
}
}

View File

@ -0,0 +1,373 @@
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';
import 'package:app/widgets/side_drawer.dart';
import 'package:flutter/material.dart';
import 'dart:core';
import 'package:provider/provider.dart';
// ignore: must_be_immutable
class StartPage extends StatefulWidget {
const StartPage({
super.key,
// required this.client,
});
// GClient client;
@override
State<StartPage> createState() => _StartPageState();
}
class _StartPageState extends State<StartPage> {
final List<BottomNavigationBarItem> bottombarButtons = [];
// void _updateClient(GClient c) {
// setState(() {
// widget.client = c;
// });
// }
@override
void initState() {
super.initState();
}
SnackBar _snackBar(BuildContext context, String message, String label,
void Function() action) {
ScaffoldMessenger.of(context).removeCurrentSnackBar();
// ScaffoldMessenger.of(context).clearSnackBars();
return SnackBar(
content: Text(
message,
style: const TextStyle(color: Colors.black),
),
backgroundColor: Colors.white,
action: SnackBarAction(
label: label,
onPressed: action,
),
);
}
@override
Widget build(BuildContext context) {
return Background(
child: ChangeNotifierProvider<AccountViewModel>(
create: (context) => AccountViewModel(),
child: Consumer<AccountViewModel>(builder: (context, value, child) {
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
),
drawer: Builder(builder: (context) {
return SideDrawer(children: [
const Spacer(),
TextButton(
onPressed: () {
Scaffold.of(context).closeDrawer();
},
child: const Row(
children: [
Text(
'About',
style: TextStyle(fontSize: 20),
),
Spacer(),
Icon(
Icons.question_answer,
color: Colors.white,
),
],
),
),
TextButton(
onPressed: () {
Scaffold.of(context).closeDrawer();
},
child: const Row(
children: [
Text(
'Datenschutz',
style: TextStyle(fontSize: 20),
),
Spacer(),
Icon(
Icons.privacy_tip,
color: Colors.white,
),
],
),
),
TextButton(
onPressed: () {
Scaffold.of(context).closeDrawer();
},
child: const Row(
children: [
Text(
'Impressum',
style: TextStyle(fontSize: 20),
),
Spacer(),
Icon(
Icons.apartment,
color: Colors.white,
),
],
),
),
TextButton(
onPressed: () {
value.logout();
// ScaffoldMessenger.of(context).clearSnackBars();
ScaffoldMessenger.of(context).showSnackBar(
_snackBar(
context,
value.response.message != null
? value.response.message!
: value.response.status.toString(),
value.response.status.toString(),
() {
print('asdf');
},
),
);
},
child: const Row(
children: [
Text(
'Log out',
style: TextStyle(fontSize: 20),
),
Spacer(),
Icon(
Icons.logout,
color: Colors.white,
),
],
),
),
const SizedBox(
height: 250,
)
]);
}),
bottomNavigationBar: Builder(builder: (context) {
return BottomBar(
// onTap: (value) => _bottomBarAction(value),
children: value.response.data != null
? [
BottomNavigationBarItem(
backgroundColor: Colors.white,
label: 'Personen',
icon: Column(
children: [
IconButton(
onPressed: () =>
Scaffold.of(context).openDrawer(),
icon: const Icon(
Icons.group,
color: Colors.white,
),
),
const Text(
'Personen',
style: TextStyle(
color: Colors.white,
fontSize: 16,
),
)
],
),
),
BottomNavigationBarItem(
backgroundColor: Colors.white,
label: 'Dashboard',
icon: Column(
children: [
IconButton(
onPressed: () {},
icon: const Icon(
Icons.dashboard,
color: Colors.white,
),
),
const Text(
'Dashboard',
style: TextStyle(
color: Colors.white,
fontSize: 16,
),
)
],
),
),
BottomNavigationBarItem(
backgroundColor: Colors.white,
label: 'Menu',
icon: IconButton(
onPressed: () {
Scaffold.of(context).openDrawer();
},
icon: const Icon(
Icons.menu,
color: Colors.white,
),
),
)
]
: [
BottomNavigationBarItem(
label: 'register',
backgroundColor: Colors.white,
icon: Column(
children: [
IconButton(
onPressed: () {},
icon: const Icon(
Icons.login,
color: Colors.white,
),
),
const Text(
'Registrieren',
style: TextStyle(
color: Colors.white,
fontSize: 16,
),
)
],
),
),
BottomNavigationBarItem(
label: 'login',
backgroundColor: Colors.white,
icon: Column(
children: [
IconButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (builder) =>
const LoginPage()));
},
icon: const Icon(
Icons.login,
color: Colors.white,
),
),
const Text(
'Login',
style: TextStyle(
color: Colors.white,
fontSize: 16,
),
)
],
),
),
BottomNavigationBarItem(
backgroundColor: Colors.white,
label: 'Menu',
icon: IconButton(
onPressed: () => Scaffold.of(context).openDrawer(),
icon: const Icon(
Icons.menu,
color: Colors.white,
),
),
),
],
);
}),
body: Column(
children: [
if (value.response.status == Status.COMPLETED &&
value.response.data != null &&
!(value.response.data as Account).emailVerified)
Card(
color: Colors.orange,
child: Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
const Text(
'E-Mail ist noch nicht verifiziert.',
style: TextStyle(
fontWeight: FontWeight.bold,
fontFamily: 'sans-serif',
fontSize: 14),
),
IconButton(
onPressed: () {},
icon: const Icon(
Icons.restore,
color: Colors.white,
),
),
],
),
),
),
Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Image(
image: AssetImage(
'lib/assets/logo_300x200.png',
),
),
const SizedBox(
height: 40,
),
Text(
'Digitale Spuren auf Knopfdruck entfernen'
.toUpperCase(),
textAlign: TextAlign.center,
style: const TextStyle(
fontFamily: 'sans-serif',
fontSize: 24,
height: 1.6,
fontWeight: FontWeight.normal,
letterSpacing: 6,
),
),
TextButton(
onPressed: () {
// ScaffoldMessenger.of(context).clearSnackBars();
ScaffoldMessenger.of(context).showSnackBar(
_snackBar(
context,
value.response.message != null
? value.response.message!
: value.response.status.toString(),
value.response.status.toString(),
() {
print('asdf');
},
),
);
},
child: const Text('asdf'))
],
),
),
],
),
);
}),
),
);
}
}

View File

@ -0,0 +1,11 @@
import 'package:flutter/material.dart';
class CustomColors {
static Color get error {
return const Color.fromARGB(200, 255, 90, 90);
}
static Color get success {
return const Color.fromARGB(200, 55, 125, 55);
}
}

View File

@ -0,0 +1,27 @@
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'^.+$');
extension valString on String {
bool get isValidEmail {
return emailRegExp.hasMatch(this);
}
bool get isValidName {
return nameRegExp.hasMatch(this);
}
bool get isValidPassword {
return passwordRegExp.hasMatch(this);
}
bool get isNotEmpty {
return this != trim();
}
bool get isValidPhone {
return phoneRegExp.hasMatch(this);
}
}

View File

@ -0,0 +1,49 @@
import 'package:flutter/material.dart';
// ignore: must_be_immutable
class BottomNavigation extends StatelessWidget {
BottomNavigation({
super.key,
required this.children,
this.backgroundColor,
this.iconColor,
}) {
backgroundColor ??= Colors.black;
}
List<Widget> children;
Color? backgroundColor;
Color? iconColor;
@override
Widget build(BuildContext context) {
return Container(
height: 70,
color: backgroundColor,
child: Center(
child: Padding(
padding: const EdgeInsets.symmetric(
// horizontal: 10,
),
child: Row(
mainAxisAlignment: children.isEmpty
? MainAxisAlignment.center
: MainAxisAlignment.spaceAround,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
...children,
Builder(builder: (context) {
return IconButton(
onPressed: () => Scaffold.of(context).openDrawer(),
icon: const Icon(
Icons.menu,
color: Colors.white,
));
}),
],
),
),
),
);
}
}

View File

@ -0,0 +1,46 @@
import 'package:flutter/material.dart';
// ignore: must_be_immutable
class BottomNavigationItem extends StatelessWidget {
BottomNavigationItem({
super.key,
required this.onPressed,
required this.icon,
required this.color,
this.textSize,
this.iconSize,
this.label,
}) {
textSize ??= 15;
iconSize ??= 25;
}
void Function() onPressed;
IconData icon;
Color color;
double? textSize;
double? iconSize;
String? label;
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment:
label != null ? MainAxisAlignment.center : MainAxisAlignment.center,
children: [
IconButton(
onPressed: onPressed,
icon: Icon(
icon,
size: iconSize,
color: color,
)),
if (label != null)
Text(
label!,
style: TextStyle(fontSize: textSize, color: color),
),
],
);
}
}

View File

@ -0,0 +1,30 @@
import 'package:flutter/material.dart';
// ignore: must_be_immutable
class SideDrawer extends StatelessWidget {
SideDrawer({super.key, required this.children, this.backgroundColor}) {
backgroundColor ??= Colors.black;
}
final List<Widget> children;
Color? backgroundColor;
@override
Widget build(BuildContext context) {
return Drawer(
backgroundColor: backgroundColor,
child: Padding(
padding: const EdgeInsets.all(40.0),
child: Center(
child: Builder(builder: (context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: children,
);
}),
),
),
);
}
}

View File

@ -0,0 +1,50 @@
import 'package:flutter/material.dart';
// ignore: must_be_immutable
class SideDrawerItem extends StatelessWidget {
SideDrawerItem({
super.key,
required this.onPressed,
required this.icon,
required this.color,
required this.label,
this.textSize,
this.iconSize,
}) {
textSize ??= 15;
iconSize ??= 25;
}
void Function() onPressed;
IconData icon;
Color color;
String label;
double? textSize;
double? iconSize;
@override
Widget build(BuildContext context) {
return TextButton(
onPressed: onPressed,
child: Row(
// mainAxisAlignment: MainAxisAlignment.start,
children: [
Icon(
icon,
color: color,
),
const SizedBox(
width: 20,
),
Text(
label,
style: TextStyle(
fontSize: textSize,
color: color,
),
)
],
),
);
}
}

View File

@ -195,6 +195,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.9.1"
nested:
dependency: transitive
description:
name: nested
sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
path:
dependency: "direct main"
description:
@ -219,6 +227,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.1.0"
provider:
dependency: "direct main"
description:
name: provider
sha256: cdbe7530b12ecd9eb455bdaa2fcb8d4dad22e80b8afb4798b41479d5ce26847f
url: "https://pub.dev"
source: hosted
version: "6.0.5"
sky_engine:
dependency: transitive
description: flutter

View File

@ -42,6 +42,7 @@ dependencies:
sqflite: ^2.3.0
path: ^1.8.3
fixnum: ^1.1.0
provider: ^6.0.5
dev_dependencies:
lints: ^2.0.0