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/person.pb.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_info.pb.dart';
|
||||
import 'package:app/pb/rpc_get_person.pb.dart';
|
||||
import 'package:app/pb/rpc_list_persons.pb.dart';
|
||||
import 'package:app/pb/rpc_login.pb.dart';
|
||||
import 'package:app/pb/rpc_refresh_token.pb.dart';
|
||||
import 'package:app/pb/service_df.pbgrpc.dart';
|
||||
@ -87,20 +89,48 @@ class BackendService {
|
||||
}
|
||||
|
||||
if (session.accessTokenExpiresAt == null) {
|
||||
await logout();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (session.refreshTokenExpiresAt == null) {
|
||||
await logout();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (session.refreshTokenExpiresAt!.toDateTime().isBefore(DateTime.now())) {
|
||||
await logout();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (session.accessTokenExpiresAt!.toDateTime().isBefore(DateTime.now())) {
|
||||
Session s = await BackendService.refreshToken(session);
|
||||
if (s == session) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static Future<bool> createAccount(
|
||||
{required String email, required String password}) async {
|
||||
try {
|
||||
await BackendService.client.createAccount(CreateAccountRequest(
|
||||
email: email,
|
||||
password: password,
|
||||
));
|
||||
|
||||
return await login(email: email, password: password);
|
||||
} on SocketException {
|
||||
throw FetchDataException('Keine Internet Verbindung');
|
||||
} on GrpcError catch (err) {
|
||||
throw FetchDataException(err.message);
|
||||
} catch (err) {
|
||||
throw InternalException(err.toString());
|
||||
}
|
||||
}
|
||||
|
||||
Future<Account> getAccount() async {
|
||||
Session? session = await _isLoggedIn();
|
||||
if (session == null) {
|
||||
@ -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 {
|
||||
// if (_session.accessToken == null) {
|
||||
// refreshToken();
|
||||
@ -221,10 +279,11 @@ class BackendService {
|
||||
}
|
||||
}
|
||||
|
||||
Future<Session> refreshToken(Session session) async {
|
||||
static Future<Session> refreshToken(Session session) async {
|
||||
try {
|
||||
final RefreshTokenResponse response = await _client.refreshToken(
|
||||
RefreshTokenRequest(refreshToken: session.refreshToken));
|
||||
final RefreshTokenResponse response = await BackendService.client
|
||||
.refreshToken(
|
||||
RefreshTokenRequest(refreshToken: session.refreshToken));
|
||||
session.accessToken = response.accessToken;
|
||||
session.accessTokenExpiresAt = response.accessTokenExpiresAt;
|
||||
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/view_model/account_vm.dart';
|
||||
import 'package:app/pages/login_overlay.dart';
|
||||
import 'package:app/pages/persons_page.dart';
|
||||
import 'package:app/widgets/background.dart';
|
||||
import 'package:app/widgets/bottom_navigation.dart';
|
||||
import 'package:app/widgets/bottom_navigation_item.dart';
|
||||
@ -28,6 +28,12 @@ class _HomePageState extends State<HomePage> {
|
||||
void _init() async {
|
||||
_setLoading(true);
|
||||
_loggedin = await BackendService.isLoggedIn;
|
||||
// if (!_loggedin) {
|
||||
// await BackendService.logout();
|
||||
// Navigator.of(context).pushAndRemoveUntil(
|
||||
// MaterialPageRoute(builder: (builder) => HomePage()),
|
||||
// (route) => 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 _loggedin = false;
|
||||
@override
|
||||
@ -45,114 +62,143 @@ class _HomePageState extends State<HomePage> {
|
||||
child: ChangeNotifierProvider<AccountViewModel>(
|
||||
create: (context) => AccountViewModel(),
|
||||
child: Consumer<AccountViewModel>(
|
||||
builder: (context, value, 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 || value.response.data != null)
|
||||
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.logout,
|
||||
icon: Icons.question_answer,
|
||||
color: Colors.white,
|
||||
label: 'Logout',
|
||||
label: 'About',
|
||||
),
|
||||
],
|
||||
),
|
||||
bottomNavigationBar: BottomNavigation(
|
||||
children: [
|
||||
if (!_loggedin) ...[
|
||||
BottomNavigationItem(
|
||||
SideDrawerItem(
|
||||
onPressed: () {},
|
||||
icon: Icons.person_add_alt,
|
||||
icon: Icons.privacy_tip,
|
||||
color: Colors.white,
|
||||
label: 'Registrieren',
|
||||
label: 'Datenschutz',
|
||||
),
|
||||
BottomNavigationItem(
|
||||
onPressed: () async {
|
||||
_loggedin = await showLogin(context);
|
||||
},
|
||||
icon: Icons.login,
|
||||
color: Colors.white,
|
||||
label: 'Login',
|
||||
),
|
||||
] else
|
||||
BottomNavigationItem(
|
||||
SideDrawerItem(
|
||||
onPressed: () {},
|
||||
icon: Icons.person_search,
|
||||
icon: Icons.apartment,
|
||||
color: Colors.white,
|
||||
label: 'Personen',
|
||||
label: 'Impressum',
|
||||
),
|
||||
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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
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',
|
||||
),
|
||||
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/widgets/background.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 mailController = 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(
|
||||
useSafeArea: true,
|
||||
isScrollControlled: true,
|
||||
@ -48,9 +67,9 @@ Future<bool> showLogin(BuildContext context) async {
|
||||
const SizedBox(
|
||||
height: 30,
|
||||
),
|
||||
const Text(
|
||||
'Login',
|
||||
style: TextStyle(
|
||||
Text(
|
||||
registration ? 'Registrieren' : 'Login',
|
||||
style: const TextStyle(
|
||||
fontFamily: 'sans-serif',
|
||||
fontSize: 24,
|
||||
height: 1.6,
|
||||
@ -68,6 +87,12 @@ Future<bool> showLogin(BuildContext context) async {
|
||||
height: 40,
|
||||
),
|
||||
TextFormField(
|
||||
autofocus: true,
|
||||
// inputFormatters: [
|
||||
// FilteringTextInputFormatter.allow(
|
||||
// emailRegExp,
|
||||
// ),
|
||||
// ],
|
||||
controller: mailController,
|
||||
decoration: const InputDecoration(
|
||||
fillColor: Color.fromARGB(30, 255, 255, 255),
|
||||
@ -79,7 +104,7 @@ Future<bool> showLogin(BuildContext context) async {
|
||||
),
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
if (value == null || !value.isValidEmail) {
|
||||
return 'Bitte eine gültige E-Mail Adresse eingeben';
|
||||
}
|
||||
return null;
|
||||
@ -89,6 +114,11 @@ Future<bool> showLogin(BuildContext context) async {
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
// inputFormatters: [
|
||||
// FilteringTextInputFormatter.allow(
|
||||
// passwordRegExp,
|
||||
// ),
|
||||
// ],
|
||||
controller: passwordController,
|
||||
decoration: const InputDecoration(
|
||||
fillColor: Color.fromARGB(30, 255, 255, 255),
|
||||
@ -101,7 +131,7 @@ Future<bool> showLogin(BuildContext context) async {
|
||||
keyboardType: TextInputType.visiblePassword,
|
||||
obscureText: true,
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
if (value == null || !value.isValidPassword) {
|
||||
return 'Bitte geben Sie Ihr Passwort ein';
|
||||
}
|
||||
return null;
|
||||
@ -122,7 +152,11 @@ Future<bool> showLogin(BuildContext context) async {
|
||||
child: const Icon(Icons.arrow_back),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: !submitted ? login : null,
|
||||
onPressed: !submitted
|
||||
? !registration
|
||||
? login
|
||||
: register
|
||||
: null,
|
||||
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