first
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
/// 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),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user