ft/adds person_detail_page
This commit is contained in:
parent
e2d3720728
commit
6d48638ef1
@ -1,11 +1,15 @@
|
|||||||
import 'package:app/pages/home_page.dart';
|
import 'package:app/pages/home_page.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_localizations/flutter_localizations.dart'
|
||||||
|
show GlobalMaterialLocalizations;
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
runApp(
|
runApp(
|
||||||
MaterialApp(
|
MaterialApp(
|
||||||
|
localizationsDelegates: const [GlobalMaterialLocalizations.delegate],
|
||||||
|
supportedLocales: const [Locale('en'), Locale('de')],
|
||||||
theme: ThemeData().copyWith(
|
theme: ThemeData().copyWith(
|
||||||
colorScheme: const ColorScheme(
|
colorScheme: const ColorScheme(
|
||||||
brightness: Brightness.dark,
|
brightness: Brightness.dark,
|
||||||
|
@ -4,6 +4,7 @@ class AppException implements Exception {
|
|||||||
|
|
||||||
AppException([this._message, this._prefix]);
|
AppException([this._message, this._prefix]);
|
||||||
|
|
||||||
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return "$_prefix$_message";
|
return "$_prefix$_message";
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@ import 'package:app/pb/rpc_get_person.pb.dart';
|
|||||||
import 'package:app/pb/rpc_list_persons.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/rpc_update_person.pb.dart';
|
||||||
import 'package:app/pb/service_df.pbgrpc.dart';
|
import 'package:app/pb/service_df.pbgrpc.dart';
|
||||||
import 'package:fixnum/fixnum.dart';
|
import 'package:fixnum/fixnum.dart';
|
||||||
import 'package:grpc/grpc.dart';
|
import 'package:grpc/grpc.dart';
|
||||||
@ -114,6 +115,10 @@ class BackendService {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Future<Int64?> get accountId async {
|
||||||
|
return (await Session.session).accountId;
|
||||||
|
}
|
||||||
|
|
||||||
static Future<bool> createAccount(
|
static Future<bool> createAccount(
|
||||||
{required String email, required String password}) async {
|
{required String email, required String password}) async {
|
||||||
try {
|
try {
|
||||||
@ -207,14 +212,15 @@ class BackendService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Person> createPerson(
|
Future<Person> updatePerson(
|
||||||
{required String firstname,
|
{required Int64 id,
|
||||||
required String lastname,
|
String? firstname,
|
||||||
required String street,
|
String? lastname,
|
||||||
required String zip,
|
String? street,
|
||||||
required String city,
|
String? zip,
|
||||||
required String country,
|
String? city,
|
||||||
required DateTime birthday}) async {
|
String? country,
|
||||||
|
Timestamp? birthday}) async {
|
||||||
Session session = await Session.session;
|
Session session = await Session.session;
|
||||||
if (session.accessTokenExpiresAt == null) {
|
if (session.accessTokenExpiresAt == null) {
|
||||||
throw UnauthorizedException('Keine Siztung gefunden');
|
throw UnauthorizedException('Keine Siztung gefunden');
|
||||||
@ -226,16 +232,74 @@ class BackendService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
final CreatePersonResponse response = await _client.createPerson(
|
final UpdatePersonRequest req = UpdatePersonRequest(
|
||||||
CreatePersonRequest(
|
id: id,
|
||||||
accountId: session.accountId,
|
);
|
||||||
lastname: lastname,
|
|
||||||
firstname: firstname,
|
if (lastname != null) {
|
||||||
street: street,
|
req.lastname = lastname;
|
||||||
zip: zip,
|
}
|
||||||
country: country,
|
if (firstname != null) {
|
||||||
birthday: Timestamp.fromDateTime(birthday),
|
req.firstname = firstname;
|
||||||
),
|
}
|
||||||
|
if (street != null) {
|
||||||
|
req.street = street;
|
||||||
|
}
|
||||||
|
if (city != null) {
|
||||||
|
req.city = city;
|
||||||
|
}
|
||||||
|
if (zip != null) {
|
||||||
|
req.zip = zip;
|
||||||
|
}
|
||||||
|
if (country != null) {
|
||||||
|
req.country = country;
|
||||||
|
}
|
||||||
|
if (birthday != null) {
|
||||||
|
req.birthday = birthday;
|
||||||
|
}
|
||||||
|
final UpdatePersonResponse response = await _client.updatePerson(req,
|
||||||
|
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<Person> createPerson(
|
||||||
|
{required String firstname,
|
||||||
|
required String lastname,
|
||||||
|
required String street,
|
||||||
|
required String zip,
|
||||||
|
required String city,
|
||||||
|
required String country,
|
||||||
|
required Timestamp 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 CreatePersonRequest req = CreatePersonRequest(
|
||||||
|
accountId: session.accountId,
|
||||||
|
lastname: lastname,
|
||||||
|
firstname: firstname,
|
||||||
|
street: street,
|
||||||
|
city: city,
|
||||||
|
zip: zip,
|
||||||
|
country: country,
|
||||||
|
birthday: birthday,
|
||||||
|
);
|
||||||
|
final CreatePersonResponse response = await _client.createPerson(req,
|
||||||
options: CallOptions(
|
options: CallOptions(
|
||||||
metadata: {'Authorization': 'Bearer ${session.accessToken}'}));
|
metadata: {'Authorization': 'Bearer ${session.accessToken}'}));
|
||||||
return response.person;
|
return response.person;
|
||||||
|
@ -7,11 +7,12 @@ class AccountViewModel extends BaseViewModel {
|
|||||||
AccountViewModel() {
|
AccountViewModel() {
|
||||||
_init();
|
_init();
|
||||||
}
|
}
|
||||||
ApiResponse _apiResponse = ApiResponse.initial('Keine Daten');
|
final ApiResponse _apiResponse = ApiResponse.initial('Keine Daten');
|
||||||
|
|
||||||
final BackendService _service = BackendService();
|
final BackendService _service = BackendService();
|
||||||
Account? _account;
|
Account? _account;
|
||||||
|
|
||||||
|
@override
|
||||||
ApiResponse get response {
|
ApiResponse get response {
|
||||||
return _apiResponse;
|
return _apiResponse;
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
import 'package:app/model/apis/api_response.dart';
|
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/pb/google/protobuf/timestamp.pb.dart';
|
||||||
import 'package:app/pb/person.pb.dart';
|
import 'package:app/pb/person.pb.dart';
|
||||||
|
import 'package:app/util/colors.dart';
|
||||||
|
import 'package:fixnum/fixnum.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class PersonsViewModel with ChangeNotifier {
|
class PersonsViewModel with ChangeNotifier {
|
||||||
@ -35,8 +38,9 @@ class PersonsViewModel with ChangeNotifier {
|
|||||||
required String zip,
|
required String zip,
|
||||||
required String city,
|
required String city,
|
||||||
required String country,
|
required String country,
|
||||||
required DateTime birthday}) async {
|
required Timestamp birthday}) async {
|
||||||
Person person = Person();
|
Person person = Person();
|
||||||
|
final messenger = ScaffoldMessenger.of(context);
|
||||||
_apiResponse = ApiResponse.loading('Erstelle Person');
|
_apiResponse = ApiResponse.loading('Erstelle Person');
|
||||||
try {
|
try {
|
||||||
person = await _service.createPerson(
|
person = await _service.createPerson(
|
||||||
@ -47,8 +51,102 @@ class PersonsViewModel with ChangeNotifier {
|
|||||||
city: city,
|
city: city,
|
||||||
country: country,
|
country: country,
|
||||||
birthday: birthday);
|
birthday: birthday);
|
||||||
|
messenger.showSnackBar(SnackBar(
|
||||||
|
backgroundColor: CustomColors.success,
|
||||||
|
content: const Text(
|
||||||
|
'Gepeichert',
|
||||||
|
style: TextStyle(color: Colors.white),
|
||||||
|
),
|
||||||
|
));
|
||||||
_apiResponse = ApiResponse.completed(person);
|
_apiResponse = ApiResponse.completed(person);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
messenger.showSnackBar(SnackBar(
|
||||||
|
backgroundColor: CustomColors.error,
|
||||||
|
content: const Text(
|
||||||
|
'Fehler beim Speichern',
|
||||||
|
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(
|
||||||
|
err.toString(),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
));
|
||||||
|
_apiResponse = ApiResponse.error(err.toString());
|
||||||
|
}
|
||||||
|
notifyListeners();
|
||||||
|
return person;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Person> updatePerson(BuildContext context,
|
||||||
|
{required Int64 id,
|
||||||
|
String? firstname,
|
||||||
|
String? lastname,
|
||||||
|
String? street,
|
||||||
|
String? zip,
|
||||||
|
String? city,
|
||||||
|
String? country,
|
||||||
|
Timestamp? birthday}) async {
|
||||||
|
Person person = Person();
|
||||||
|
final messenger = ScaffoldMessenger.of(context);
|
||||||
|
_apiResponse = ApiResponse.loading('Erstelle Person');
|
||||||
|
try {
|
||||||
|
person = await _service.updatePerson(
|
||||||
|
id: id,
|
||||||
|
firstname: firstname,
|
||||||
|
lastname: lastname,
|
||||||
|
street: street,
|
||||||
|
zip: zip,
|
||||||
|
city: city,
|
||||||
|
country: country,
|
||||||
|
birthday: birthday);
|
||||||
|
messenger.showSnackBar(SnackBar(
|
||||||
|
backgroundColor: CustomColors.success,
|
||||||
|
content: const Text(
|
||||||
|
'Gepeichert',
|
||||||
|
style: TextStyle(color: Colors.white),
|
||||||
|
),
|
||||||
|
));
|
||||||
|
_apiResponse = ApiResponse.completed(person);
|
||||||
|
} catch (err) {
|
||||||
|
messenger.showSnackBar(SnackBar(
|
||||||
|
backgroundColor: CustomColors.error,
|
||||||
|
content: const Text(
|
||||||
|
'Fehler beim Speichern',
|
||||||
|
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(
|
||||||
|
err.toString(),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
));
|
||||||
_apiResponse = ApiResponse.error(err.toString());
|
_apiResponse = ApiResponse.error(err.toString());
|
||||||
}
|
}
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
339
frontend/app/lib/pages/person_details_page.dart
Normal file
339
frontend/app/lib/pages/person_details_page.dart
Normal file
@ -0,0 +1,339 @@
|
|||||||
|
import 'package:app/model/view_model/persons_vm.dart';
|
||||||
|
import 'package:app/pb/google/protobuf/timestamp.pb.dart';
|
||||||
|
import 'package:app/pb/person.pb.dart';
|
||||||
|
import 'package:app/util/validation.dart';
|
||||||
|
import 'package:app/widgets/background.dart';
|
||||||
|
import 'package:app/widgets/bottom_navigation.dart';
|
||||||
|
import 'package:app/widgets/bottom_navigation_item.dart';
|
||||||
|
import 'package:fixnum/fixnum.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
|
Future<Person> showPerson(BuildContext context, {Person? person}) async {
|
||||||
|
PersonsViewModel vm = PersonsViewModel();
|
||||||
|
|
||||||
|
final formKey = GlobalKey<FormState>();
|
||||||
|
final firstnameController = TextEditingController();
|
||||||
|
final lastnameController = TextEditingController();
|
||||||
|
final cityController = TextEditingController();
|
||||||
|
final zipController = TextEditingController();
|
||||||
|
final streetController = TextEditingController();
|
||||||
|
final countryController = TextEditingController();
|
||||||
|
final birthdayController = TextEditingController();
|
||||||
|
|
||||||
|
Future<void> _init() async {
|
||||||
|
if (person == null) {
|
||||||
|
person ??= Person();
|
||||||
|
// person ??= Person(accountId: await BackendService.accountId);
|
||||||
|
} else {
|
||||||
|
firstnameController.text = person!.firstname;
|
||||||
|
lastnameController.text = person!.lastname;
|
||||||
|
cityController.text = person!.city;
|
||||||
|
zipController.text = person!.zip;
|
||||||
|
streetController.text = person!.street;
|
||||||
|
countryController.text = person!.country;
|
||||||
|
birthdayController.text =
|
||||||
|
DateFormat('dd.MM.yyyy').format(person!.birthday.toDateTime());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await _init();
|
||||||
|
|
||||||
|
void _updateData() {
|
||||||
|
person!.firstname = firstnameController.text;
|
||||||
|
person!.lastname = lastnameController.text;
|
||||||
|
person!.city = cityController.text;
|
||||||
|
person!.street = streetController.text;
|
||||||
|
person!.zip = zipController.text;
|
||||||
|
person!.country = countryController.text;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> createPerson(BuildContext context) async {
|
||||||
|
final navigator = Navigator.of(context);
|
||||||
|
_updateData();
|
||||||
|
person!.id = Int64(0);
|
||||||
|
person = await vm.createPerson(context,
|
||||||
|
firstname: person!.firstname,
|
||||||
|
lastname: person!.lastname,
|
||||||
|
street: person!.street,
|
||||||
|
zip: person!.zip,
|
||||||
|
city: person!.city,
|
||||||
|
country: person!.country,
|
||||||
|
birthday: person!.birthday);
|
||||||
|
|
||||||
|
if (person!.id != 0) {
|
||||||
|
navigator.pop(person);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> updatePerson(BuildContext context) async {
|
||||||
|
final navigator = Navigator.of(context);
|
||||||
|
_updateData();
|
||||||
|
final personUpdate = await vm.updatePerson(context,
|
||||||
|
id: person!.id,
|
||||||
|
firstname: person!.firstname != firstnameController.text
|
||||||
|
? person!.firstname
|
||||||
|
: null,
|
||||||
|
lastname: person!.lastname != lastnameController.text
|
||||||
|
? person!.lastname
|
||||||
|
: null,
|
||||||
|
street: person!.street != streetController.text ? person!.street : null,
|
||||||
|
zip: person!.zip != zipController.text ? person!.zip : null,
|
||||||
|
city: person!.city != cityController.text ? person!.city : null,
|
||||||
|
country:
|
||||||
|
person!.country != countryController.text ? person!.country : null,
|
||||||
|
birthday:
|
||||||
|
DateFormat('dd.MM.yyyy').format(person!.birthday.toDateTime()) !=
|
||||||
|
birthdayController.text
|
||||||
|
? person!.birthday
|
||||||
|
: null);
|
||||||
|
|
||||||
|
if (personUpdate != person) {
|
||||||
|
navigator.pop(person);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ignore: use_build_context_synchronously
|
||||||
|
await showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
builder: (builder) {
|
||||||
|
return Background(
|
||||||
|
child: Scaffold(
|
||||||
|
bottomNavigationBar: BottomNavigation(
|
||||||
|
hideMenu: true,
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
person!.id == 0 ? 'Person anlegen' : 'Person anpassen',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontFamily: 'sans-serif',
|
||||||
|
fontSize: 24,
|
||||||
|
height: 1.6,
|
||||||
|
fontWeight: FontWeight.normal,
|
||||||
|
letterSpacing: 6),
|
||||||
|
),
|
||||||
|
ChangeNotifierProvider<PersonsViewModel>(
|
||||||
|
create: (context) => PersonsViewModel(),
|
||||||
|
child: Consumer<PersonsViewModel>(
|
||||||
|
builder: (context, value, child) => Form(
|
||||||
|
key: formKey,
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const SizedBox(
|
||||||
|
height: 40,
|
||||||
|
),
|
||||||
|
TextFormField(
|
||||||
|
controller: firstnameController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
fillColor: Color.fromARGB(30, 255, 255, 255),
|
||||||
|
filled: true,
|
||||||
|
suffix: Text('Vorname'),
|
||||||
|
hintStyle: TextStyle(
|
||||||
|
color: Colors.white38,
|
||||||
|
),
|
||||||
|
hintText: 'Vorname',
|
||||||
|
),
|
||||||
|
keyboardType: TextInputType.name,
|
||||||
|
validator: (value) {
|
||||||
|
if (value == null || !value.isValidName) {
|
||||||
|
return 'Bitte einen gültigen Vornamen eingeben';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
TextFormField(
|
||||||
|
controller: lastnameController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
fillColor: Color.fromARGB(30, 255, 255, 255),
|
||||||
|
filled: true,
|
||||||
|
suffix: Text('Nachname'),
|
||||||
|
hintStyle: TextStyle(
|
||||||
|
color: Colors.white38,
|
||||||
|
),
|
||||||
|
hintText: 'Nachname',
|
||||||
|
),
|
||||||
|
keyboardType: TextInputType.name,
|
||||||
|
validator: (value) {
|
||||||
|
if (value == null || !value.isValidName) {
|
||||||
|
return 'Bitte einen gültigen Nachnamen eingeben';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
TextFormField(
|
||||||
|
readOnly: true,
|
||||||
|
onTap: () async {
|
||||||
|
DateTime? pickedDate = await showDatePicker(
|
||||||
|
context: context,
|
||||||
|
locale: const Locale('de', 'DE'),
|
||||||
|
initialDate: DateTime.now(),
|
||||||
|
firstDate: DateTime(1930),
|
||||||
|
lastDate: DateTime(DateTime.now().year + 1),
|
||||||
|
builder: (context, child) => Theme(
|
||||||
|
data: ThemeData.dark(),
|
||||||
|
child: child != null ? child : Text(''),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (pickedDate != null) {
|
||||||
|
person!.birthday =
|
||||||
|
Timestamp.fromDateTime(pickedDate);
|
||||||
|
birthdayController.text =
|
||||||
|
DateFormat('dd.MM.yyyy')
|
||||||
|
.format(pickedDate);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
controller: birthdayController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
fillColor: Color.fromARGB(30, 255, 255, 255),
|
||||||
|
filled: true,
|
||||||
|
suffix: Text('Geburtstag'),
|
||||||
|
hintStyle: TextStyle(
|
||||||
|
color: Colors.white38,
|
||||||
|
),
|
||||||
|
hintText: 'Geburtstag',
|
||||||
|
),
|
||||||
|
keyboardType: TextInputType.name,
|
||||||
|
// validator: (value) {
|
||||||
|
// if (value == null || !value.isValidName) {
|
||||||
|
// return 'Bitte einen gültigen Nachnamen eingeben';
|
||||||
|
// }
|
||||||
|
// return null;
|
||||||
|
// },
|
||||||
|
),
|
||||||
|
TextFormField(
|
||||||
|
controller: streetController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
fillColor: Color.fromARGB(30, 255, 255, 255),
|
||||||
|
filled: true,
|
||||||
|
suffix: Text('Straße'),
|
||||||
|
hintStyle: TextStyle(
|
||||||
|
color: Colors.white38,
|
||||||
|
),
|
||||||
|
hintText: 'Straße mit Hausnummer',
|
||||||
|
),
|
||||||
|
keyboardType: TextInputType.name,
|
||||||
|
validator: (value) {
|
||||||
|
if (value == null || !value.isValidName) {
|
||||||
|
return 'Bitte eine gültige Straße mit Hausnummer eingeben';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
TextFormField(
|
||||||
|
controller: zipController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
fillColor: Color.fromARGB(30, 255, 255, 255),
|
||||||
|
filled: true,
|
||||||
|
suffix: Text('PLZ'),
|
||||||
|
hintStyle: TextStyle(
|
||||||
|
color: Colors.white38,
|
||||||
|
),
|
||||||
|
hintText: 'PLZ',
|
||||||
|
),
|
||||||
|
keyboardType: TextInputType.name,
|
||||||
|
validator: (value) {
|
||||||
|
if (value == null || !value.isValidName) {
|
||||||
|
return 'Bitte eine gültige PLZ eingeben';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
TextFormField(
|
||||||
|
controller: cityController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
fillColor: Color.fromARGB(30, 255, 255, 255),
|
||||||
|
filled: true,
|
||||||
|
suffix: Text('Stadt'),
|
||||||
|
hintStyle: TextStyle(
|
||||||
|
color: Colors.white38,
|
||||||
|
),
|
||||||
|
hintText: 'Stadt',
|
||||||
|
),
|
||||||
|
keyboardType: TextInputType.name,
|
||||||
|
validator: (value) {
|
||||||
|
if (value == null || !value.isValidName) {
|
||||||
|
return 'Bitte eine gültige Stadt eingeben';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
TextFormField(
|
||||||
|
controller: countryController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
fillColor: Color.fromARGB(30, 255, 255, 255),
|
||||||
|
filled: true,
|
||||||
|
suffix: Text('Land'),
|
||||||
|
hintStyle: TextStyle(
|
||||||
|
color: Colors.white38,
|
||||||
|
),
|
||||||
|
hintText: 'Land',
|
||||||
|
),
|
||||||
|
keyboardType: TextInputType.name,
|
||||||
|
validator: (value) {
|
||||||
|
if (value == null || !value.isValidName) {
|
||||||
|
return 'Bitte ein gültiges Land eingeben';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 15,
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
|
children: [
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () async {
|
||||||
|
person!.id.isZero
|
||||||
|
? await createPerson(context)
|
||||||
|
: await updatePerson(context);
|
||||||
|
},
|
||||||
|
child: const Icon(Icons.update),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
useSafeArea: true,
|
||||||
|
isScrollControlled: true,
|
||||||
|
backgroundColor: Colors.black);
|
||||||
|
return person!;
|
||||||
|
}
|
@ -2,7 +2,9 @@ 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/persons_vm.dart';
|
import 'package:app/model/view_model/persons_vm.dart';
|
||||||
import 'package:app/pages/home_page.dart';
|
import 'package:app/pages/home_page.dart';
|
||||||
|
import 'package:app/pages/person_details_page.dart';
|
||||||
import 'package:app/pb/person.pb.dart';
|
import 'package:app/pb/person.pb.dart';
|
||||||
|
import 'package:app/util/validation.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';
|
||||||
@ -61,13 +63,56 @@ class _PersonsPageState extends State<PersonsPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
List<Widget> _personsList(List<Person> persons) {
|
List<Widget> _personsList(List<Person> persons) {
|
||||||
|
persons.sort((a, b) {
|
||||||
|
final comp = a.lastname.compareTo(b.lastname);
|
||||||
|
if (comp != 0) {
|
||||||
|
return comp;
|
||||||
|
}
|
||||||
|
return a.firstname.compareTo(b.firstname);
|
||||||
|
});
|
||||||
final List<Widget> list = [];
|
final List<Widget> list = [];
|
||||||
for (var p in persons) {
|
for (var p in persons) {
|
||||||
list.add(Card(
|
list.add(TextButton(
|
||||||
color: Colors.black,
|
onPressed: () async {
|
||||||
child: Text(
|
final Person per = await showPerson(context, person: p);
|
||||||
'${p.firstname} ${p.lastname}',
|
setState(() {
|
||||||
style: const TextStyle(color: Colors.white),
|
this.persons.add(per);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: Card(
|
||||||
|
shape:
|
||||||
|
RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||||
|
color: const Color.fromARGB(100, 89, 88, 88),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 14),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
height: 40,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
p.lastname.titleCase,
|
||||||
|
style: const TextStyle(color: Colors.white),
|
||||||
|
// overflow: TextOverflow.fade,
|
||||||
|
textAlign: TextAlign.start,
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
Text(
|
||||||
|
p.firstname.titleCase,
|
||||||
|
style: const TextStyle(color: Colors.white),
|
||||||
|
textAlign: TextAlign.start,
|
||||||
|
// overflow: TextOverflow.fade,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
const Text('STATUS')
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@ -80,7 +125,14 @@ class _PersonsPageState extends State<PersonsPage> {
|
|||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
|
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
|
||||||
floatingActionButton: FloatingActionButton(
|
floatingActionButton: FloatingActionButton(
|
||||||
onPressed: () {},
|
onPressed: () async {
|
||||||
|
final p = await showPerson(context);
|
||||||
|
if (!p.id.isZero) {
|
||||||
|
setState(() {
|
||||||
|
persons.add(p);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
child: const Icon(Icons.add),
|
child: const Icon(Icons.add),
|
||||||
),
|
),
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
|
@ -1,9 +1,5 @@
|
|||||||
import 'package:app/gapi/client.dart';
|
|
||||||
import 'package:app/pages_old/start_page.dart';
|
|
||||||
import 'package:app/pb/account_info.pb.dart';
|
import 'package:app/pb/account_info.pb.dart';
|
||||||
import 'package:app/pb/rpc_get_account_info.pb.dart';
|
|
||||||
import 'package:app/widgets/background.dart';
|
import 'package:app/widgets/background.dart';
|
||||||
import 'package:app/widgets/bottom_bar.dart';
|
|
||||||
import 'package:app/widgets/loading_widget.dart';
|
import 'package:app/widgets/loading_widget.dart';
|
||||||
import 'package:app/widgets/side_drawer.dart';
|
import 'package:app/widgets/side_drawer.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
@ -1,12 +1,9 @@
|
|||||||
import 'package:app/gapi/client.dart';
|
|
||||||
import 'package:app/model/services/backend_service.dart';
|
import 'package:app/model/services/backend_service.dart';
|
||||||
import 'package:app/pages_old/start_page.dart';
|
|
||||||
import 'package:app/widgets/background.dart';
|
import 'package:app/widgets/background.dart';
|
||||||
import 'package:app/widgets/bottom_bar.dart';
|
import 'package:app/widgets/bottom_bar.dart';
|
||||||
import 'package:app/widgets/loading_widget.dart';
|
import 'package:app/widgets/loading_widget.dart';
|
||||||
import 'package:app/widgets/side_drawer.dart';
|
import 'package:app/widgets/side_drawer.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:grpc/grpc.dart';
|
|
||||||
|
|
||||||
// GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
|
// GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
|
||||||
|
|
||||||
|
@ -1,11 +1,8 @@
|
|||||||
import 'package:app/gapi/client.dart';
|
|
||||||
import 'package:app/pages_old/start_page.dart';
|
|
||||||
import 'package:app/widgets/background.dart';
|
import 'package:app/widgets/background.dart';
|
||||||
import 'package:app/widgets/bottom_bar.dart';
|
import 'package:app/widgets/bottom_bar.dart';
|
||||||
import 'package:app/widgets/loading_widget.dart';
|
import 'package:app/widgets/loading_widget.dart';
|
||||||
import 'package:app/widgets/side_drawer.dart';
|
import 'package:app/widgets/side_drawer.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:grpc/grpc.dart';
|
|
||||||
|
|
||||||
class RegisterPage extends StatefulWidget {
|
class RegisterPage extends StatefulWidget {
|
||||||
const RegisterPage({
|
const RegisterPage({
|
||||||
|
@ -1,9 +1,6 @@
|
|||||||
import 'package:app/gapi/client.dart';
|
|
||||||
import 'package:app/model/apis/api_response.dart';
|
import 'package:app/model/apis/api_response.dart';
|
||||||
import 'package:app/model/view_model/account_vm.dart';
|
import 'package:app/model/view_model/account_vm.dart';
|
||||||
import 'package:app/pages_old/dashboard_page.dart';
|
|
||||||
import 'package:app/pages_old/login_page.dart';
|
import 'package:app/pages_old/login_page.dart';
|
||||||
import 'package:app/pages_old/register_page.dart';
|
|
||||||
import 'package:app/pb/account.pb.dart';
|
import 'package:app/pb/account.pb.dart';
|
||||||
import 'package:app/widgets/background.dart';
|
import 'package:app/widgets/background.dart';
|
||||||
import 'package:app/widgets/bottom_bar.dart';
|
import 'package:app/widgets/bottom_bar.dart';
|
||||||
|
@ -24,4 +24,10 @@ extension valString on String {
|
|||||||
bool get isValidPhone {
|
bool get isValidPhone {
|
||||||
return phoneRegExp.hasMatch(this);
|
return phoneRegExp.hasMatch(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String get titleCase {
|
||||||
|
return split(' ')
|
||||||
|
.map((str) => str[0].toUpperCase() + str.substring(1))
|
||||||
|
.join(' ');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,10 +7,13 @@ class BottomNavigation extends StatelessWidget {
|
|||||||
required this.children,
|
required this.children,
|
||||||
this.backgroundColor,
|
this.backgroundColor,
|
||||||
this.iconColor,
|
this.iconColor,
|
||||||
|
this.hideMenu,
|
||||||
}) {
|
}) {
|
||||||
|
hideMenu ??= false;
|
||||||
backgroundColor ??= Colors.black;
|
backgroundColor ??= Colors.black;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool? hideMenu;
|
||||||
List<Widget> children;
|
List<Widget> children;
|
||||||
Color? backgroundColor;
|
Color? backgroundColor;
|
||||||
Color? iconColor;
|
Color? iconColor;
|
||||||
@ -32,14 +35,18 @@ class BottomNavigation extends StatelessWidget {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
...children,
|
...children,
|
||||||
Builder(builder: (context) {
|
if (!hideMenu!)
|
||||||
return IconButton(
|
Builder(
|
||||||
onPressed: () => Scaffold.of(context).openDrawer(),
|
builder: (context) {
|
||||||
icon: const Icon(
|
return IconButton(
|
||||||
Icons.menu,
|
onPressed: () => Scaffold.of(context).openDrawer(),
|
||||||
color: Colors.white,
|
icon: const Icon(
|
||||||
));
|
Icons.menu,
|
||||||
}),
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -110,6 +110,11 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.3"
|
version: "2.0.3"
|
||||||
|
flutter_localizations:
|
||||||
|
dependency: "direct main"
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
flutter_test:
|
flutter_test:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description: flutter
|
description: flutter
|
||||||
@ -155,6 +160,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.0.2"
|
version: "4.0.2"
|
||||||
|
intl:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: intl
|
||||||
|
sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.18.1"
|
||||||
js:
|
js:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -33,6 +33,8 @@ dependencies:
|
|||||||
collection: ^1.15.0-nullsafety.4
|
collection: ^1.15.0-nullsafety.4
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
flutter_localizations:
|
||||||
|
sdk: flutter
|
||||||
|
|
||||||
|
|
||||||
# The following adds the Cupertino Icons font to your application.
|
# The following adds the Cupertino Icons font to your application.
|
||||||
@ -43,6 +45,7 @@ dependencies:
|
|||||||
path: ^1.8.3
|
path: ^1.8.3
|
||||||
fixnum: ^1.1.0
|
fixnum: ^1.1.0
|
||||||
provider: ^6.0.5
|
provider: ^6.0.5
|
||||||
|
intl: ^0.18.1
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
lints: ^2.0.0
|
lints: ^2.0.0
|
||||||
|
Loading…
x
Reference in New Issue
Block a user