diff --git a/lib/main.dart b/lib/main.dart index 4b87d97..6836f33 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'app_theme.dart'; import 'model/repositories/local_repository.dart'; +import 'pages/locations_overview_page.dart'; import 'pages/task_edit_page.dart'; import 'pages/task_overview_page.dart'; import 'service/controller_scope.dart'; @@ -39,6 +40,7 @@ class MainApp extends StatelessWidget { routes: { TaskOverviewPage.routeName: (context) => TaskOverviewPage(), TaskEditPage.routeName: (context) => TaskEditPage(), + LocationsOverviewPage.routeName: (context) => LocationsOverviewPage(), }, initialRoute: TaskOverviewPage.routeName, ); diff --git a/lib/pages/locations_overview_page.dart b/lib/pages/locations_overview_page.dart new file mode 100644 index 0000000..b1835bd --- /dev/null +++ b/lib/pages/locations_overview_page.dart @@ -0,0 +1,74 @@ +import 'package:flutter/material.dart'; + +import '../model/extensions/controller_context.dart'; +import '../model/location.dart'; +import '../service/controllers/location_controller.dart'; +import '../widgets/dialogs/create_location_dialog.dart'; + +class LocationsOverviewPage extends StatefulWidget { + static const routeName = '/locations'; + const LocationsOverviewPage({super.key}); + + @override + State createState() => _LocationsOverviewPageState(); +} + +class _LocationsOverviewPageState extends State { + List locations = []; + @override + Widget build(BuildContext context) { + locations = context.controller().locations; + + return Scaffold( + appBar: AppBar(title: Text('Manage Locations')), + body: Padding( + padding: EdgeInsetsGeometry.symmetric( + horizontal: MediaQuery.of(context).size.width * 0.05, + ), + child: ListView.builder( + itemBuilder: listViewBuilder, + itemCount: context.controller().locations.length, + ), + ), + floatingActionButton: FloatingActionButton( + onPressed: onAddLocationButtonPressed, + child: Icon(Icons.add), + ), + ); + } + + Widget listViewBuilder(BuildContext context, int index) { + final location = locations.elementAt(index); + final String subtitle = location.address.isEmpty + ? location.coordinates.toString() + : location.address; + + return ListTile( + title: Text(location.name), + subtitle: Text(subtitle), + onTap: () => onEditLocationButtonPressed(location), + ); + } + + void onAddLocationButtonPressed() async { + final result = await showDialog( + context: context, + builder: (context) => CreateLocationDialog(), + barrierDismissible: false, + ); + if (mounted && result != null) { + context.controller().addLocation(result); + } + } + + void onEditLocationButtonPressed(Location location) async { + final result = await showDialog( + context: context, + builder: (context) => CreateLocationDialog(initialLocation: location), + barrierDismissible: false, + ); + if (mounted && result != null) { + context.controller().updateLocation(location, result); + } + } +} diff --git a/lib/pages/task_overview_page.dart b/lib/pages/task_overview_page.dart index 6ef7ddd..43aa664 100644 --- a/lib/pages/task_overview_page.dart +++ b/lib/pages/task_overview_page.dart @@ -6,6 +6,7 @@ import '../model/task.dart'; import '../service/controllers/task_controller.dart'; import '../service/tools.dart'; import '../widgets/task_dismissible.dart'; +import 'locations_overview_page.dart'; import 'task_edit_page.dart'; class TaskOverviewPage extends StatefulWidget { @@ -22,7 +23,20 @@ class _TaskOverviewPageState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar(title: Text('Hallo Yannick')), + appBar: AppBar( + title: Text('Hallo Yannick'), + actions: [ + PopupMenuButton( + itemBuilder: (_) => [ + PopupMenuItem( + onTap: onLocationsButtonTapped, + child: Text('Locations'), + ), + ], + icon: Icon(Icons.more_vert), + ), + ], + ), body: Padding( padding: EdgeInsetsGeometry.symmetric( horizontal: MediaQuery.of(context).size.width * 0.05, @@ -84,10 +98,13 @@ class _TaskOverviewPageState extends State { await Navigator.of(context).pushNamed(TaskEditPage.routeName) as CreateTaskRequest?; - if (result != null && context.mounted) { + if (result != null && mounted) { context.controller().saveTask( result.toTask(id: generateId()), ); } } + + void onLocationsButtonTapped() => + Navigator.of(context).pushNamed(LocationsOverviewPage.routeName); } diff --git a/lib/widgets/dialogs/create_location_dialog.dart b/lib/widgets/dialogs/create_location_dialog.dart new file mode 100644 index 0000000..0f47ce9 --- /dev/null +++ b/lib/widgets/dialogs/create_location_dialog.dart @@ -0,0 +1,93 @@ +import 'package:flutter/material.dart'; + +import '../../app_theme.dart'; +import '../../model/latlng.dart'; +import '../../model/location.dart'; +import '../../service/validators.dart'; + +class CreateLocationDialog extends StatefulWidget { + const CreateLocationDialog({super.key, this.initialLocation}); + final Location? initialLocation; + + @override + State createState() => _CreateLocationDialogState(); +} + +class _CreateLocationDialogState extends State { + final nameController = TextEditingController(); + final addressController = TextEditingController(); + final coordinatesController = TextEditingController(); + final formKey = GlobalKey(debugLabel: 'Create Location Form'); + + @override + void initState() { + if (widget.initialLocation != null) { + nameController.text = widget.initialLocation!.name; + addressController.text = widget.initialLocation!.address; + coordinatesController.text = widget.initialLocation!.coordinates + .toString(); + } + super.initState(); + } + + @override + Widget build(BuildContext context) { + return AlertDialog( + actions: [ + TextButton(onPressed: onCancelPressed, child: Text('Cancel')), + TextButton(onPressed: onSavePressed, child: Text('Save')), + ], + title: Text('Create Location'), + content: Form( + key: formKey, + child: Column( + spacing: AppTheme.formColumnSpacing, + mainAxisSize: MainAxisSize.min, + children: [ + TextFormField( + autofocus: true, + textInputAction: TextInputAction.next, + controller: nameController, + keyboardType: TextInputType.text, + decoration: InputDecoration(labelText: 'Name'), + validator: notEmptyValidator, + ), + TextFormField( + textInputAction: TextInputAction.next, + controller: addressController, + keyboardType: TextInputType.streetAddress, + decoration: InputDecoration(labelText: 'Address (optional)'), + ), + TextFormField( + textInputAction: TextInputAction.done, + controller: coordinatesController, + onFieldSubmitted: (_) => onSavePressed(), + keyboardType: TextInputType.numberWithOptions(), + decoration: InputDecoration( + labelText: 'Coordinates', + hint: Text('25.5892, 50.5051662'), + ), + validator: (value) { + return notEmptyValidator(value) ?? coordinatesValidator(value); + }, + ), + ], + ), + ), + ); + } + + void onCancelPressed() => Navigator.of(context).pop(); + + void onSavePressed() { + if (formKey.currentState!.validate()) { + Navigator.of(context).pop( + Location( + name: nameController.text, + coordinates: LatLng.fromString(coordinatesController.text), + address: addressController.text, + ), + ); + } + } +}