143 lines
4.6 KiB
Dart
143 lines
4.6 KiB
Dart
/// Validated form that inserts a new [Item] into [ItemController].
|
|
library;
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:provider/provider.dart';
|
|
|
|
import '../models/item.dart';
|
|
import '../providers/item_provider.dart';
|
|
import '../widgets/app_text_field.dart';
|
|
import '../widgets/responsive_body.dart';
|
|
|
|
class AddItemScreen extends StatefulWidget {
|
|
const AddItemScreen({super.key});
|
|
|
|
@override
|
|
State<AddItemScreen> createState() => _AddItemScreenState();
|
|
}
|
|
|
|
class _AddItemScreenState extends State<AddItemScreen> {
|
|
final _formKey = GlobalKey<FormState>();
|
|
final _title = TextEditingController();
|
|
final _subtitle = TextEditingController();
|
|
final _description = TextEditingController();
|
|
ItemCategory? _category;
|
|
bool _submitted = false;
|
|
|
|
@override
|
|
void dispose() {
|
|
_title.dispose();
|
|
_subtitle.dispose();
|
|
_description.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
String? _requiredLength(String? value, {required int min, String? label}) {
|
|
final text = value?.trim() ?? '';
|
|
if (text.isEmpty) return 'Enter a ${label ?? 'value'}.';
|
|
if (text.length < min) {
|
|
return '${label ?? 'This field'} must be at least $min characters.';
|
|
}
|
|
return null;
|
|
}
|
|
|
|
void _submit() {
|
|
setState(() => _submitted = true);
|
|
final form = _formKey.currentState;
|
|
if (form == null || !form.validate() || _category == null) return;
|
|
|
|
context.read<ItemController>().addItem(
|
|
title: _title.text,
|
|
subtitle: _subtitle.text,
|
|
description: _description.text,
|
|
category: _category!,
|
|
);
|
|
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(content: Text('Added “${_title.text.trim()}”')),
|
|
);
|
|
Navigator.of(context).pop();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final theme = Theme.of(context);
|
|
|
|
return Scaffold(
|
|
appBar: AppBar(title: const Text('Add item')),
|
|
body: ResponsiveBody(
|
|
child: Form(
|
|
key: _formKey,
|
|
autovalidateMode: _submitted
|
|
? AutovalidateMode.onUserInteraction
|
|
: AutovalidateMode.disabled,
|
|
child: ListView(
|
|
children: [
|
|
const SizedBox(height: 8),
|
|
Text(
|
|
'Create a catalog entry. All fields are required.',
|
|
style: theme.textTheme.bodyMedium,
|
|
),
|
|
const SizedBox(height: 20),
|
|
AppTextField(
|
|
controller: _title,
|
|
label: 'Title',
|
|
hint: 'Give it a short name',
|
|
autofocus: true,
|
|
textInputAction: TextInputAction.next,
|
|
validator: (value) =>
|
|
_requiredLength(value, min: 3, label: 'Title'),
|
|
),
|
|
const SizedBox(height: 16),
|
|
AppTextField(
|
|
controller: _subtitle,
|
|
label: 'Summary',
|
|
hint: 'One sentence overview',
|
|
textInputAction: TextInputAction.next,
|
|
validator: (value) =>
|
|
_requiredLength(value, min: 8, label: 'Summary'),
|
|
),
|
|
const SizedBox(height: 16),
|
|
DropdownButtonFormField<ItemCategory>(
|
|
initialValue: _category,
|
|
decoration: const InputDecoration(labelText: 'Category'),
|
|
items: [
|
|
for (final category in ItemCategory.values)
|
|
DropdownMenuItem(
|
|
value: category,
|
|
child: Text(category.label),
|
|
),
|
|
],
|
|
onChanged: (value) => setState(() => _category = value),
|
|
validator: (value) =>
|
|
value == null ? 'Choose a category.' : null,
|
|
),
|
|
const SizedBox(height: 16),
|
|
AppTextField(
|
|
controller: _description,
|
|
label: 'Description',
|
|
hint: 'A longer note the detail screen will show',
|
|
maxLines: 5,
|
|
keyboardType: TextInputType.multiline,
|
|
validator: (value) =>
|
|
_requiredLength(value, min: 16, label: 'Description'),
|
|
),
|
|
const SizedBox(height: 24),
|
|
FilledButton(
|
|
onPressed: _submit,
|
|
child: const Text('Save item'),
|
|
),
|
|
const SizedBox(height: 12),
|
|
OutlinedButton(
|
|
onPressed: () => Navigator.of(context).pop(),
|
|
child: const Text('Cancel'),
|
|
),
|
|
const SizedBox(height: 24),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|