ft/adds person_detail_page

This commit is contained in:
itsscb 2023-11-07 14:48:55 +01:00
parent e2d3720728
commit 6d48638ef1
15 changed files with 622 additions and 47 deletions

View File

@ -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,

View File

@ -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";
} }

View File

@ -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;

View File

@ -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;
} }

View File

@ -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();

View 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!;
}

View File

@ -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(

View File

@ -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';

View File

@ -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>();

View File

@ -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({

View File

@ -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';

View File

@ -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(' ');
}
} }

View File

@ -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,
),
);
},
)
], ],
), ),
), ),

View File

@ -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:

View File

@ -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