From 4887e0fd4dc1dbe2f843afe2e180bab76865d2f9 Mon Sep 17 00:00:00 2001 From: itsscb Date: Tue, 31 Oct 2023 15:50:58 +0100 Subject: [PATCH] ft/adds register_page and some improvements --- frontend/app/lib/gapi/client.dart | 66 ++++- frontend/app/lib/pages/dashboard_page.dart | 43 ++- frontend/app/lib/pages/login_page.dart | 19 +- frontend/app/lib/pages/register_page.dart | 301 +++++++++++++++++++++ frontend/app/lib/pages/start_page.dart | 80 +++--- 5 files changed, 446 insertions(+), 63 deletions(-) create mode 100644 frontend/app/lib/pages/register_page.dart diff --git a/frontend/app/lib/gapi/client.dart b/frontend/app/lib/gapi/client.dart index 3ec591a..eacc050 100644 --- a/frontend/app/lib/gapi/client.dart +++ b/frontend/app/lib/gapi/client.dart @@ -1,8 +1,8 @@ import 'package:app/data/database.dart'; -import 'package:fixnum/fixnum.dart'; import 'package:app/pb/rpc_create_account.pb.dart'; import 'package:app/pb/rpc_get_account_info.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:grpc/grpc.dart'; @@ -26,6 +26,7 @@ class GClient { if (sessions.isNotEmpty) { c.session = sessions[0]; } + print('ACCESS_TOKEN: ${c.session.accessToken}'); return c; } @@ -51,11 +52,21 @@ class GClient { } Future createAccount( - CreateAccountRequest request) async { + {required String email, + required String password, + required Function({GrpcError? error}) onError}) async { try { - final response = stub.createAccount(request); + final response = await stub.createAccount(CreateAccountRequest( + email: email, + password: password, + )); return response; - } catch (e) {} + } on GrpcError catch (e) { + onError(error: e); + print('GRPC ERROR: ${e.message}'); + } catch (e) { + print('ERROR: $e'); + } return CreateAccountResponse(); } @@ -94,23 +105,56 @@ class GClient { return response; } - Future getAccountInfo(GetAccountInfoRequest request, - {required Function onError}) async { + bool get isLoggedIn => + session.accessTokenExpiresAt != null && + session.accessTokenExpiresAt!.toDateTime().isAfter(DateTime.now()); + + Future resetSession() async { + if (session.sessionId != null) { + session.removeSession(session.sessionId!); + } + session = await Session.newSession; + } + + Future refreshToken() async { try { - final response = await stub.getAccountInfo( + final response = await stub.refreshToken( + RefreshTokenRequest( + refreshToken: session.refreshToken, + ), + ); + session.accessToken = response.accessToken; + session.insertSession(session); + return true; + } on GrpcError catch (e) { + print('caught grpc error: $e'); + } + return false; + } + + Future getAccountInfo(GetAccountInfoRequest request, + {required Function({String msg}) onError}) async { + GetAccountInfoResponse response = GetAccountInfoResponse(); + try { + print('SENDING REQ: $request WITH $metadata'); + response = await stub.getAccountInfo( request, options: CallOptions( - metadata: metadata, + metadata: {'Authorization': 'Bearer ${session.accessToken}'}, ), ); return response; } on GrpcError catch (e) { - print('caught error: ${e.message}'); - onError(); + print('caught grpc error: ${e.message} [${e.code}]'); + if (e.code == 16) { + onError(msg: 'Sitzung ist abgelaufen.\nBitte loggen Sie sich neu ein.'); + } else { + onError(msg: e.message != null ? e.message! : 'Interner Fehler'); + } } catch (e) { print('caught error: $e'); onError(); } - return GetAccountInfoResponse(); + return response; } } diff --git a/frontend/app/lib/pages/dashboard_page.dart b/frontend/app/lib/pages/dashboard_page.dart index 7279de4..6be86ba 100644 --- a/frontend/app/lib/pages/dashboard_page.dart +++ b/frontend/app/lib/pages/dashboard_page.dart @@ -39,10 +39,35 @@ class _DashboardPageState extends State { GetAccountInfoRequest( accountId: widget.client.session.accountId, ), - onError: () { - ScaffoldMessenger.of(context).showSnackBar(const SnackBar( - content: Text('AccountInfo konnte nicht geladen werden'), - )); + 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; @@ -126,7 +151,12 @@ class _DashboardPageState extends State { widget.client.session .removeSession(widget.client.session.sessionId!); - Navigator.of(context).pop(widget.client); + Navigator.of(context).pushAndRemoveUntil( + MaterialPageRoute( + builder: (context) => + StartPage(client: widget.client), + ), + (route) => false); }, child: const Row( children: [ @@ -225,8 +255,7 @@ class _DashboardPageState extends State { label: 'back', backgroundColor: Colors.white, icon: IconButton( - onPressed: () => - Navigator.of(context).pop(widget.client), + onPressed: () {}, icon: const Icon( Icons.arrow_back, color: Colors.white, diff --git a/frontend/app/lib/pages/login_page.dart b/frontend/app/lib/pages/login_page.dart index 26d7482..daf5e9b 100644 --- a/frontend/app/lib/pages/login_page.dart +++ b/frontend/app/lib/pages/login_page.dart @@ -10,11 +10,13 @@ import 'package:grpc/grpc.dart'; // GlobalKey scaffoldKey = GlobalKey(); class LoginPage extends StatefulWidget { - LoginPage({ + const LoginPage({ super.key, + required this.client, // required this.onChangePage, }); + final GClient client; // void Function(Pages page) onChangePage; @override @@ -32,13 +34,6 @@ class _LoginPageState extends State { _addBottomBarButtons(); } - void _bottomBarAction(int index) { - switch (bottombarButtons[index].label?.toLowerCase()) { - case 'back': - Navigator.of(context).pop(client); - } - } - void _addBottomBarButtons() { bottombarButtons.addAll([ const BottomNavigationBarItem( @@ -69,7 +64,7 @@ class _LoginPageState extends State { }); } - final GClient client = GClient(); + // final GClient client = GClient(); final _formKey = GlobalKey(); final mailController = TextEditingController(); @@ -92,7 +87,7 @@ class _LoginPageState extends State { label: 'back', backgroundColor: Colors.white, icon: IconButton( - onPressed: () => Navigator.of(context).pop(client), + onPressed: () => Navigator.of(context).pop(widget.client), icon: const Icon( Icons.arrow_back, color: Colors.white, @@ -255,7 +250,7 @@ class _LoginPageState extends State { if (_formKey.currentState!.validate()) { // final navigator = Navigator.of(context); _setLoading(true); - client + widget.client .login( email: mailController.text, password: passwordController.text, @@ -317,7 +312,7 @@ class _LoginPageState extends State { context, MaterialPageRoute( builder: (ctx) => StartPage( - client: client, + client: widget.client, ), ), (ctx) => false, diff --git a/frontend/app/lib/pages/register_page.dart b/frontend/app/lib/pages/register_page.dart new file mode 100644 index 0000000..923f72a --- /dev/null +++ b/frontend/app/lib/pages/register_page.dart @@ -0,0 +1,301 @@ +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 createState() => _RegisterPageState(); +} + +class _RegisterPageState extends State { + bool _loading = false; + final _formKey = GlobalKey(); + 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 != '') { + 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()), + ); + } +} diff --git a/frontend/app/lib/pages/start_page.dart b/frontend/app/lib/pages/start_page.dart index c4f76db..bc4578b 100644 --- a/frontend/app/lib/pages/start_page.dart +++ b/frontend/app/lib/pages/start_page.dart @@ -1,12 +1,14 @@ 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, @@ -23,16 +25,23 @@ class _StartPageState extends State { // List _selectedBottomBarButtons = bottomBarButtons; final List bottombarButtons = []; - void _init() async { - final c = await GClient.client; + // void _init() async { + // final c = await GClient.client; + // setState(() { + // widget.client = c; + // }); + // } + + void _updateClient(GClient c) { setState(() { + print('GOT CLIENT: $c'); widget.client = c; }); } @override void initState() { - _init(); + // _init(); super.initState(); } @@ -103,9 +112,9 @@ class _StartPageState extends State { TextButton( onPressed: () { setState(() { - widget.client?.session.accessToken = null; - widget.client?.session - .removeSession(widget.client!.session.sessionId!); + widget.client.session.accessToken = null; + widget.client.session + .removeSession(widget.client.session.sessionId!); }); }, child: const Row( @@ -130,7 +139,7 @@ class _StartPageState extends State { bottomNavigationBar: Builder(builder: (context) { return BottomBar( // onTap: (value) => _bottomBarAction(value), - children: widget.client?.session.accessToken != null + children: widget.client.session.accessToken != null ? [ BottomNavigationBarItem( backgroundColor: Colors.white, @@ -161,21 +170,29 @@ class _StartPageState extends State { children: [ IconButton( onPressed: () async { - // setState(() { - - GClient c = await Navigator.push( - context, - MaterialPageRoute( - builder: (context) => DashboardPage( - client: widget.client!, + if (!widget.client.isLoggedIn) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Sitzung ist abgelaufen.'), ), - ), - ); - print('Got Client back: $c'); - // }); - setState(() { - widget.client = c; - }); + ); + Navigator.pushAndRemoveUntil( + context, + MaterialPageRoute( + builder: (context) => + LoginPage(client: widget.client)), + (route) => false); + } else { + GClient c = await Navigator.push( + context, + MaterialPageRoute( + builder: (context) => DashboardPage( + client: widget.client, + ), + ), + ); + _updateClient(c); + } }, icon: const Icon( Icons.dashboard, @@ -217,7 +234,9 @@ class _StartPageState extends State { widget.client = await Navigator.push( context, MaterialPageRoute( - builder: (context) => LoginPage())); + builder: (context) => RegisterPage( + client: widget.client, + ))); }, icon: const Icon( Icons.login, @@ -241,10 +260,14 @@ class _StartPageState extends State { children: [ IconButton( onPressed: () async { - widget.client = await Navigator.push( + final c = await Navigator.push( context, MaterialPageRoute( - builder: (context) => LoginPage())); + builder: (context) => LoginPage( + client: widget.client, + ), + )); + _updateClient(c); }, icon: const Icon( Icons.login, @@ -299,15 +322,6 @@ class _StartPageState extends State { letterSpacing: 6, ), ), - TextButton( - onPressed: () async { - final s = await widget.client?.session.getSessions(); - print('SESSIONS: $s'); - }, - child: const Text( - "GET SESSIONS", - ), - ), ], ), ),