ft/adds backend service

TODO: everything new
This commit is contained in:
itsscb 2023-11-03 15:35:44 +01:00
parent 06d3b4bc19
commit 5f0e46a1b8
12 changed files with 910 additions and 654 deletions

View File

@ -1,4 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'package:app/pb/session.pbjson.dart';
import 'package:fixnum/fixnum.dart'; import 'package:fixnum/fixnum.dart';
import 'package:app/pb/google/protobuf/timestamp.pb.dart'; import 'package:app/pb/google/protobuf/timestamp.pb.dart';
@ -40,6 +41,12 @@ class Session {
); );
Session s = Session(); Session s = Session();
s._database = db; s._database = db;
final sessions = await s.getSessions();
if (sessions.isNotEmpty) {
final session = sessions[0];
session._database = db;
return session;
}
return s; return s;
} }
@ -90,6 +97,41 @@ class Session {
return 'Session{accountId: $accountId, sessionId: $sessionId, accessToken: $accessToken, accessTokenExpiresAt: ${accessTokenExpiresAt.toString()}, refreshToken: $refreshToken, refreshTokenExpiresAt: ${refreshTokenExpiresAt.toString()}}'; 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 { Future<void> insertSession(Session session) async {
// print('INSERTING SESSION: ${session.sessionId}'); // print('INSERTING SESSION: ${session.sessionId}');
final db = _database; final db = _database;
@ -101,6 +143,17 @@ class Session {
// print('INSERT RESULT: $result'); // 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 { Future<void> removeSession(String sessionId) async {
final db = _database; final db = _database;
await db.delete('sessions', where: 'sessionId = ?', whereArgs: [sessionId]); await db.delete('sessions', where: 'sessionId = ?', whereArgs: [sessionId]);

View File

@ -45,8 +45,8 @@ void main() async {
)), )),
home: Background( home: Background(
child: StartPage( child: StartPage(
client: await GClient.client, // client: await GClient.client,
), ),
), ),
), ),
); );

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, this.message) : 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,237 @@
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/person.pb.dart';
import 'package:app/data/database.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_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';
import 'package:sqflite/sqflite.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) {
return false;
}
if (session.refreshTokenExpiresAt == null) {
return false;
}
if (session.refreshTokenExpiresAt!.toDateTime().isBefore(DateTime.now())) {
return false;
}
return true;
}
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) {
// await refreshToken(session);
// return getAccount();
// }
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<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,
),
);
await Session.newSession(response as Session);
return response.accessToken != '';
} on SocketException {
throw FetchDataException('Keine Internet Verbindung');
} on GrpcError catch (err) {
throw FetchDataException(err.message);
} catch (err) {
throw InternalException(err.toString());
}
}
Future<Session> refreshToken(Session session) async {
try {
final RefreshTokenResponse response = await _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,56 @@
import 'package:app/model/apis/api_response.dart';
import 'package:app/model/services/backend_service.dart';
import 'package:app/pb/account.pb.dart';
import 'package:flutter/material.dart';
class AccountViewModel with ChangeNotifier {
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 {
_apiResponse = ApiResponse.loading('Bereite alles vor');
try {
_apiResponse = ApiResponse.completed(await _service.getAccount(), 'done');
} catch (e) {
_apiResponse = ApiResponse.error(e.toString());
}
notifyListeners();
}
Future<void> fetchAccount() async {
_apiResponse = ApiResponse.loading('Hole Account');
notifyListeners();
try {
_apiResponse = ApiResponse.completed(await _service.getAccount(), 'done');
} catch (e) {
_apiResponse = ApiResponse.error(e.toString());
}
notifyListeners();
}
Future<void> logout() async {
_apiResponse = ApiResponse.loading('Logge aus');
notifyListeners();
try {
await BackendService.logout();
_apiResponse = ApiResponse.completed(null, 'Erfolgreich ausgeloggt');
} catch (e) {
_apiResponse = ApiResponse.error(e.toString());
}
print(_apiResponse.message);
notifyListeners();
}
}

View File

@ -11,10 +11,10 @@ import 'package:flutter/material.dart';
class DashboardPage extends StatefulWidget { class DashboardPage extends StatefulWidget {
const DashboardPage({ const DashboardPage({
super.key, super.key,
required this.client, // required this.client,
}); });
final GClient client; // final GClient client;
@override @override
State<DashboardPage> createState() => _DashboardPageState(); State<DashboardPage> createState() => _DashboardPageState();
@ -35,44 +35,44 @@ class _DashboardPageState extends State<DashboardPage> {
super.initState(); super.initState();
_setLoading(true); _setLoading(true);
widget.client.getAccountInfo( // widget.client.getAccountInfo(
GetAccountInfoRequest( // GetAccountInfoRequest(
accountId: widget.client.session.accountId, // accountId: widget.client.session.accountId,
), // ),
onError: ({String? msg}) { // onError: ({String? msg}) {
ScaffoldMessenger.of(context).showSnackBar( // ScaffoldMessenger.of(context).showSnackBar(
SnackBar( // SnackBar(
content: const Text('AccountInfo konnte nicht geladen werden'), // content: const Text('AccountInfo konnte nicht geladen werden'),
action: msg != null // action: msg != null
? SnackBarAction( // ? SnackBarAction(
label: 'Details', // label: 'Details',
textColor: Colors.grey, // textColor: Colors.grey,
onPressed: () { // onPressed: () {
showDialog( // showDialog(
context: context, // context: context,
builder: (context) { // builder: (context) {
return AlertDialog( // return AlertDialog(
content: Text( // content: Text(
msg, // msg,
textAlign: TextAlign.center, // textAlign: TextAlign.center,
style: const TextStyle(color: Colors.black), // style: const TextStyle(color: Colors.black),
), // ),
icon: const Icon( // icon: const Icon(
Icons.warning, // Icons.warning,
color: Colors.red, // color: Colors.red,
), // ),
); // );
}, // },
); // );
}) // })
: null, // : null,
), // ),
); // );
}, // },
).then((value) { // ).then((value) {
accountInfo = value.accountInfo; // accountInfo = value.accountInfo;
_setLoading(false); // _setLoading(false);
}); // });
} }
@override @override
@ -144,139 +144,139 @@ class _DashboardPageState extends State<DashboardPage> {
], ],
), ),
), ),
if (widget.client.session.accessToken != null) // if (widget.client.session.accessToken != null)
TextButton( // TextButton(
onPressed: () { // onPressed: () {
widget.client.session.accessToken = null; // widget.client.session.accessToken = null;
widget.client.session // widget.client.session
.removeSession(widget.client.session.sessionId!); // .removeSession(widget.client.session.sessionId!);
Navigator.of(context).pushAndRemoveUntil( // Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute( // MaterialPageRoute(
builder: (context) => // builder: (context) =>
StartPage(client: widget.client), // StartPage(client: widget.client),
), // ),
(route) => false); // (route) => false);
}, // },
child: const Row( // child: const Row(
children: [ // children: [
Text( // Text(
'Log out', // 'Log out',
style: TextStyle(fontSize: 20), // style: TextStyle(fontSize: 20),
), // ),
Spacer(), // Spacer(),
Icon( // Icon(
Icons.logout, // Icons.logout,
color: Colors.white, // color: Colors.white,
), // ),
], // ],
), // ),
), // ),
const SizedBox( const SizedBox(
height: 250, height: 250,
) )
], ],
); );
}), }),
bottomNavigationBar: Builder( // bottomNavigationBar: Builder(
builder: (context) { // builder: (context) {
return BottomBar( // return BottomBar(
children: widget.client.session.accessToken != null // children: widget.client.session.accessToken != null
? [ // ? [
BottomNavigationBarItem( // BottomNavigationBarItem(
backgroundColor: Colors.white, // backgroundColor: Colors.white,
label: 'Personen', // label: 'Personen',
icon: Column( // icon: Column(
children: [ // children: [
IconButton( // IconButton(
onPressed: () => // onPressed: () =>
Scaffold.of(context).openDrawer(), // Scaffold.of(context).openDrawer(),
icon: const Icon( // icon: const Icon(
Icons.group, // Icons.group,
color: Colors.white, // color: Colors.white,
), // ),
), // ),
const Text( // const Text(
'Personen', // 'Personen',
style: TextStyle( // style: TextStyle(
color: Colors.white, // color: Colors.white,
fontSize: 16, // fontSize: 16,
), // ),
) // )
], // ],
), // ),
), // ),
BottomNavigationBarItem( // BottomNavigationBarItem(
backgroundColor: Colors.white, // backgroundColor: Colors.white,
label: 'Home', // label: 'Home',
icon: Column( // icon: Column(
children: [ // children: [
IconButton( // IconButton(
onPressed: () { // onPressed: () {
Navigator.of(context).push( // Navigator.of(context).push(
MaterialPageRoute( // MaterialPageRoute(
builder: (context) => StartPage( // builder: (context) => StartPage(
client: widget.client, // client: widget.client,
), // ),
), // ),
); // );
}, // },
icon: const Icon( // icon: const Icon(
Icons.home, // Icons.home,
color: Colors.white, // color: Colors.white,
), // ),
), // ),
const Text( // const Text(
'Home', // 'Home',
style: TextStyle( // style: TextStyle(
color: Colors.white, // color: Colors.white,
fontSize: 16, // fontSize: 16,
), // ),
) // )
], // ],
), // ),
), // ),
BottomNavigationBarItem( // BottomNavigationBarItem(
backgroundColor: Colors.white, // backgroundColor: Colors.white,
label: 'Menu', // label: 'Menu',
icon: IconButton( // icon: IconButton(
onPressed: () { // onPressed: () {
Scaffold.of(context).openDrawer(); // Scaffold.of(context).openDrawer();
}, // },
icon: const Icon( // icon: const Icon(
Icons.menu, // Icons.menu,
color: Colors.white, // color: Colors.white,
), // ),
), // ),
) // )
] // ]
: [ // : [
BottomNavigationBarItem( // BottomNavigationBarItem(
label: 'back', // label: 'back',
backgroundColor: Colors.white, // backgroundColor: Colors.white,
icon: IconButton( // icon: IconButton(
onPressed: () {}, // onPressed: () {},
icon: const Icon( // icon: const Icon(
Icons.arrow_back, // Icons.arrow_back,
color: Colors.white, // color: Colors.white,
), // ),
), // ),
), // ),
BottomNavigationBarItem( // BottomNavigationBarItem(
backgroundColor: Colors.white, // backgroundColor: Colors.white,
label: 'Menu', // label: 'Menu',
icon: IconButton( // icon: IconButton(
onPressed: () => Scaffold.of(context).openDrawer(), // onPressed: () => Scaffold.of(context).openDrawer(),
icon: const Icon( // icon: const Icon(
Icons.menu, // Icons.menu,
color: Colors.white, // color: Colors.white,
), // ),
), // ),
), // ),
], // ],
); // );
}, // },
), // ),
body: !_loading body: !_loading
? Background( ? Background(
child: Center( child: Center(

View File

@ -1,4 +1,5 @@
import 'package:app/gapi/client.dart'; import 'package:app/gapi/client.dart';
import 'package:app/model/services/backend_service.dart';
import 'package:app/pages/start_page.dart'; import 'package:app/pages/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';
@ -12,11 +13,11 @@ import 'package:grpc/grpc.dart';
class LoginPage extends StatefulWidget { class LoginPage extends StatefulWidget {
const LoginPage({ const LoginPage({
super.key, super.key,
required this.client, // required this.client,
// required this.onChangePage, // required this.onChangePage,
}); });
final GClient client; // final GClient client;
// void Function(Pages page) onChangePage; // void Function(Pages page) onChangePage;
@override @override
@ -87,7 +88,7 @@ class _LoginPageState extends State<LoginPage> {
label: 'back', label: 'back',
backgroundColor: Colors.white, backgroundColor: Colors.white,
icon: IconButton( icon: IconButton(
onPressed: () => Navigator.of(context).pop(widget.client), onPressed: () => Navigator.of(context).pop(),
icon: const Icon( icon: const Icon(
Icons.arrow_back, Icons.arrow_back,
color: Colors.white, color: Colors.white,
@ -250,73 +251,23 @@ class _LoginPageState extends State<LoginPage> {
if (_formKey.currentState!.validate()) { if (_formKey.currentState!.validate()) {
// final navigator = Navigator.of(context); // final navigator = Navigator.of(context);
_setLoading(true); _setLoading(true);
widget.client BackendService.login(
.login(
email: mailController.text, email: mailController.text,
password: passwordController.text, password: passwordController.text,
onError: ({GrpcError? error}) { ).then(
_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(
(r) { (r) {
if (r.accessToken != '') { if (r) {
Navigator.pushAndRemoveUntil( Navigator.pop(context);
context, Navigator.pop(context);
MaterialPageRoute( // Navigator.pushAndRemoveUntil(
builder: (ctx) => StartPage( // context,
client: widget.client, // MaterialPageRoute(
), // builder: (ctx) => const StartPage(
), // // client: widget.client,
(ctx) => false, // ),
); // ),
// (ctx) => false,
// );
// widget.onChangePage( // widget.onChangePage(
// Pages.dashboard, // Pages.dashboard,
// ); // );

View File

@ -10,10 +10,10 @@ import 'package:grpc/grpc.dart';
class RegisterPage extends StatefulWidget { class RegisterPage extends StatefulWidget {
const RegisterPage({ const RegisterPage({
super.key, super.key,
required this.client, // required this.client,
}); });
final GClient client; // final GClient client;
@override @override
State<RegisterPage> createState() => _RegisterPageState(); State<RegisterPage> createState() => _RegisterPageState();
@ -112,7 +112,7 @@ class _RegisterPageState extends State<RegisterPage> {
label: 'back', label: 'back',
backgroundColor: Colors.white, backgroundColor: Colors.white,
icon: IconButton( icon: IconButton(
onPressed: () => Navigator.of(context).pop(widget.client), onPressed: () => Navigator.of(context).pop(),
icon: const Icon( icon: const Icon(
Icons.arrow_back, Icons.arrow_back,
color: Colors.white, color: Colors.white,
@ -209,143 +209,7 @@ class _RegisterPageState extends State<RegisterPage> {
ElevatedButton( ElevatedButton(
onPressed: () { onPressed: () {
if (_formKey.currentState!.validate()) { if (_formKey.currentState!.validate()) {
_setLoading(true); // _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)) child: const Icon(Icons.login))

View File

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

@ -195,6 +195,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.9.1" version: "1.9.1"
nested:
dependency: transitive
description:
name: nested
sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
path: path:
dependency: "direct main" dependency: "direct main"
description: description:
@ -219,6 +227,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.0" 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: sky_engine:
dependency: transitive dependency: transitive
description: flutter description: flutter

View File

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