todo_app/lib/features/todo/presentation/screens/todo_detail_screen.dart

204 lines
6.3 KiB
Dart
Raw Normal View History

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import '../../domain/entities/create_todo_params.dart';
import '../../domain/entities/update_todo_params.dart';
import '../providers/todo_providers.dart';
class TodoDetailScreen extends ConsumerStatefulWidget {
final String? todoId;
const TodoDetailScreen({super.key, this.todoId});
@override
ConsumerState<TodoDetailScreen> createState() => _TodoDetailScreenState();
}
class _TodoDetailScreenState extends ConsumerState<TodoDetailScreen> {
final _formKey = GlobalKey<FormState>();
late final TextEditingController _titleController;
late final TextEditingController _descriptionController;
String _priority = 'medium';
DateTime? _dueDate;
bool _isLoading = false;
bool get isEditing => widget.todoId != null;
@override
void initState() {
super.initState();
_titleController = TextEditingController();
_descriptionController = TextEditingController();
if (isEditing) {
_loadTodo();
}
}
Future<void> _loadTodo() async {
final todoAsync = ref.read(todoDetailProvider(widget.todoId!));
todoAsync.whenData((todo) {
_titleController.text = todo.title;
_descriptionController.text = todo.description ?? '';
_priority = todo.priority;
_dueDate = todo.dueDate;
});
}
@override
void dispose() {
_titleController.dispose();
_descriptionController.dispose();
super.dispose();
}
Future<void> _save() async {
if (!_formKey.currentState!.validate()) return;
setState(() => _isLoading = true);
try {
if (isEditing) {
await ref.read(todoListProvider.notifier).updateTodo(
widget.todoId!,
UpdateTodoParams(
title: _titleController.text,
description: _descriptionController.text.isEmpty
? null
: _descriptionController.text,
priority: _priority,
dueDate: _dueDate,
),
);
} else {
await ref.read(todoListProvider.notifier).createTodo(
CreateTodoParams(
title: _titleController.text,
description: _descriptionController.text.isEmpty
? null
: _descriptionController.text,
priority: _priority,
dueDate: _dueDate,
),
);
}
if (mounted) context.pop();
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Failed to save: $e')),
);
}
} finally {
if (mounted) setState(() => _isLoading = false);
}
}
Future<void> _pickDueDate() async {
final date = await showDatePicker(
context: context,
initialDate: _dueDate ?? DateTime.now().add(const Duration(days: 1)),
firstDate: DateTime.now(),
lastDate: DateTime.now().add(const Duration(days: 365)),
);
if (date != null) {
setState(() => _dueDate = date);
}
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Scaffold(
appBar: AppBar(
title: Text(isEditing ? 'Edit Todo' : 'New Todo'),
),
body: Form(
key: _formKey,
child: ListView(
padding: const EdgeInsets.all(16),
children: [
TextFormField(
controller: _titleController,
decoration: const InputDecoration(
labelText: 'Title',
border: OutlineInputBorder(),
),
validator: (value) {
if (value == null || value.trim().isEmpty) {
return 'Title is required';
}
return null;
},
autofocus: !isEditing,
),
const SizedBox(height: 16),
TextFormField(
controller: _descriptionController,
decoration: const InputDecoration(
labelText: 'Description (optional)',
border: OutlineInputBorder(),
),
maxLines: 3,
),
const SizedBox(height: 16),
DropdownButtonFormField<String>(
initialValue: _priority,
decoration: const InputDecoration(
labelText: 'Priority',
border: OutlineInputBorder(),
),
items: const [
DropdownMenuItem(value: 'low', child: Text('Low')),
DropdownMenuItem(value: 'medium', child: Text('Medium')),
DropdownMenuItem(value: 'high', child: Text('High')),
],
onChanged: (value) {
if (value != null) setState(() => _priority = value);
},
),
const SizedBox(height: 16),
InkWell(
onTap: _pickDueDate,
child: InputDecorator(
decoration: const InputDecoration(
labelText: 'Due Date (optional)',
border: OutlineInputBorder(),
suffixIcon: Icon(Icons.calendar_today),
),
child: Text(
_dueDate != null
? '${_dueDate!.year}-${_dueDate!.month.toString().padLeft(2, '0')}-${_dueDate!.day.toString().padLeft(2, '0')}'
: 'No date selected',
style: theme.textTheme.bodyLarge?.copyWith(
color: _dueDate != null ? null : theme.colorScheme.onSurfaceVariant,
),
),
),
),
if (_dueDate != null)
TextButton(
onPressed: () => setState(() => _dueDate = null),
child: const Text('Clear date'),
),
const SizedBox(height: 24),
FilledButton(
onPressed: _isLoading ? null : _save,
child: _isLoading
? const SizedBox(
height: 20,
width: 20,
child: CircularProgressIndicator(strokeWidth: 2),
)
: Text(isEditing ? 'Save Changes' : 'Create Todo'),
),
],
),
),
);
}
}