From c4da7bea27db9e0727255a77eb771d225076559f Mon Sep 17 00:00:00 2001 From: itsscb Date: Tue, 7 Nov 2023 00:22:11 +0100 Subject: [PATCH] ft/adds toasts to api calls --- frontend/app/lib/data/database.dart | 3 +- frontend/app/lib/main.dart | 4 +- frontend/app/lib/model/apis/api_response.dart | 2 +- .../lib/model/services/backend_service.dart | 53 ++- .../app/lib/model/view_model/account_vm.dart | 42 +-- .../app/lib/model/view_model/base_vm.dart | 254 ++++++++++++++ .../app/lib/model/view_model/persons_vm.dart | 38 +- frontend/app/lib/pages/home_page.dart | 326 +++++++++--------- frontend/app/lib/pages/login_overlay.dart | 298 +++++++++------- frontend/app/lib/pages/persons_page.dart | 196 ++++++----- frontend/app/lib/util/colors.dart | 11 + .../app/lib/widgets/bottom_navigation.dart | 1 + .../lib/widgets/bottom_navigation_item.dart | 1 + frontend/app/lib/widgets/drawer.dart | 5 +- .../app/lib/widgets/side_drawer_item.dart | 2 +- 15 files changed, 818 insertions(+), 418 deletions(-) create mode 100644 frontend/app/lib/model/view_model/base_vm.dart create mode 100644 frontend/app/lib/util/colors.dart diff --git a/frontend/app/lib/data/database.dart b/frontend/app/lib/data/database.dart index 1ae9a7f..42b37c8 100644 --- a/frontend/app/lib/data/database.dart +++ b/frontend/app/lib/data/database.dart @@ -1,5 +1,4 @@ import 'dart:async'; -import 'package:app/pb/session.pbjson.dart'; import 'package:fixnum/fixnum.dart'; import 'package:app/pb/google/protobuf/timestamp.pb.dart'; @@ -167,7 +166,7 @@ class Session { final db = await database; final List> maps = await db.query('sessions'); - print(maps); + // print(maps); final List sessions = List.generate( maps.length, (i) { diff --git a/frontend/app/lib/main.dart b/frontend/app/lib/main.dart index 14f789a..6b36af7 100644 --- a/frontend/app/lib/main.dart +++ b/frontend/app/lib/main.dart @@ -41,7 +41,9 @@ void main() async { backgroundColor: Colors.black, foregroundColor: Colors.white, )), - home: const HomePage(), + home: HomePage( + loggedOut: false, + ), ), ); } diff --git a/frontend/app/lib/model/apis/api_response.dart b/frontend/app/lib/model/apis/api_response.dart index 764c500..6300f64 100644 --- a/frontend/app/lib/model/apis/api_response.dart +++ b/frontend/app/lib/model/apis/api_response.dart @@ -5,7 +5,7 @@ class ApiResponse { ApiResponse.initial(this.message) : status = Status.INITIAL; ApiResponse.loading(this.message) : status = Status.LOADING; - ApiResponse.completed(this.data, this.message) : status = Status.COMPLETED; + ApiResponse.completed(this.data) : status = Status.COMPLETED; ApiResponse.error(this.message) : status = Status.ERROR; @override diff --git a/frontend/app/lib/model/services/backend_service.dart b/frontend/app/lib/model/services/backend_service.dart index 669bc3e..af38df8 100644 --- a/frontend/app/lib/model/services/backend_service.dart +++ b/frontend/app/lib/model/services/backend_service.dart @@ -3,9 +3,11 @@ 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'; @@ -15,7 +17,6 @@ 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() { @@ -138,17 +139,16 @@ class BackendService { } try { final GetAccountResponse response = await _client.getAccount( - GetAccountRequest(id: session!.accountId), + 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(); - // } + if (err.code == 16) { + throw UnauthorizedException(err.message); + } throw FetchDataException(err.message); } catch (err) { throw InternalException(err.toString()); @@ -207,6 +207,47 @@ class BackendService { } } + Future 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> listPersons() async { Session session = await Session.session; if (session.accessTokenExpiresAt == null) { diff --git a/frontend/app/lib/model/view_model/account_vm.dart b/frontend/app/lib/model/view_model/account_vm.dart index 13356fa..fb3a998 100644 --- a/frontend/app/lib/model/view_model/account_vm.dart +++ b/frontend/app/lib/model/view_model/account_vm.dart @@ -1,9 +1,9 @@ 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'; -import 'package:flutter/material.dart'; -class AccountViewModel with ChangeNotifier { +class AccountViewModel extends BaseViewModel { AccountViewModel() { _init(); } @@ -21,36 +21,12 @@ class AccountViewModel with ChangeNotifier { } 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 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 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(); + super.init(); + // try { + // _apiResponse = ApiResponse.completed(await _service.getAccount()); + // } catch (e) { + // _apiResponse = ApiResponse.error(e.toString()); + // } + // notifyListeners(); } } diff --git a/frontend/app/lib/model/view_model/base_vm.dart b/frontend/app/lib/model/view_model/base_vm.dart new file mode 100644 index 0000000..7745a8e --- /dev/null +++ b/frontend/app/lib/model/view_model/base_vm.dart @@ -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 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 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 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 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 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; + } +} diff --git a/frontend/app/lib/model/view_model/persons_vm.dart b/frontend/app/lib/model/view_model/persons_vm.dart index 40e4606..a8ed578 100644 --- a/frontend/app/lib/model/view_model/persons_vm.dart +++ b/frontend/app/lib/model/view_model/persons_vm.dart @@ -1,6 +1,5 @@ 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:app/pb/person.pb.dart'; import 'package:flutter/material.dart'; @@ -16,14 +15,43 @@ class PersonsViewModel with ChangeNotifier { return _apiResponse; } - void listPersons() async { - _apiResponse = ApiResponse.loading('Bereite alles vor'); + Future> listPersons() async { + List persons = []; + _apiResponse = ApiResponse.loading('Lade Daten'); try { - _apiResponse = - ApiResponse.completed(await _service.listPersons(), 'done'); + persons = await _service.listPersons(); + _apiResponse = ApiResponse.completed(persons); } catch (e) { _apiResponse = ApiResponse.error(e.toString()); } notifyListeners(); + return persons; + } + + Future 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; } } diff --git a/frontend/app/lib/pages/home_page.dart b/frontend/app/lib/pages/home_page.dart index c2eb137..7b62f12 100644 --- a/frontend/app/lib/pages/home_page.dart +++ b/frontend/app/lib/pages/home_page.dart @@ -1,4 +1,3 @@ -import 'package:app/model/apis/api_response.dart'; import 'package:app/model/services/backend_service.dart'; import 'package:app/model/view_model/account_vm.dart'; import 'package:app/pages/login_overlay.dart'; @@ -11,8 +10,11 @@ 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 { - const HomePage({super.key}); + HomePage({super.key, required this.loggedOut}); + + bool loggedOut; @override State createState() => _HomePageState(); @@ -25,182 +27,192 @@ class _HomePageState extends State { _init(); } + AccountViewModel vm = AccountViewModel(); void _init() async { - _setLoading(true); - _loggedin = await BackendService.isLoggedIn; + // _setLoading(true); + // _setLoading(widget.loggedOut); + // _loading = widget.loggedOut; + // _loggedin = await BackendService.isLoggedIn; // if (!_loggedin) { // await BackendService.logout(); - // Navigator.of(context).pushAndRemoveUntil( - // MaterialPageRoute(builder: (builder) => HomePage()), + // 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; }); } - void _checkResponse(ApiResponse response) async { - print('${response.message}'); - if (response.status == Status.ERROR && - response.message!.contains('unauthorized')) { - await BackendService.logout(); - Navigator.of(context).pushAndRemoveUntil( - MaterialPageRoute(builder: (builder) => const HomePage()), - (route) => false); - } - } - bool _loading = true; bool _loggedin = false; @override Widget build(BuildContext context) { return Background( - child: ChangeNotifierProvider( - create: (context) => AccountViewModel(), - child: Consumer( - builder: (context, value, child) { - _checkResponse(value.response); - return 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 && value.response.data != null) - SideDrawerItem( - onPressed: () async { - setState(() { - _loading = true; - }); - await BackendService.logout(); - // ignore: use_build_context_synchronously - Navigator.of(context).pushAndRemoveUntil( - MaterialPageRoute( - builder: (builder) => const HomePage()), - (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 { - final bool res = await showLogin(context); - setState(() { - _loggedin = res; - }); - }, - icon: Icons.login, - color: Colors.white, - label: 'Login', - ), - ] else - BottomNavigationItem( - onPressed: () { - Navigator.of(context).push(MaterialPageRoute( - builder: (builder) => const PersonsPage())); - }, - 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, 40, 16, 16), - child: Center( - child: _loading - ? const CircularProgressIndicator( - color: Colors.grey, - ) - : Column( - children: [ - Image.asset( - 'lib/assets/logo_300x200.png', + 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( + create: (context) => AccountViewModel(), + child: + Consumer(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, ), - 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, - ), - ), - ], - ), - ), - ), - ); - }, + ), + ], + ); + })), ), ), - ); + )); } } diff --git a/frontend/app/lib/pages/login_overlay.dart b/frontend/app/lib/pages/login_overlay.dart index 927dc46..ef927a5 100644 --- a/frontend/app/lib/pages/login_overlay.dart +++ b/frontend/app/lib/pages/login_overlay.dart @@ -1,7 +1,12 @@ -import 'package:app/model/services/backend_service.dart'; +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 showLogin(BuildContext context, {bool registration = false}) async { @@ -9,15 +14,17 @@ Future showLogin(BuildContext context, final mailController = TextEditingController(); final passwordController = TextEditingController(); + BaseViewModel vm = BaseViewModel(); bool submitted = false; bool loggedin = false; - void login() { + void login(BuildContext context) { if (formKey.currentState!.validate()) { submitted = true; - BackendService.login( - email: mailController.text, - password: passwordController.text, - ).then( + FocusScope.of(context).unfocus(); + vm + .login(context, + email: mailController.text, password: passwordController.text) + .then( (r) { if (r) { loggedin = r; @@ -25,16 +32,21 @@ Future showLogin(BuildContext context, } }, ); + passwordController.clear(); + submitted = false; } } - void register() { + void register(BuildContext context) { if (formKey.currentState!.validate()) { submitted = true; - BackendService.createAccount( + vm + .createAccount( + context, email: mailController.text, password: passwordController.text, - ).then( + ) + .then( (r) { if (r) { loggedin = r; @@ -52,120 +64,174 @@ Future showLogin(BuildContext context, context: context, builder: (builder) { return Background( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const SizedBox( - height: 50, - ), - const Image( - width: 180, - image: AssetImage( - 'lib/assets/logo_300x200.png', + child: Scaffold( + drawer: SideDrawer( + children: [ + const Spacer( + flex: 3, ), - ), - const SizedBox( - height: 30, - ), - Text( - registration ? 'Registrieren' : 'Login', - style: const TextStyle( - fontFamily: 'sans-serif', - fontSize: 24, - height: 1.6, - fontWeight: FontWeight.normal, - letterSpacing: 6, + SideDrawerItem( + onPressed: () {}, + icon: Icons.question_answer, + color: Colors.white, + label: 'About', ), - ), - Form( - key: formKey, - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const SizedBox( - height: 40, + 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', ), - 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; - }, + ), + const SizedBox( + height: 30, + ), + Text( + registration ? 'Registrieren' : 'Login', + style: const TextStyle( + fontFamily: 'sans-serif', + fontSize: 24, + height: 1.6, + fontWeight: FontWeight.normal, + letterSpacing: 6, ), - 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 - ? () { - Navigator.pop(context); + ), + ChangeNotifierProvider( + create: (context) => BaseViewModel(), + child: Consumer( + 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'; } - : null, - child: const Icon(Icons.arrow_back), + 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), + ), + ], + ) + ], ), - ElevatedButton( - onPressed: !submitted - ? !registration - ? login - : register - : null, - child: const Icon(Icons.login), - ), - ], - ) - ], - ), + ), + ), + ), + const Spacer(), + ], ), - const Spacer(), - ], + ), ), ); }); diff --git a/frontend/app/lib/pages/persons_page.dart b/frontend/app/lib/pages/persons_page.dart index 746dfad..d863dec 100644 --- a/frontend/app/lib/pages/persons_page.dart +++ b/frontend/app/lib/pages/persons_page.dart @@ -42,13 +42,23 @@ class _PersonsPageState extends State { response.message!.contains('unauthenticated')) { BackendService.logout(); Navigator.of(context).pushAndRemoveUntil( - MaterialPageRoute(builder: (builder) => const HomePage()), + MaterialPageRoute( + builder: (builder) => HomePage( + loggedOut: true, + )), (route) => false); } } bool _loading = true; bool _loggedin = false; + List persons = []; + + PersonsViewModel vm = PersonsViewModel(); + + void listPersons(BuildContext context) async { + persons = await vm.listPersons(); + } List _personsList(List persons) { final List list = []; @@ -67,106 +77,106 @@ class _PersonsPageState extends State { @override Widget build(BuildContext context) { return Background( - child: ChangeNotifierProvider( - create: (context) => PersonsViewModel(), - child: Consumer( - builder: (context, value, child) { - _checkResponse(value.response); - return Scaffold( - floatingActionButtonLocation: - FloatingActionButtonLocation.centerFloat, - floatingActionButton: FloatingActionButton( - onPressed: () {}, - child: const Icon(Icons.add), + 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', ), - 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 || value.response.data != null) - SideDrawerItem( - onPressed: () async { - setState(() { - _loading = true; - }); - await BackendService.logout(); - // ignore: use_build_context_synchronously - Navigator.of(context).pushAndRemoveUntil( - MaterialPageRoute( - builder: (builder) => const HomePage()), - (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: _loading + ], + ), + 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( + create: (context) => PersonsViewModel(), + child: Consumer( + 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( - (value.response.data as List))) + ? ListView(children: _personsList(persons)) : const Text('Noch keine Personen angelegt') - : const Text('Lade Daten...'), - ), + : const Text('Lade Daten...'); + }, ), - ); - }, + ), + ), ), ), ); diff --git a/frontend/app/lib/util/colors.dart b/frontend/app/lib/util/colors.dart new file mode 100644 index 0000000..5c8d297 --- /dev/null +++ b/frontend/app/lib/util/colors.dart @@ -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); + } +} diff --git a/frontend/app/lib/widgets/bottom_navigation.dart b/frontend/app/lib/widgets/bottom_navigation.dart index 25e74b9..c3f6519 100644 --- a/frontend/app/lib/widgets/bottom_navigation.dart +++ b/frontend/app/lib/widgets/bottom_navigation.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; +// ignore: must_be_immutable class BottomNavigation extends StatelessWidget { BottomNavigation({ super.key, diff --git a/frontend/app/lib/widgets/bottom_navigation_item.dart b/frontend/app/lib/widgets/bottom_navigation_item.dart index 72f46c3..c6d6be4 100644 --- a/frontend/app/lib/widgets/bottom_navigation_item.dart +++ b/frontend/app/lib/widgets/bottom_navigation_item.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; +// ignore: must_be_immutable class BottomNavigationItem extends StatelessWidget { BottomNavigationItem({ super.key, diff --git a/frontend/app/lib/widgets/drawer.dart b/frontend/app/lib/widgets/drawer.dart index 5ec955b..47343bb 100644 --- a/frontend/app/lib/widgets/drawer.dart +++ b/frontend/app/lib/widgets/drawer.dart @@ -1,13 +1,12 @@ -import 'package:app/pages/home_page.dart'; -import 'package:app/widgets/side_drawer_item.dart'; import 'package:flutter/material.dart'; +// ignore: must_be_immutable class SideDrawer extends StatelessWidget { SideDrawer({super.key, required this.children, this.backgroundColor}) { backgroundColor ??= Colors.black; } - List children; + final List children; Color? backgroundColor; @override diff --git a/frontend/app/lib/widgets/side_drawer_item.dart b/frontend/app/lib/widgets/side_drawer_item.dart index 35668a4..b12e882 100644 --- a/frontend/app/lib/widgets/side_drawer_item.dart +++ b/frontend/app/lib/widgets/side_drawer_item.dart @@ -1,6 +1,6 @@ -import 'package:app/pages/home_page.dart'; import 'package:flutter/material.dart'; +// ignore: must_be_immutable class SideDrawerItem extends StatelessWidget { SideDrawerItem({ super.key,