ft/adds auto logout and persons_page
This commit is contained in:
parent
ef7f6c093f
commit
604c2bdd27
@ -5,9 +5,11 @@ import 'package:app/pb/account.pb.dart';
|
|||||||
import 'package:app/pb/account_info.pb.dart';
|
import 'package:app/pb/account_info.pb.dart';
|
||||||
import 'package:app/pb/person.pb.dart';
|
import 'package:app/pb/person.pb.dart';
|
||||||
import 'package:app/data/database.dart';
|
import 'package:app/data/database.dart';
|
||||||
|
import 'package:app/pb/rpc_create_account.pb.dart';
|
||||||
import 'package:app/pb/rpc_get_account.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_account_info.pb.dart';
|
||||||
import 'package:app/pb/rpc_get_person.pb.dart';
|
import 'package:app/pb/rpc_get_person.pb.dart';
|
||||||
|
import 'package:app/pb/rpc_list_persons.pb.dart';
|
||||||
import 'package:app/pb/rpc_login.pb.dart';
|
import 'package:app/pb/rpc_login.pb.dart';
|
||||||
import 'package:app/pb/rpc_refresh_token.pb.dart';
|
import 'package:app/pb/rpc_refresh_token.pb.dart';
|
||||||
import 'package:app/pb/service_df.pbgrpc.dart';
|
import 'package:app/pb/service_df.pbgrpc.dart';
|
||||||
@ -87,20 +89,48 @@ class BackendService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (session.accessTokenExpiresAt == null) {
|
if (session.accessTokenExpiresAt == null) {
|
||||||
|
await logout();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (session.refreshTokenExpiresAt == null) {
|
if (session.refreshTokenExpiresAt == null) {
|
||||||
|
await logout();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (session.refreshTokenExpiresAt!.toDateTime().isBefore(DateTime.now())) {
|
if (session.refreshTokenExpiresAt!.toDateTime().isBefore(DateTime.now())) {
|
||||||
|
await logout();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (session.accessTokenExpiresAt!.toDateTime().isBefore(DateTime.now())) {
|
||||||
|
Session s = await BackendService.refreshToken(session);
|
||||||
|
if (s == session) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Future<bool> createAccount(
|
||||||
|
{required String email, required String password}) async {
|
||||||
|
try {
|
||||||
|
await BackendService.client.createAccount(CreateAccountRequest(
|
||||||
|
email: email,
|
||||||
|
password: password,
|
||||||
|
));
|
||||||
|
|
||||||
|
return await login(email: email, password: password);
|
||||||
|
} on SocketException {
|
||||||
|
throw FetchDataException('Keine Internet Verbindung');
|
||||||
|
} on GrpcError catch (err) {
|
||||||
|
throw FetchDataException(err.message);
|
||||||
|
} catch (err) {
|
||||||
|
throw InternalException(err.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<Account> getAccount() async {
|
Future<Account> getAccount() async {
|
||||||
Session? session = await _isLoggedIn();
|
Session? session = await _isLoggedIn();
|
||||||
if (session == null) {
|
if (session == null) {
|
||||||
@ -177,6 +207,34 @@ class BackendService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<List<Person>> listPersons() async {
|
||||||
|
Session session = await Session.session;
|
||||||
|
if (session.accessTokenExpiresAt == null) {
|
||||||
|
throw UnauthorizedException('Keine Siztung gefunden');
|
||||||
|
}
|
||||||
|
if (session.accessTokenExpiresAt!.toDateTime().isBefore(DateTime.now())) {
|
||||||
|
session = await refreshToken(session);
|
||||||
|
if (session.accessTokenExpiresAt == null) {
|
||||||
|
throw UnauthorizedException('Sitzung ist abgelaufen');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
final ListPersonsResponse response = await _client.listPersons(
|
||||||
|
ListPersonsRequest(
|
||||||
|
accountId: session.accountId,
|
||||||
|
),
|
||||||
|
options: CallOptions(
|
||||||
|
metadata: {'Authorization': 'Bearer ${session.accessToken}'}));
|
||||||
|
return response.persons;
|
||||||
|
} on SocketException {
|
||||||
|
throw FetchDataException('Keine Internet Verbindung');
|
||||||
|
} on GrpcError catch (err) {
|
||||||
|
throw FetchDataException(err.message);
|
||||||
|
} catch (err) {
|
||||||
|
throw InternalException(err.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Future<List<Person>> listPersons() async {
|
// Future<List<Person>> listPersons() async {
|
||||||
// if (_session.accessToken == null) {
|
// if (_session.accessToken == null) {
|
||||||
// refreshToken();
|
// refreshToken();
|
||||||
@ -221,10 +279,11 @@ class BackendService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Session> refreshToken(Session session) async {
|
static Future<Session> refreshToken(Session session) async {
|
||||||
try {
|
try {
|
||||||
final RefreshTokenResponse response = await _client.refreshToken(
|
final RefreshTokenResponse response = await BackendService.client
|
||||||
RefreshTokenRequest(refreshToken: session.refreshToken));
|
.refreshToken(
|
||||||
|
RefreshTokenRequest(refreshToken: session.refreshToken));
|
||||||
session.accessToken = response.accessToken;
|
session.accessToken = response.accessToken;
|
||||||
session.accessTokenExpiresAt = response.accessTokenExpiresAt;
|
session.accessTokenExpiresAt = response.accessTokenExpiresAt;
|
||||||
session = await Session.updateToken(session);
|
session = await Session.updateToken(session);
|
||||||
|
29
frontend/app/lib/model/view_model/persons_vm.dart
Normal file
29
frontend/app/lib/model/view_model/persons_vm.dart
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
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';
|
||||||
|
|
||||||
|
class PersonsViewModel with ChangeNotifier {
|
||||||
|
PersonsViewModel() {
|
||||||
|
listPersons();
|
||||||
|
}
|
||||||
|
ApiResponse _apiResponse = ApiResponse.initial('Keine Daten');
|
||||||
|
|
||||||
|
final BackendService _service = BackendService();
|
||||||
|
|
||||||
|
ApiResponse get response {
|
||||||
|
return _apiResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
void listPersons() async {
|
||||||
|
_apiResponse = ApiResponse.loading('Bereite alles vor');
|
||||||
|
try {
|
||||||
|
_apiResponse =
|
||||||
|
ApiResponse.completed(await _service.listPersons(), 'done');
|
||||||
|
} catch (e) {
|
||||||
|
_apiResponse = ApiResponse.error(e.toString());
|
||||||
|
}
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
}
|
@ -1,8 +1,8 @@
|
|||||||
import 'dart:io';
|
import 'package:app/model/apis/api_response.dart';
|
||||||
|
|
||||||
import 'package:app/model/services/backend_service.dart';
|
import 'package:app/model/services/backend_service.dart';
|
||||||
import 'package:app/model/view_model/account_vm.dart';
|
import 'package:app/model/view_model/account_vm.dart';
|
||||||
import 'package:app/pages/login_overlay.dart';
|
import 'package:app/pages/login_overlay.dart';
|
||||||
|
import 'package:app/pages/persons_page.dart';
|
||||||
import 'package:app/widgets/background.dart';
|
import 'package:app/widgets/background.dart';
|
||||||
import 'package:app/widgets/bottom_navigation.dart';
|
import 'package:app/widgets/bottom_navigation.dart';
|
||||||
import 'package:app/widgets/bottom_navigation_item.dart';
|
import 'package:app/widgets/bottom_navigation_item.dart';
|
||||||
@ -28,6 +28,12 @@ class _HomePageState extends State<HomePage> {
|
|||||||
void _init() async {
|
void _init() async {
|
||||||
_setLoading(true);
|
_setLoading(true);
|
||||||
_loggedin = await BackendService.isLoggedIn;
|
_loggedin = await BackendService.isLoggedIn;
|
||||||
|
// if (!_loggedin) {
|
||||||
|
// await BackendService.logout();
|
||||||
|
// Navigator.of(context).pushAndRemoveUntil(
|
||||||
|
// MaterialPageRoute(builder: (builder) => HomePage()),
|
||||||
|
// (route) => false);
|
||||||
|
// }
|
||||||
_setLoading(false);
|
_setLoading(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -37,6 +43,17 @@ class _HomePageState extends State<HomePage> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 _loading = true;
|
||||||
bool _loggedin = false;
|
bool _loggedin = false;
|
||||||
@override
|
@override
|
||||||
@ -45,114 +62,143 @@ class _HomePageState extends State<HomePage> {
|
|||||||
child: ChangeNotifierProvider<AccountViewModel>(
|
child: ChangeNotifierProvider<AccountViewModel>(
|
||||||
create: (context) => AccountViewModel(),
|
create: (context) => AccountViewModel(),
|
||||||
child: Consumer<AccountViewModel>(
|
child: Consumer<AccountViewModel>(
|
||||||
builder: (context, value, child) => Scaffold(
|
builder: (context, value, child) {
|
||||||
appBar: AppBar(
|
_checkResponse(value.response);
|
||||||
automaticallyImplyLeading: false,
|
return Scaffold(
|
||||||
// flexibleSpace: Image.asset(
|
appBar: AppBar(
|
||||||
// 'lib/assets/logo_300x200.png',
|
automaticallyImplyLeading: false,
|
||||||
// // height: 400,
|
// flexibleSpace: Image.asset(
|
||||||
// ),
|
// 'lib/assets/logo_300x200.png',
|
||||||
),
|
// // height: 400,
|
||||||
drawer: SideDrawer(
|
// ),
|
||||||
children: [
|
),
|
||||||
const Spacer(
|
drawer: SideDrawer(
|
||||||
flex: 3,
|
children: [
|
||||||
),
|
const Spacer(
|
||||||
SideDrawerItem(
|
flex: 3,
|
||||||
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(
|
SideDrawerItem(
|
||||||
onPressed: () {},
|
onPressed: () {},
|
||||||
icon: Icons.logout,
|
icon: Icons.question_answer,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
label: 'Logout',
|
label: 'About',
|
||||||
),
|
),
|
||||||
],
|
SideDrawerItem(
|
||||||
),
|
|
||||||
bottomNavigationBar: BottomNavigation(
|
|
||||||
children: [
|
|
||||||
if (!_loggedin) ...[
|
|
||||||
BottomNavigationItem(
|
|
||||||
onPressed: () {},
|
onPressed: () {},
|
||||||
icon: Icons.person_add_alt,
|
icon: Icons.privacy_tip,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
label: 'Registrieren',
|
label: 'Datenschutz',
|
||||||
),
|
),
|
||||||
BottomNavigationItem(
|
SideDrawerItem(
|
||||||
onPressed: () async {
|
|
||||||
_loggedin = await showLogin(context);
|
|
||||||
},
|
|
||||||
icon: Icons.login,
|
|
||||||
color: Colors.white,
|
|
||||||
label: 'Login',
|
|
||||||
),
|
|
||||||
] else
|
|
||||||
BottomNavigationItem(
|
|
||||||
onPressed: () {},
|
onPressed: () {},
|
||||||
icon: Icons.person_search,
|
icon: Icons.apartment,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
label: 'Personen',
|
label: 'Impressum',
|
||||||
),
|
),
|
||||||
BottomNavigationItem(
|
const Spacer(
|
||||||
onPressed: () {},
|
flex: 1,
|
||||||
icon: Icons.dashboard,
|
),
|
||||||
color: Colors.white,
|
if (_loggedin && value.response.data != null)
|
||||||
label: 'Dashboard',
|
SideDrawerItem(
|
||||||
),
|
onPressed: () async {
|
||||||
...[]
|
setState(() {
|
||||||
],
|
_loading = true;
|
||||||
),
|
});
|
||||||
body: Padding(
|
await BackendService.logout();
|
||||||
padding: const EdgeInsets.fromLTRB(16, 40, 16, 16),
|
// ignore: use_build_context_synchronously
|
||||||
child: Center(
|
Navigator.of(context).pushAndRemoveUntil(
|
||||||
child: _loading
|
MaterialPageRoute(
|
||||||
? const CircularProgressIndicator(
|
builder: (builder) => const HomePage()),
|
||||||
color: Colors.grey,
|
(route) => false);
|
||||||
)
|
setState(() {
|
||||||
: Column(
|
_loggedin = false;
|
||||||
children: [
|
_loading = false;
|
||||||
Image.asset(
|
});
|
||||||
'lib/assets/logo_300x200.png',
|
},
|
||||||
),
|
icon: Icons.logout,
|
||||||
const SizedBox(
|
color: Colors.white,
|
||||||
height: 40,
|
label: 'Logout',
|
||||||
),
|
),
|
||||||
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,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
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',
|
||||||
|
),
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
import 'package:app/model/services/backend_service.dart';
|
import 'package:app/model/services/backend_service.dart';
|
||||||
import 'package:app/widgets/background.dart';
|
import 'package:app/widgets/background.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:app/util/validation.dart';
|
||||||
|
|
||||||
Future<bool> showLogin(BuildContext context) async {
|
Future<bool> showLogin(BuildContext context,
|
||||||
|
{bool registration = false}) async {
|
||||||
final formKey = GlobalKey<FormState>();
|
final formKey = GlobalKey<FormState>();
|
||||||
final mailController = TextEditingController();
|
final mailController = TextEditingController();
|
||||||
final passwordController = TextEditingController();
|
final passwordController = TextEditingController();
|
||||||
@ -26,6 +28,23 @@ Future<bool> showLogin(BuildContext context) async {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void register() {
|
||||||
|
if (formKey.currentState!.validate()) {
|
||||||
|
submitted = true;
|
||||||
|
BackendService.createAccount(
|
||||||
|
email: mailController.text,
|
||||||
|
password: passwordController.text,
|
||||||
|
).then(
|
||||||
|
(r) {
|
||||||
|
if (r) {
|
||||||
|
loggedin = r;
|
||||||
|
Navigator.pop(context, true);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await showModalBottomSheet(
|
await showModalBottomSheet(
|
||||||
useSafeArea: true,
|
useSafeArea: true,
|
||||||
isScrollControlled: true,
|
isScrollControlled: true,
|
||||||
@ -48,9 +67,9 @@ Future<bool> showLogin(BuildContext context) async {
|
|||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 30,
|
height: 30,
|
||||||
),
|
),
|
||||||
const Text(
|
Text(
|
||||||
'Login',
|
registration ? 'Registrieren' : 'Login',
|
||||||
style: TextStyle(
|
style: const TextStyle(
|
||||||
fontFamily: 'sans-serif',
|
fontFamily: 'sans-serif',
|
||||||
fontSize: 24,
|
fontSize: 24,
|
||||||
height: 1.6,
|
height: 1.6,
|
||||||
@ -68,6 +87,12 @@ Future<bool> showLogin(BuildContext context) async {
|
|||||||
height: 40,
|
height: 40,
|
||||||
),
|
),
|
||||||
TextFormField(
|
TextFormField(
|
||||||
|
autofocus: true,
|
||||||
|
// inputFormatters: [
|
||||||
|
// FilteringTextInputFormatter.allow(
|
||||||
|
// emailRegExp,
|
||||||
|
// ),
|
||||||
|
// ],
|
||||||
controller: mailController,
|
controller: mailController,
|
||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(
|
||||||
fillColor: Color.fromARGB(30, 255, 255, 255),
|
fillColor: Color.fromARGB(30, 255, 255, 255),
|
||||||
@ -79,7 +104,7 @@ Future<bool> showLogin(BuildContext context) async {
|
|||||||
),
|
),
|
||||||
keyboardType: TextInputType.emailAddress,
|
keyboardType: TextInputType.emailAddress,
|
||||||
validator: (value) {
|
validator: (value) {
|
||||||
if (value == null || value.isEmpty) {
|
if (value == null || !value.isValidEmail) {
|
||||||
return 'Bitte eine gültige E-Mail Adresse eingeben';
|
return 'Bitte eine gültige E-Mail Adresse eingeben';
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@ -89,6 +114,11 @@ Future<bool> showLogin(BuildContext context) async {
|
|||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
),
|
),
|
||||||
|
// inputFormatters: [
|
||||||
|
// FilteringTextInputFormatter.allow(
|
||||||
|
// passwordRegExp,
|
||||||
|
// ),
|
||||||
|
// ],
|
||||||
controller: passwordController,
|
controller: passwordController,
|
||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(
|
||||||
fillColor: Color.fromARGB(30, 255, 255, 255),
|
fillColor: Color.fromARGB(30, 255, 255, 255),
|
||||||
@ -101,7 +131,7 @@ Future<bool> showLogin(BuildContext context) async {
|
|||||||
keyboardType: TextInputType.visiblePassword,
|
keyboardType: TextInputType.visiblePassword,
|
||||||
obscureText: true,
|
obscureText: true,
|
||||||
validator: (value) {
|
validator: (value) {
|
||||||
if (value == null || value.isEmpty) {
|
if (value == null || !value.isValidPassword) {
|
||||||
return 'Bitte geben Sie Ihr Passwort ein';
|
return 'Bitte geben Sie Ihr Passwort ein';
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@ -122,7 +152,11 @@ Future<bool> showLogin(BuildContext context) async {
|
|||||||
child: const Icon(Icons.arrow_back),
|
child: const Icon(Icons.arrow_back),
|
||||||
),
|
),
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: !submitted ? login : null,
|
onPressed: !submitted
|
||||||
|
? !registration
|
||||||
|
? login
|
||||||
|
: register
|
||||||
|
: null,
|
||||||
child: const Icon(Icons.login),
|
child: const Icon(Icons.login),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
174
frontend/app/lib/pages/persons_page.dart
Normal file
174
frontend/app/lib/pages/persons_page.dart
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
import 'package:app/model/apis/api_response.dart';
|
||||||
|
import 'package:app/model/services/backend_service.dart';
|
||||||
|
import 'package:app/model/view_model/persons_vm.dart';
|
||||||
|
import 'package:app/pages/home_page.dart';
|
||||||
|
import 'package:app/pb/person.pb.dart';
|
||||||
|
import 'package:app/widgets/background.dart';
|
||||||
|
import 'package:app/widgets/bottom_navigation.dart';
|
||||||
|
import 'package:app/widgets/bottom_navigation_item.dart';
|
||||||
|
import 'package:app/widgets/side_drawer.dart';
|
||||||
|
import 'package:app/widgets/side_drawer_item.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
class PersonsPage extends StatefulWidget {
|
||||||
|
const PersonsPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<PersonsPage> createState() => _PersonsPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PersonsPageState extends State<PersonsPage> {
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_init();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _init() async {
|
||||||
|
_setLoading(true);
|
||||||
|
_loggedin = await BackendService.isLoggedIn;
|
||||||
|
_setLoading(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _setLoading(bool loading) {
|
||||||
|
setState(() {
|
||||||
|
_loading = loading;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _checkResponse(ApiResponse response) {
|
||||||
|
if (response.status == Status.ERROR &&
|
||||||
|
response.message!.contains('unauthenticated')) {
|
||||||
|
BackendService.logout();
|
||||||
|
Navigator.of(context).pushAndRemoveUntil(
|
||||||
|
MaterialPageRoute(builder: (builder) => const HomePage()),
|
||||||
|
(route) => false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _loading = true;
|
||||||
|
bool _loggedin = false;
|
||||||
|
|
||||||
|
List<Widget> _personsList(List<Person> persons) {
|
||||||
|
final List<Widget> list = [];
|
||||||
|
for (var p in persons) {
|
||||||
|
list.add(Card(
|
||||||
|
color: Colors.black,
|
||||||
|
child: Text(
|
||||||
|
'${p.firstname} ${p.lastname}',
|
||||||
|
style: const TextStyle(color: Colors.white),
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Background(
|
||||||
|
child: ChangeNotifierProvider<PersonsViewModel>(
|
||||||
|
create: (context) => PersonsViewModel(),
|
||||||
|
child: Consumer<PersonsViewModel>(
|
||||||
|
builder: (context, value, child) {
|
||||||
|
_checkResponse(value.response);
|
||||||
|
return 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 || 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
|
||||||
|
? const CircularProgressIndicator(
|
||||||
|
color: Colors.grey,
|
||||||
|
)
|
||||||
|
: value.response.status == Status.COMPLETED
|
||||||
|
? value.response.data.length > 0
|
||||||
|
? ListView(
|
||||||
|
children: _personsList(
|
||||||
|
(value.response.data as List<Person>)))
|
||||||
|
: const Text('Noch keine Personen angelegt')
|
||||||
|
: const Text('Lade Daten...'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
27
frontend/app/lib/util/validation.dart
Normal file
27
frontend/app/lib/util/validation.dart
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
final emailRegExp = RegExp(r"^[a-zA-Z0-9.]+@[a-zA-Z0-9]+\.[a-zA-Z]+");
|
||||||
|
final nameRegExp =
|
||||||
|
RegExp(r"^\s*([A-Za-z]{1,}([\.,] |[-']| ))+[A-Za-z]+\.?\s*$");
|
||||||
|
final phoneRegExp = RegExp(r"^\+?0[0-9]{10}$");
|
||||||
|
final passwordRegExp = RegExp(r'^.+$');
|
||||||
|
|
||||||
|
extension valString on String {
|
||||||
|
bool get isValidEmail {
|
||||||
|
return emailRegExp.hasMatch(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get isValidName {
|
||||||
|
return nameRegExp.hasMatch(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get isValidPassword {
|
||||||
|
return passwordRegExp.hasMatch(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get isNotEmpty {
|
||||||
|
return this != trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get isValidPhone {
|
||||||
|
return phoneRegExp.hasMatch(this);
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user